diff --git a/atst/routes/portfolios/task_orders.py b/atst/routes/portfolios/task_orders.py index b8e76ef3..8bcb4715 100644 --- a/atst/routes/portfolios/task_orders.py +++ b/atst/routes/portfolios/task_orders.py @@ -61,3 +61,16 @@ def view_task_order(portfolio_id, task_order_id): return render_template( "portfolios/task_orders/show.html", portfolio=portfolio, task_order=task_order ) + + +@portfolios_bp.route( + "/portfolios//task_order//invitations" +) +def task_order_invitations(portfolio_id, task_order_id): + portfolio = Portfolios.get(g.current_user, portfolio_id) + task_order = TaskOrders.get(g.current_user, task_order_id) + return render_template( + "portfolios/task_orders/invitations.html", + portfolio=portfolio, + task_order=task_order, + ) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 440ee0a2..b63d064f 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -267,3 +267,84 @@ } } } + +.task-order-invitations { + .task-order-invitations__heading { + margin-bottom: 0; + + &.subheading .h2 { + color: $color-gray; + } + } + + .officer { + margin-top: 0; + margin-left: 0; + margin-right: 0; + margin-bottom: $gap; + padding-bottom: 4 * $gap; + + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: $color-gray-light; + + &:last-child { + border-bottom-width: 0; + } + + h2 { + color: $color-gray; + } + + .officer__info { + .officer__info--name { + font-weight: bold; + } + + p { + margin: .5rem 0; + } + + .officer__info--name { + margin-right: 2 * $gap; + } + + .officer__info--status { + font-weight: bold; + + &.invited { + color: $color-green; + .icon { + @include icon-color($color-green); + } + } + + &.uninvited { + color: $color-red; + .icon { + @include icon-color($color-red); + } + } + } + } + + .officer__actions { + margin-left: -2 * $gap; + + button { + margin-left: 2 * $gap; + } + + .icon-link { + margin: 0 $gap; + } + + .remove { + color: $color-red; + .icon { + @include icon-color($color-red); + } + } + } + } +} diff --git a/templates/portfolios/task_orders/invitations.html b/templates/portfolios/task_orders/invitations.html new file mode 100644 index 00000000..0ebad204 --- /dev/null +++ b/templates/portfolios/task_orders/invitations.html @@ -0,0 +1,94 @@ +{% extends "portfolios/base.html" %} + +{% from "components/icon.html" import Icon %} + +{% macro Link(text, icon_name, url='#', classes='') %} + + {{ Icon(icon_name) }} + {{ text }} + +{% endmacro %} + +{% macro OfficerInfo(task_order, officer_type) %} +
+

{{ ("task_orders.invitations." + officer_type + ".title") | translate }}

+

{{ ("task_orders.invitations." + officer_type + ".description") | translate }}

+ + {% set prefix = { "contracting_officer": "ko", "contracting_officer_representative": "cor", "security_officer": "so" }[officer_type] %} + {% set first_name = task_order[prefix + "_first_name"] %} + {% set last_name = task_order[prefix + "_last_name"] %} + {% set email = task_order[prefix + "_email"] %} + {% set phone_number = task_order[prefix + "_phone_number"] %} + {% set dod_id = task_order[prefix + "_dod_id"] %} + + {% if task_order[officer_type] %} +
+
+
{{ first_name }} {{ last_name }}
+
+ {{ Icon("ok", classes="invited") }} + Invited +
+
+ +

{{ phone_number | usPhone }}

+

{{ "task_orders.invitations.dod_id_label" | translate}}: {{ dod_id }}

+
+
+ {{ Link("Update", "edit") }} + {{ Link("Resend Invitation", "avatar") }} + {{ Link("Remove", "trash", classes="remove") }} +
+ {% elif first_name and last_name %} +
+
+
{{ first_name }} {{ last_name }}
+
+ {{ Icon("alert", classes="uninvited") }} + Not Invited +
+
+ +

{{ phone_number | usPhone }}

+
+
+ {{ Link("Update", "edit") }} + {{ Link("Remove", "trash", classes="remove") }} + +
+ {% else %} +
+
+ {{ Icon("alert", classes="uninvited") }} + Not specified +
+
+
+ +
+ {% endif %} +
+{% endmacro %} + +{% block portfolio_content %} +
+ {% include "fragments/flash.html" %} + +
+
+

+
Edit Task Order
+ Oversight +

+
+ + {% for officer in ["contracting_officer", "contracting_officer_representative", "security_officer"] %} + {{ OfficerInfo(task_order, officer) }} + {% endfor %} +
+
+{% endblock %} diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 11e45c4d..45cafdfa 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -153,7 +153,7 @@ {{ InvitationStatus('Contracting Officer Representative', task_order.contracting_officer_representative) }} {{ InvitationStatus('IA Security Officer', officer=task_order.security_officer) }} - + {{ Icon("edit") }} manage invitations diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index f1bb3425..6bebd3aa 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -86,3 +86,17 @@ def test_ko_can_view_task_order(client, user_session): ) ) assert response.status_code == 200 + + +def test_can_view_task_order_invitations(client, user_session): + portfolio = PortfolioFactory.create() + user_session(portfolio.owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + response = client.get( + url_for( + "portfolios.task_order_invitations", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + ) + assert response.status_code == 200 diff --git a/translations.yaml b/translations.yaml index 2cf3d4e7..8a188943 100644 --- a/translations.yaml +++ b/translations.yaml @@ -438,6 +438,23 @@ task_orders: invited: Invited not_invited: Not Yet Invited not_uploaded: Not Uploaded + invitations: + dod_id_label: DoD ID + contracting_officer: + title: Contracting Officer (KO) Information + description: You'll need a signature from your KO. You might want to work with your program Financial Manager to get your TO documents moved in the right direction. + add_button_text: Add / Invite KO + invite_button_text: Invite KO + contracting_officer_representative: + title: Contracting Officer Representative (COR) Information + description: Your COR may assist in submitting the Task Order documents within their official system of record. + add_button_text: Add / Invite COR + invite_button_text: Invite COR + security_officer: + title: IA Security Officer Information + description: Your Security Officer will need to answer some security configuration questions in order to generate a DD-254 document, then electronically sign. + add_button_text: Add / Invite Security Officer + invite_button_text: Invite Security Officer testing: example_string: Hello World example_with_variables: 'Hello, {name}!'