Merge pull request #894 from dod-ccpo/style-to-form

Style to form
This commit is contained in:
leigh-mil 2019-06-12 15:59:22 -04:00 committed by GitHub
commit 0bafa51bb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 404 additions and 320 deletions

View File

@ -19,8 +19,8 @@ from atst.utils.localization import translate
class CLINForm(FlaskForm):
jedi_clin_type = SelectField("Jedi CLIN type", choices=JEDI_CLIN_TYPES)
number = StringField(validators=[Required()])
jedi_clin_type = SelectField("CLIN type", choices=JEDI_CLIN_TYPES)
number = StringField(label="CLIN", validators=[Required()])
start_date = DateField(
translate("forms.task_order.start_date_label"),
format="%m/%d/%Y",
@ -31,7 +31,7 @@ class CLINForm(FlaskForm):
format="%m/%d/%Y",
validators=[Required()],
)
obligated_amount = DecimalField()
obligated_amount = DecimalField(label="Funds obligated for cloud")
loas = FieldList(StringField())
@ -42,9 +42,7 @@ class UnclassifiedCLINForm(CLINForm):
class TaskOrderForm(BaseForm):
number = StringField(
translate("forms.task_order.number_label"),
description=translate("forms.task_order.number_description"),
validators=[Required()],
label=translate("forms.task_order.number_description"), validators=[Required()]
)
pdf = FileField(
None,

View File

@ -8,11 +8,12 @@ from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
def render_task_orders_edit(portfolio_id, task_order_id=None, form=None):
def render_task_orders_edit(portfolio_id=None, task_order_id=None, form=None):
render_args = {}
if task_order_id:
task_order = TaskOrders.get(task_order_id)
portfolio_id = task_order.portfolio_id
render_args["form"] = form or TaskOrderForm(**task_order.to_dictionary())
render_args["task_order_id"] = task_order_id
else:
@ -28,18 +29,16 @@ def render_task_orders_edit(portfolio_id, task_order_id=None, form=None):
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new")
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/<task_order_id>/edit")
@task_orders_bp.route("/task_orders/<task_order_id>/edit")
@user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form")
def edit(portfolio_id, task_order_id=None):
def edit(portfolio_id=None, task_order_id=None):
return render_task_orders_edit(portfolio_id, task_order_id)
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new", methods=["POST"])
@task_orders_bp.route(
"/portfolios/<portfolio_id>/task_orders/<task_order_id>", methods=["POST"]
)
@task_orders_bp.route("/task_orders/<task_order_id>", methods=["POST"])
@user_can(Permissions.CREATE_TASK_ORDER, message="create new task order")
def update(portfolio_id, task_order_id=None):
def update(portfolio_id=None, task_order_id=None):
form_data = {**http_request.form, **http_request.files}
form = None
@ -53,18 +52,18 @@ def update(portfolio_id, task_order_id=None):
task_order = None
if task_order_id:
task_order = TaskOrders.update(task_order_id, **form.data)
portfolio_id = task_order.portfolio_id
else:
task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data)
if http_request.args.get("review"):
return redirect(
url_for("task_orders.review_task_order", task_order_id=task_order.id)
)
flash("task_order_draft")
return redirect(
url_for(
"task_orders.edit",
portfolio_id=portfolio_id,
task_order_id=task_order.id,
)
)
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
else:
flash("form_errors")
return render_task_orders_edit(portfolio_id, task_order_id, form), 400

View File

@ -2,6 +2,7 @@ import ClinFields from '../clin_fields'
import DateSelector from '../date_selector'
import FormMixin from '../../mixins/form'
import optionsinput from '../options_input'
import SemiCollapsibleText from '../semi_collapsible_text'
import textinput from '../text_input'
import uploadinput from '../upload_input'
@ -14,6 +15,7 @@ export default {
ClinFields,
DateSelector,
optionsinput,
SemiCollapsibleText,
textinput,
uploadinput,
},

View File

@ -36,4 +36,19 @@
.sticky-cta-buttons {
display: flex;
.action-group {
margin: 0;
a.action-group__action.icon-link {
width: auto;
}
input {
margin: $gap $gap * 1.5 $gap 0;
width: 19rem;
height: 3.2rem;
font-size: $small-font-size;
}
}
}

View File

@ -157,7 +157,7 @@
.usa-input__choices {
// checkbox & radio sets
legend {
padding: 0 0 $gap 0;
padding: 0 0 ($gap / 2) 0;
@include h4;

View File

@ -86,8 +86,9 @@
min-width: 14rem;
}
.task-order-summary {
.task-order {
margin-top: $gap * 4;
width: 900px;
hr {
border: 0;
@ -119,11 +120,43 @@
flex-grow: unset;
margin-left: $gap * 6;
margin-top: $gap * 3;
width: 33.77%;
width: 35%;
height: fit-content;
background-color: $color-gray-lightest;
}
&.task_order__form {
.totals-box {
margin-top: 0;
}
.icon {
left: 100%;
}
.usa-input--validation--dollars input {
max-width: unset;
}
.usa-form-group-year {
margin-right: 0.4rem;
}
.usa-alert {
margin: 1.5em 0;
}
}
.row.row__form-fields {
.col {
margin-left: $gap;
}
.col:first-child {
margin-left: 0;
}
}
.panel {
@include shadow-panel;
}

View File

@ -14,7 +14,8 @@
initial_value='',
classes='',
noMaxWidth=False,
optional=False) -%}
optional=False,
showLabel=True) -%}
<textinput
v-cloak
@ -32,6 +33,7 @@
class='{% if disabled %}input--disabled{% endif %} {{ classes }}'
v-bind:class="['usa-input usa-input--validation--' + validation, { 'usa-input--error': showError, 'usa-input--success': showValid, 'usa-input--validation--paragraph': paragraph, 'no-max-width': noMaxWidth }]">
{% if showLabel %}
<label for={{field.name}}>
<div class="usa-input__title">
{{ label }}
@ -50,6 +52,7 @@
{% endif %}
</label>
{% endif %}
{% if paragraph %}

View File

@ -1,14 +1,19 @@
{% macro TotalsBox(task_order) -%}
{% macro TotalsBox(task_order=None, obligated_funds=0, contract_amount=0) -%}
<div class="col totals-box">
{% if task_order %}
{% set obligated_funds = task_order.total_obligated_funds %}
{% set contract_amount = task_order.total_contract_amount %}
{% endif %}
<div class="h4">Total obligated funds</div>
<div class="h3">{{ task_order.total_obligated_funds | dollars }}</div>
<div class="h3">{{ obligated_funds | dollars }}</div>
<div>This is the funding allocated to cloud services. It may be 100% or a portion of the total task order budget.</div>
<hr>
<div class="h4">Total contract amount</div>
<div class="h3">{{ task_order.total_contract_amount | dollars }}</div>
<div class="h3">{{ contract_amount | dollars }}</div>
<div>This is the value of all funds obligated for this contract, including -- but not limited to -- funds obligated for the cloud.</div>
</div>

View File

@ -22,11 +22,11 @@
{% endcall %}
{% call StickyCTA(text="Review Funding") %}
<a href="{{ url_for("task_orders.edit", portfolio_id=portfolio.id, task_order_id=task_order.id) }}" class="usa-button usa-button-secondary" type="submit">Edit</a>
<a href="{{ url_for("task_orders.edit", task_order_id=task_order.id) }}" class="usa-button usa-button-secondary" type="submit">Edit</a>
<a v-on:click="openModal('submit-to-1')" class="usa-button usa-button-primary" type="submit">Submit task order</a>
{% endcall %}
<div class="task-order-summary">
<div class="task-order">
{{ SemiCollapsibleText() }}
@ -101,7 +101,6 @@
</a>
</div>
</div>
{{ TotalsBox(task_order=task_order) }}
</div>

View File

@ -2,20 +2,18 @@
{% from 'components/date_picker.html' import DatePicker %}
{% from 'components/icon.html' import Icon %}
{% from 'components/save_button.html' import SaveButton %}
{% from 'components/options_input.html' import OptionsInput %}
{% from 'components/save_button.html' import SaveButton %}
{% from "components/semi_collapsible_text.html" import SemiCollapsibleText %}
{% from "components/sticky_cta.html" import StickyCTA %}
{% from 'components/text_input.html' import TextInput %}
{% from "components/totals_box.html" import TotalsBox %}
{% from 'components/upload_input.html' import UploadInput %}
{% macro LOAInput() %}
<div v-for="loa in loas">
<textinput :name="'clins-' + clinIndex + '-loas-' + loaIndex(loa)" inline-template>
<div>
<label :for="name">
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
<div class="usa-input__title">Line of Accounting</div>
</label>
<masked-input
v-on:input='onInput'
@ -43,23 +41,38 @@
</textinput>
</div>
<button v-on:click="addLoa" type="button">
Add another line of accounting
<button
class="icon-link"
v-on:click="addLoa"
type="button">
{{ Icon('plus') }}
<span>Add another line of accounting</span>
</button>
{% endmacro %}
{% macro CLINFields(fields, index) %}
{% if index != 0 %}
<hr>
{% endif %}
<clin-fields
v-bind:initial-clin-index='{{ index }}'
v-bind:initial-loa-count="{{ fields.loas.data | length }}"
inline-template>
<div>
<hr>
<div class="row row__form-fields">
<div class="col">
{{ OptionsInput(fields.jedi_clin_type) }}
</div>
<div class="col">
{{ TextInput(fields.number) }}
</div>
</div>
<div>Line of accounting (43 characters)</div>
{% for loa in fields.loas %}
{{ TextInput(loa) }}
{{ TextInput(loa, showLabel=False) }}
{% endfor %}
{{ LOAInput() }}
@ -71,41 +84,65 @@
{% endmacro %}
{% block portfolio_content %}
<div class="col task-order-form">
{% include "fragments/flash.html" %}
<to-form inline-template v-bind:initial-clin-count="{{ form.clins.data | length }}">
{% if task_order_id %}
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order_id) %}
{% set action = url_for("task_orders.update", task_order_id=task_order_id) %}
{% set review_action = url_for("task_orders.update", task_order_id=task_order_id, review=True) %}
{% else %}
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id) %}
{% set review_action = url_for("task_orders.update", portfolio_id=portfolio.id, review=True) %}
{% endif %}
<form id="new-task-order" action='{{ action }}' method="POST" autocomplete="off" enctype="multipart/form-data">
{{ form.csrf_token }}
<!-- TODO: implement save bar with component -->
<span class="h3">Add Funding</span>
{% call StickyCTA(text="Add Funding") %}
<span class="action-group">
<input type="submit" formaction="{{ review_action }}" tabindex="0" value="Review task order" form="new-task-order" class="usa-button usa-button-primary">
<input type="submit" tabindex="0" value="Save" form="new-task-order" class="usa-button usa-button-secondary">
<a
href="{{ cancel_url }}"
class="action-group__action icon-link">
<span class="icon icon--x"></span>
{{ "common.cancel" | translate }}
</a>
<input type="submit" tabindex="0" value="Save" form="new-task-order" class="usa-button usa-button-primary">
<p>
</span>
{% endcall %}
<to-form inline-template v-bind:initial-clin-count="{{ form.clins.data | length }}">
<div class="task-order task_order__form">
<p class="section-description">
{{ "task_orders.new.form_help_text" | translate }}
</p>
<hr>
{% include "fragments/flash.html" %}
<div class="row">
<div class="col">
<div class="h1">Add your task order</div>
{{ TextInput(form.number, validation='taskOrderNumber') }}
<hr>
<div class="h3">Add the summary of cloud funding</div>
<div>
Data must match with what is in your uploaded document.
</div>
{% for clin in form.clins %}
{{ CLINFields(clin, index=loop.index - 1) }}
{% endfor %}
<div v-for="clin in clins">
<hr>
<hr v-if="clinIndex !== 0">
<clin-fields v-bind:initial-clin-index='clinIndex' inline-template>
<div>
<div class="row row__form-fields">
<div class="col">
<div class="usa-input">
<fieldset data-ally-disabled="true" class="usa-input__choices">
<legend>
<div class="usa-input__title">
CLIN type
</div>
</legend>
<select :id="'clins-' + clinIndex + '-jedi_clin_type'" :name="'clins-' + clinIndex + '-jedi_clin_type'">
@ -116,13 +153,14 @@
</select>
</fieldset>
</div>
</div>
<div class="col">
<textinput :name="'clins-' + clinIndex + '-number'" inline-template>
<div>
<div class="usa-input">
<label :for="name">
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
<div class="usa-input__title"> Number </div>
<div class="usa-input__title">CLIN</div>
</label>
<masked-input
@ -149,7 +187,10 @@
</template>
</div>
</textinput>
</div>
</div>
<div>Line of accounting (43 characters)</div>
{{ LOAInput() }}
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" inline-template>
@ -267,7 +308,7 @@
<label :for="name">
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
<div class="usa-input__title">Obligated Amount</div>
<div class="usa-input__title">Funds obligated for cloud</div>
</label>
<masked-input
@ -295,16 +336,29 @@
</template>
</div>
</textinput>
</div>
</clin-fields>
</div>
<button v-on:click="addClin" type="button">
Add CLIN
<button
class="icon-link"
v-on:click="addClin"
type="button">
{{ Icon('plus') }}
<span>Add another CLIN or Sub-CLIN</span>
</button>
{{ UploadInput(form.pdf) }}
</form>
</to-form>
<hr>
<div class="h3">Upload your supporting documentation</div>
<div>
Upload a single PDF containing all relevant information. {{ Icon('help')}}
</div>
{{ UploadInput(form.pdf) }}
</div>
{{ TotalsBox(task_order=task_order) }}
</div>
</div>
</to-form>
</form>
{% endblock %}

View File

@ -68,13 +68,7 @@ def test_task_orders_update(client, user_session, portfolio):
def test_task_orders_edit_existing_to(client, user_session, task_order):
user_session(task_order.creator)
response = client.get(
url_for(
"task_orders.edit",
portfolio_id=task_order.portfolio_id,
task_order_id=task_order.id,
)
)
response = client.get(url_for("task_orders.edit", task_order_id=task_order.id))
assert response.status_code == 200
@ -90,12 +84,7 @@ def test_task_orders_update_existing_to(client, user_session, task_order):
"clins-0-loas-0": "123123123123",
}
response = client.post(
url_for(
"task_orders.update",
portfolio_id=task_order.portfolio_id,
task_order_id=task_order.id,
),
data=form_data,
url_for("task_orders.update", task_order_id=task_order.id), data=form_data
)
assert response.status_code == 302
assert task_order.number == "0123456789"
@ -115,12 +104,9 @@ def test_task_orders_update_invalid_data(client, user_session, portfolio):
def test_task_orders_update(client, user_session, portfolio, pdf_upload):
user_session(portfolio.owner)
data = {"number": "0123456789", "pdf": pdf_upload}
task_order = TaskOrderFactory.create(number="0987654321")
task_order = TaskOrderFactory.create(number="0987654321", portfolio=portfolio)
response = client.post(
url_for(
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
),
data=data,
url_for("task_orders.update", task_order_id=task_order.id), data=data
)
assert response.status_code == 302
assert task_order.number == data["number"]
@ -130,13 +116,10 @@ def test_task_orders_update_pdf(
client, user_session, portfolio, pdf_upload, pdf_upload2
):
user_session(portfolio.owner)
task_order = TaskOrderFactory.create(pdf=pdf_upload)
task_order = TaskOrderFactory.create(pdf=pdf_upload, portfolio=portfolio)
data = {"number": "0123456789", "pdf": pdf_upload2}
response = client.post(
url_for(
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
),
data=data,
url_for("task_orders.update", task_order_id=task_order.id), data=data
)
assert response.status_code == 302
assert task_order.pdf.filename == pdf_upload2.filename
@ -144,13 +127,10 @@ def test_task_orders_update_pdf(
def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_upload):
user_session(portfolio.owner)
task_order = TaskOrderFactory.create(pdf=pdf_upload)
task_order = TaskOrderFactory.create(pdf=pdf_upload, portfolio=portfolio)
data = {"number": "0123456789", "pdf": ""}
response = client.post(
url_for(
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
),
data=data,
url_for("task_orders.update", task_order_id=task_order.id), data=data
)
assert response.status_code == 302
assert task_order.pdf is None
@ -167,9 +147,7 @@ def test_task_order_form_shows_errors(client, user_session, task_order):
funding_data.update({"clin_01": "one milllllion dollars"})
response = client.post(
url_for(
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
),
url_for("task_orders.update", task_order_id=task_order.id),
data=funding_data,
follow_redirects=False,
)

View File

@ -515,9 +515,7 @@ def test_task_orders_update_access(post_url_assert_status):
task_order = TaskOrderFactory.create(portfolio=portfolio)
url = url_for(
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
)
url = url_for("task_orders.update", task_order_id=task_order.id)
post_url_assert_status(owner, url, 302)
post_url_assert_status(ccpo, url, 302)
post_url_assert_status(rando, url, 404)

View File

@ -302,7 +302,7 @@ forms:
military: Military
other: Other <em class='description'>(E.g. University or other partner)</em>
dev_team_other_label: Development Team Other
end_date_label: End Date
end_date_label: End of period of performance (PoP)
file_format_not_allowed: Only PDF or PNG files can be uploaded.
first_step_title: Overview
ko_invite_label: Invite contracting officer to Task Order Builder
@ -330,7 +330,7 @@ forms:
skip_invite_description: |
<i>An invitation won't actually be sent until you click Done on the Review page. You can skip this for now and invite them later.</i>
so_invite_label: Invite Security Officer to Task Order Builder
start_date_label: Start Date
start_date_label: Start of period of performance (PoP)
team_experience:
built_1: Built, migrated, or consulted on 1-2 applications
built_3: Built, migrated, or consulted on 3-5 applications