diff --git a/atst/forms/ccpo_review.py b/atst/forms/ccpo_review.py index 8b6ea64d..2636d529 100644 --- a/atst/forms/ccpo_review.py +++ b/atst/forms/ccpo_review.py @@ -7,7 +7,10 @@ from .validators import Alphabet, PhoneNumber class CCPOReviewForm(ValidatedForm): - comment = TextAreaField("Comments (optional)") + comment = TextAreaField( + "Instructions or comments", + description="Provide instructions or notes for additional information that is necessary to approve the request here. The requestor may then re-submit the updated request or initiate contact outside of AT-AT if further discussion is required. This message will be shared with the person making the JEDI request..", + ) fname_mao = StringField( "First Name (optional)", validators=[Optional(), Alphabet()] ) diff --git a/atst/forms/internal_comment.py b/atst/forms/internal_comment.py index a64833c8..71b88f7e 100644 --- a/atst/forms/internal_comment.py +++ b/atst/forms/internal_comment.py @@ -5,4 +5,8 @@ from .forms import ValidatedForm class InternalCommentForm(ValidatedForm): - text = TextAreaField(validators=[Optional()]) + text = TextAreaField( + "CCPO Internal Notes", + description="You may add additional comments and notes for internal CCPO reference and follow-up here.", + validators=[Optional()], + ) diff --git a/atst/models/request_review.py b/atst/models/request_review.py index c46f1d11..20fa1ed4 100644 --- a/atst/models/request_review.py +++ b/atst/models/request_review.py @@ -8,7 +8,7 @@ class RequestReview(Base): __tablename__ = "request_reviews" id = Column(BigInteger, primary_key=True) - status = relationship("RequestStatusEvent", back_populates="review") + status = relationship("RequestStatusEvent", uselist=False, back_populates="review") user_id = Column(ForeignKey("users.id"), nullable=False) reviewer = relationship("User") diff --git a/atst/models/request_status_event.py b/atst/models/request_status_event.py index 283633b1..4680d162 100644 --- a/atst/models/request_status_event.py +++ b/atst/models/request_status_event.py @@ -48,6 +48,8 @@ class RequestStatusEvent(Base): def log_name(self): if self.new_status == RequestStatus.CHANGES_REQUESTED: return "Denied" + if self.new_status == RequestStatus.CHANGES_REQUESTED_TO_FINVER: + return "Denied" elif self.new_status == RequestStatus.PENDING_FINANCIAL_VERIFICATION: return "Accepted" else: diff --git a/atst/routes/requests/approval.py b/atst/routes/requests/approval.py index 1843e127..a0f0f5e8 100644 --- a/atst/routes/requests/approval.py +++ b/atst/routes/requests/approval.py @@ -35,7 +35,7 @@ def render_approval(request, form=None): return render_template( "requests/approval.html", data=data, - status_events=reversed(request.status_events), + reviews=list(reversed(request.reviews)), request=request, current_status=request.status.value, pending_review=pending_review, @@ -58,7 +58,7 @@ def submit_approval(request_id): form = CCPOReviewForm(http_request.form) if form.validate(): - if http_request.form.get("approved"): + if http_request.form.get("review") == "approving": Requests.advance(g.current_user, request, form.data) else: Requests.request_changes(g.current_user, request, form.data) @@ -93,4 +93,6 @@ def create_internal_comment(request_id): request = Requests.get(g.current_user, request_id) Requests.update_internal_comments(g.current_user, request, form.data["text"]) - return redirect(url_for("requests.approval", request_id=request_id)) + return redirect( + url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes") + ) diff --git a/js/components/forms/ccpo_approval.js b/js/components/forms/ccpo_approval.js new file mode 100644 index 00000000..7fe245ed --- /dev/null +++ b/js/components/forms/ccpo_approval.js @@ -0,0 +1,28 @@ +import textinput from '../text_input' + +export default { + name: 'ccpo-approval', + + components: { + textinput + }, + + data: function () { + return { + approving: false, + denying: false + } + }, + + methods: { + setReview: function (e) { + if (e.target.value === 'approving') { + this.approving = true + this.denying = false + } else { + this.approving = false + this.denying = true + } + }, + } +} diff --git a/js/components/local_datetime.js b/js/components/local_datetime.js new file mode 100644 index 00000000..94a338dc --- /dev/null +++ b/js/components/local_datetime.js @@ -0,0 +1,21 @@ +import { format } from 'date-fns' + +export default { + name: 'local-datetime', + + props: { + timestamp: String, + format: { + type: String, + default: 'MMM D YYYY H:mm' + } + }, + + computed: { + displayTime: function () { + return format(this.timestamp, this.format) + } + }, + + template: '' +} diff --git a/js/components/text_input.js b/js/components/text_input.js index 8e4f8048..7d0bb78d 100644 --- a/js/components/text_input.js +++ b/js/components/text_input.js @@ -19,7 +19,8 @@ export default { default: () => '' }, initialErrors: Array, - paragraph: String + paragraph: String, + noMaxWidth: String }, data: function () { diff --git a/js/index.js b/js/index.js index f5e9613d..0cb08b60 100644 --- a/js/index.js +++ b/js/index.js @@ -16,6 +16,8 @@ import NewProject from './components/forms/new_project' import Modal from './mixins/modal' import selector from './components/selector' import BudgetChart from './components/charts/budget_chart' +import CcpoApproval from './components/forms/ccpo_approval' +import LocalDatetime from './components/local_datetime' Vue.use(VTooltip) @@ -33,7 +35,9 @@ const app = new Vue({ financial, NewProject, selector, - BudgetChart + BudgetChart, + CcpoApproval, + LocalDatetime }, mounted: function() { const modalOpen = document.querySelector("#modalOpen") diff --git a/styles/components/_alerts.scss b/styles/components/_alerts.scss index e2da79fd..0aa6aee4 100644 --- a/styles/components/_alerts.scss +++ b/styles/components/_alerts.scss @@ -9,6 +9,10 @@ border-left-width: $gap / 2; border-left-style: solid; @include panel-margin; + + @include media($medium-screen) { + padding: $gap * 4; + } } @mixin alert-level($level) { @@ -53,6 +57,10 @@ .alert__title { @include h3; margin-top: 0; + + &:last-child { + margin-bottom: 0; + } } .alert__content { diff --git a/styles/elements/_action_group.scss b/styles/elements/_action_group.scss index fc38eb04..2e1fb191 100644 --- a/styles/elements/_action_group.scss +++ b/styles/elements/_action_group.scss @@ -5,6 +5,10 @@ margin-top: $gap * 4; margin-bottom: $gap * 4; + &--tight { + margin-top: $gap * 2; + } + .usa-button, a { margin: 0 0 0 ($gap * 2); diff --git a/styles/elements/_inputs.scss b/styles/elements/_inputs.scss index 84167345..babd6ce5 100644 --- a/styles/elements/_inputs.scss +++ b/styles/elements/_inputs.scss @@ -246,6 +246,19 @@ } } + &.no-max-width { + padding-right: $gap * 3; + + input, textarea, select, label { + max-width: none; + } + + .icon-validation { + left: auto; + right: - $gap * 4; + } + } + &.usa-input--error { @include input-state('error'); } diff --git a/styles/elements/_labels.scss b/styles/elements/_labels.scss index 104894e4..1e994013 100644 --- a/styles/elements/_labels.scss +++ b/styles/elements/_labels.scss @@ -15,6 +15,7 @@ margin: 0 $gap; padding: 0 $gap; border-radius: $gap / 2; + white-space: nowrap; &.label--info { background-color: $color-primary; diff --git a/styles/elements/_panels.scss b/styles/elements/_panels.scss index fd10671d..7d1b31d4 100644 --- a/styles/elements/_panels.scss +++ b/styles/elements/_panels.scss @@ -63,6 +63,7 @@ .panel__heading { padding: $gap * 2; + @include media($medium-screen) { padding: $gap * 4; } @@ -71,6 +72,10 @@ padding: $gap*2; } + &--divider { + border-bottom: 1px solid $color-gray-light; + } + h1, h2, h3, h4, h5, h6 { margin: 0; display: inline-block; @@ -90,15 +95,16 @@ justify-content: space-between; } } + + hr { + border: 0; + border-bottom: 1px dashed $color-gray-light; + margin: ($gap * 4) ($site-margins*-4); + } } .panel__actions { @include panel-actions; } -hr { - border: 0; - border-bottom: 1px dashed $color-gray-light; - margin: 0px $site-margins*-4; -} diff --git a/styles/sections/_request_approval.scss b/styles/sections/_request_approval.scss index 5769d5d9..f4b36140 100644 --- a/styles/sections/_request_approval.scss +++ b/styles/sections/_request_approval.scss @@ -32,6 +32,12 @@ } } + .request-approval__review { + .action-group { + margin-bottom: $gap * 6; + } + } + .approval-log { ol { list-style: none; @@ -43,7 +49,7 @@ border-top: 1px dashed $color-gray-light; &:first-child { - border-top-style: solid; + border-top: none; } @include media($medium-screen) { @@ -94,4 +100,10 @@ } } } + + .internal-notes { + textarea { + resize: vertical; + } + } } diff --git a/templates/components/text_input.html b/templates/components/text_input.html index ecd29f5c..f1cfb567 100644 --- a/templates/components/text_input.html +++ b/templates/components/text_input.html @@ -1,24 +1,35 @@ {% from "components/icon.html" import Icon %} {% from "components/tooltip.html" import Tooltip %} -{% macro TextInput(field, tooltip='', placeholder='', validation='anything', paragraph=False, initial_value='') -%} +{% macro TextInput( + field, + label=field.label | striptags, + description=field.description, + tooltip='', + placeholder='', + validation='anything', + paragraph=False, + initial_value='', + noMaxWidth=False) -%} +
+ 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 }]">