Merge pull request #1095 from dod-ccpo/clin-errors

PoP Date Range
This commit is contained in:
leigh-mil 2019-09-30 11:12:54 -04:00 committed by GitHub
commit b7677018c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1015 additions and 483 deletions

View File

@ -2,6 +2,7 @@ import os
import re
import pathlib
from configparser import ConfigParser
from datetime import datetime
from flask import Flask, request, g, session
from flask_session import Session
import redis
@ -175,6 +176,12 @@ def map_config(config):
# with a Beat job once a day)
"CELERY_RESULT_EXPIRES": 0,
"CELERY_RESULT_EXTENDED": True,
"CONTRACT_START_DATE": datetime.strptime(
config.get("default", "CONTRACT_START_DATE"), "%Y-%m-%d"
).date(),
"CONTRACT_END_DATE": datetime.strptime(
config.get("default", "CONTRACT_END_DATE"), "%Y-%m-%d"
).date(),
}

View File

@ -9,7 +9,6 @@ from wtforms.fields import (
from wtforms.fields.html5 import DateField
from wtforms.validators import Required, Optional, Length, NumberRange, ValidationError
from flask_wtf import FlaskForm
from datetime import datetime
from numbers import Number
from .data import JEDI_CLIN_TYPES
@ -85,12 +84,8 @@ class CLINForm(FlaskForm):
def validate(self, *args, **kwargs):
valid = super().validate(*args, **kwargs)
contract_start = datetime.strptime(
app.config.get("CONTRACT_START_DATE"), "%Y-%m-%d"
).date()
contract_end = datetime.strptime(
app.config.get("CONTRACT_END_DATE"), "%Y-%m-%d"
).date()
contract_start = app.config.get("CONTRACT_START_DATE")
contract_end = app.config.get("CONTRACT_END_DATE")
if (
self.start_date.data

View File

@ -163,6 +163,20 @@ describe('DateSelector', () => {
component.year = new Date().getFullYear()
expect(component.isYearValid).toEqual(true)
})
it('returns true when year is between min and max years', () => {
component.year = new Date('2019-01-01').getFullYear()
component.mindate = new Date('2018-01-01')
component.maxdate = new Date('2019-12-31')
expect(component.isYearValid).toEqual(true)
})
it('returns false when year is outside of min and max years', () => {
component.year = new Date('2020-01-01').getFullYear()
component.mindate = new Date('2018-01-01')
component.maxdate = new Date('2019-01-01')
expect(component.isYearValid).toEqual(false)
})
})
describe('formattedDate', () => {
@ -184,4 +198,56 @@ describe('DateSelector', () => {
expect(component.formattedDate).toEqual('01/22/1988')
})
})
describe('isDateComplete', () => {
it('returns true if all fields are completed', () => {
component.day = 22
component.month = 1
component.year = 1988
expect(component.isDateComplete).toEqual(true)
})
it('returns false if all fields are not completed', () => {
component.day = 22
component.month = 1
component.year = 19
expect(component.isDateComplete).toEqual(false)
})
})
describe('minError', () => {
it('returns true if the date is before mindate', () => {
component.mindate = new Date('2020-01-01')
component.day = 1
component.month = 1
component.year = 2000
expect(component.minError).toEqual(true)
})
it('returns fals if the date is after mindate', () => {
component.mindate = new Date('2020-01-01')
component.day = 1
component.month = 1
component.year = 2025
expect(component.minError).toEqual(false)
})
})
describe('maxError', () => {
it('returns true if the date is after maxdate', () => {
component.maxdate = new Date('2020-01-01')
component.day = 1
component.month = 1
component.year = 2025
expect(component.maxError).toEqual(true)
})
it('returns false if the date is before maxdate', () => {
component.maxdate = new Date('2020-01-01')
component.day = 1
component.month = 1
component.year = 2005
expect(component.maxError).toEqual(false)
})
})
})

View File

@ -0,0 +1,105 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import PopDateRange from '../pop_date_range'
import { makeTestWrapper } from '../../test_utils/component_test_helpers'
const PopDateRangeWrapper = makeTestWrapper({
components: { PopDateRange },
templatePath: 'pop_date_range.html',
data: function() {
return {
initialMinStartDate: '2019-09-14',
initialMaxEndDate: '2022-09-14',
}
},
})
describe('PopDateRange Test', () => {
const component = new Vue(PopDateRange)
it('should calculate the max start date', () => {
component.maxStartDate = new Date('2020-01-01')
const date = new Date('2019-12-31')
expect(component.calcMaxStartDate(date)).toEqual(date)
})
it('should calculate the min end date', () => {
component.minEndDate = new Date('2020-01-01')
const date = new Date('2020-02-02')
expect(component.calcMinEndDate(date)).toEqual(date)
})
it('should add an error to the start date if it is out of range', () => {
const wrapper = mount(PopDateRangeWrapper, {
propsData: {
initialData: {},
},
})
const error = ['usa-input--error']
var startDateField = wrapper.find('fieldset[name="start_date"]')
var endDateField = wrapper.find('fieldset[name="end_date"]')
// set valid date range
startDateField.find('input[name="date-month"]').setValue('01')
startDateField.find('input[name="date-day"]').setValue('01')
startDateField.find('input[name="date-year"]').setValue('2020')
endDateField.find('input[name="date-month"]').setValue('01')
endDateField.find('input[name="date-day"]').setValue('01')
endDateField.find('input[name="date-year"]').setValue('2021')
// manually trigger the change event in the hidden fields
startDateField.find('input[name="start_date"]').trigger('change')
endDateField.find('input[name="end_date"]').trigger('change')
// check that both dates do not have error class
expect(startDateField.classes()).toEqual(expect.not.arrayContaining(error))
expect(endDateField.classes()).toEqual(expect.not.arrayContaining(error))
// update start date to be after end date and trigger change event
startDateField.find('input[name="date-year"]').setValue('2022')
startDateField.find('input[name="start_date"]').trigger('change')
expect(startDateField.classes()).toEqual(expect.arrayContaining(error))
expect(endDateField.classes()).toEqual(expect.not.arrayContaining(error))
})
it('should add an error to the end date if it is out of range', () => {
const wrapper = mount(PopDateRangeWrapper, {
propsData: {
initialData: {},
},
})
const error = ['usa-input--error']
var startDateField = wrapper.find('fieldset[name="start_date"]')
var endDateField = wrapper.find('fieldset[name="end_date"]')
// set valid date range
startDateField.find('input[name="date-month"]').setValue('01')
startDateField.find('input[name="date-day"]').setValue('01')
startDateField.find('input[name="date-year"]').setValue('2020')
endDateField.find('input[name="date-month"]').setValue('01')
endDateField.find('input[name="date-day"]').setValue('01')
endDateField.find('input[name="date-year"]').setValue('2021')
// manually trigger the change event in the hidden fields
startDateField.find('input[name="start_date"]').trigger('change')
endDateField.find('input[name="end_date"]').trigger('change')
// check that both dates do not have error class
expect(startDateField.classes()).toEqual(expect.not.arrayContaining(error))
expect(endDateField.classes()).toEqual(expect.not.arrayContaining(error))
// update end date to be before end date and trigger change event
endDateField.find('input[name="date-year"]').setValue('2019')
endDateField.find('input[name="end_date"]').trigger('change')
expect(startDateField.classes()).toEqual(expect.not.arrayContaining(error))
expect(endDateField.classes()).toEqual(expect.arrayContaining(error))
})
})

View File

@ -1,28 +1,22 @@
import * as R from 'ramda'
import { format } from 'date-fns'
import DateSelector from './date_selector'
import { emitEvent } from '../lib/emitters'
import Modal from '../mixins/modal'
import optionsinput from './options_input'
import textinput from './text_input'
import clindollaramount from './clin_dollar_amount'
import PopDateRange from './pop_date_range'
const TOTAL_AMOUNT = 'total_amount'
const OBLIGATED_AMOUNT = 'obligated_amount'
const START_DATE = 'start_date'
const END_DATE = 'end_date'
const POP = 'period_of_performance'
const NUMBER = 'number'
export default {
name: 'clin-fields',
components: {
DateSelector,
optionsinput,
textinput,
clindollaramount,
PopDateRange,
},
mixins: [Modal],
@ -37,79 +31,26 @@ export default {
type: Number,
default: 0,
},
initialStartDate: {
type: String,
default: null,
},
initialEndDate: {
type: String,
default: null,
},
initialClinNumber: {
type: String,
default: null,
},
contractStart: {
type: String,
required: true,
},
contractEnd: {
type: String,
required: true,
},
},
data: function() {
const start = !!this.initialStartDate
? new Date(this.initialStartDate)
: undefined
const end = !!this.initialEndDate
? new Date(this.initialEndDate)
: undefined
const fundingValidation =
this.initialObligated && this.initialTotal
? this.initialObligated <= this.initialTotal
: true
const popValidation = !this.initialStartDate ? false : start < end
const clinNumber = !!this.initialClinNumber
? this.initialClinNumber
: undefined
const contractStartDate = new Date(this.contractStart)
const contractEndDate = new Date(this.contractEnd)
return {
clinIndex: this.initialClinIndex,
clinNumber: clinNumber,
startDate: start,
endDate: end,
popValid: popValidation,
startDateValid: false,
endDateValid: false,
contractStartDate: contractStartDate,
contractEndDate: contractEndDate,
clinNumber: clinNumber,
showClin: true,
popErrors: [],
validations: [
{
func: this.popDateOrder,
message: 'PoP start date must be before end date.',
},
{
func: this.popStartsAfterContract,
message: `PoP start date must be on or after ${format(
contractStartDate,
'MMM D, YYYY'
)}.`,
},
{
func: this.popEndsBeforeContract,
message: `PoP end date must be before or on ${format(
contractEndDate,
'MMM D, YYYY'
)}.`,
},
],
totalAmount: this.initialTotal || 0,
obligatedAmount: this.initialObligated || 0,
fundingValid: fundingValidation,
@ -127,11 +68,6 @@ export default {
obligatedAmount: this.initialObligated,
totalAmount: this.initialTotal,
})
emitEvent('field-mount', this, {
optional: false,
name: 'clins-' + this.clinIndex + '-' + POP,
valid: this.checkPopValid(),
})
},
methods: {
@ -143,50 +79,6 @@ export default {
})
},
checkPopValid: function() {
return (
this.popDateOrder() &&
this.popStartsAfterContract() &&
this.popEndsBeforeContract()
)
},
validatePop: function() {
this.popValid = this.checkPopValid()
emitEvent('field-change', this, {
name: 'clins-' + this.clinIndex + '-' + POP,
valid: this.popValid,
})
this.popErrors = R.pipe(
R.map(validation =>
!validation.func() ? validation.message : undefined
),
R.filter(Boolean)
)(this.validations)
},
popStartsAfterContract: function() {
if (this.startDateValid) {
return this.startDate >= this.contractStartDate
}
return true
},
popEndsBeforeContract: function() {
if (this.endDateValid) {
return this.endDate <= this.contractEndDate
}
return true
},
popDateOrder: function() {
if (!!this.startDate && !!this.endDate) {
return this.startDate < this.endDate
}
return true
},
checkFundingValid: function() {
return this.obligatedAmount <= this.totalAmount
},
@ -205,14 +97,6 @@ export default {
} else if (event.name.includes(OBLIGATED_AMOUNT)) {
this.obligatedAmount = parseFloat(event.value)
this.validateFunding()
} else if (event.name.includes(START_DATE)) {
if (!!event.value) this.startDate = new Date(event.value)
if (!!event.valid) this.startDateValid = event.valid
this.validatePop()
} else if (event.name.includes(END_DATE)) {
if (!!event.value) this.endDate = new Date(event.value)
if (!!event.valid) this.endDateValid = event.valid
this.validatePop()
} else if (event.name.includes(NUMBER)) {
this.clinNumber = event.value
}

View File

@ -2,7 +2,7 @@ import Vue from 'vue'
import { getDaysInMonth } from 'date-fns'
import { emitEvent } from '../lib/emitters'
var paddedNumber = function(number) {
let paddedNumber = function(number) {
if ((number + '').length === 1) {
return `0${number}`
} else {
@ -20,10 +20,6 @@ export default {
mindate: { type: String },
maxdate: { type: String },
nameTag: { type: String },
watch: {
type: Boolean,
default: false,
},
optional: {
type: Boolean,
default: true,
@ -36,7 +32,6 @@ export default {
month: this.initialmonth,
year: this.initialyear,
name: this.nameTag,
showValidation: false,
}
},
@ -87,23 +82,29 @@ export default {
},
isMonthValid: function() {
var _month = parseInt(this.month)
var valid = _month >= 0 && _month <= 12
this._emitChange('month', this.month, valid)
let _month = parseInt(this.month)
let valid = _month >= 0 && _month <= 12
return valid
},
isDayValid: function() {
var _day = parseInt(this.day)
var valid = _day >= 0 && _day <= this.daysMaxCalculation
this._emitChange('day', this.day, valid)
let _day = parseInt(this.day)
let valid = _day >= 0 && _day <= this.daysMaxCalculation
return valid
},
isYearValid: function() {
// Emit a change event
var valid = parseInt(this.year) >= 1
this._emitChange('year', this.year, valid)
let valid
let minYear = this.mindate ? this.minDateParsed.getFullYear() : null
let maxYear = this.maxdate ? this.maxDateParsed.getFullYear() : null
if (minYear && maxYear) {
valid = this.year >= minYear && this.year <= maxYear
} else {
valid = parseInt(this.year) >= 1
}
return valid
},
@ -135,6 +136,10 @@ export default {
)
},
isDateComplete: function() {
return !!this.day && !!this.month && !!this.year && this.year > 999
},
daysMaxCalculation: function() {
switch (parseInt(this.month)) {
case 2: // February
@ -157,22 +162,49 @@ export default {
return 31
}
},
minError: function() {
if (this.isDateComplete) {
return this.minDateParsed > this.dateParsed
}
return false
},
maxError: function() {
if (this.isDateComplete) {
return this.maxDateParsed < this.dateParsed
}
return false
},
maxDateParsed: function() {
return new Date(this.maxdate)
},
minDateParsed: function() {
return new Date(this.mindate)
},
dateParsed: function() {
return new Date(this.formattedDate)
},
},
methods: {
onInput: function(e) {
this.showValidation = true
emitEvent('field-change', this, {
value: this.formattedDate,
name: this.name,
watch: this.watch,
valid: this.isDateValid,
})
},
_emitChange: function(name, value, valid) {
emitEvent('field-change', this, { value, name, valid })
this.$emit('date-change', {
value: this.formattedDate,
name: this.name,
valid: this.isDateValid,
})
},
},

View File

@ -0,0 +1,88 @@
import { format } from 'date-fns'
import DateSelector from './date_selector'
const START_DATE = 'start_date'
const END_DATE = 'end_date'
export default {
name: 'pop-date-range',
components: {
DateSelector,
},
props: {
initialMinStartDate: String,
initialMaxEndDate: String,
initialStartDate: {
type: String,
default: null,
},
initialEndDate: {
type: String,
default: null,
},
clinIndex: Number,
},
data: function() {
let start = !!this.initialStartDate
? new Date(this.initialStartDate)
: false
let contractStart = new Date(this.initialMinStartDate)
let minEndDate = start && start > contractStart ? start : contractStart
let end = !!this.initialEndDate ? new Date(this.initialEndDate) : false
let contractEnd = new Date(this.initialMaxEndDate)
let maxStartDate = end && end < contractEnd ? end : contractEnd
// the maxStartDate and minEndDate change based on user input:
// the latest date the start can be is the PoP end date
// the earliest date the end can be is the PoP start date
// if the form is initialized with out a PoP, the maxStartDate and minEndDate
// default to the contract dates
return {
maxStartDate: maxStartDate,
minEndDate: minEndDate,
}
},
methods: {
handleDateChange: function(event) {
if (event.name.includes(START_DATE) && event.valid) {
let date = new Date(event.value)
this.minEndDate = this.calcMinEndDate(date)
} else if (event.name.includes(END_DATE) && event.valid) {
let date = new Date(event.value)
this.maxStartDate = this.calcMaxStartDate(date)
}
},
calcMaxStartDate: function(date) {
if (!!date && date < this.maxStartDate) {
return date
} else {
return this.maxStartDate
}
},
calcMinEndDate: function(date) {
if (!!date && date > this.minEndDate) {
return date
} else {
return this.minEndDate
}
},
},
computed: {
maxStartProp: function() {
return format(this.maxStartDate, 'YYYY-MM-DD')
},
minEndProp: function() {
return format(this.minEndDate, 'YYYY-MM-DD')
},
},
}

View File

@ -32,6 +32,7 @@ import NewEnvironment from './components/forms/new_environment'
import SemiCollapsibleText from './components/semi_collapsible_text'
import ToForm from './components/forms/to_form'
import ClinFields from './components/clin_fields'
import PopDateRange from './components/pop_date_range'
Vue.config.productionTip = false
@ -65,6 +66,7 @@ const app = new Vue({
SemiCollapsibleText,
ToForm,
ClinFields,
PopDateRange,
},
mounted: function() {

View File

@ -312,119 +312,140 @@
</div>
<div class="form-row">
<div class="form-col">
<date-selector
<pop-date-range
initial-min-start-date="2019-09-14"
initial-max-end-date="2022-09-14"
v-bind:clin-index="clinIndex"
name-tag='start_date'
initialmonth=""
initialday=""
initialyear=""
v-bind:watch='true'
:optional='false'
inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && showValidation }">
<legend>
<div class="usa-input__title">
Start Date
</div>
initial-start-date="None"
initial-end-date="None"
inline-template>
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div>
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="initialMinStartDate"
:maxdate="maxStartProp"
</legend>
name-tag='start_date'
initialmonth=""
initialday=""
initialyear=""
<div class="date-picker-component">
<input name="start_date" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
:optional='false'
v-on:date-change='handleDateChange'
inline-template>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
Start Date
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
id="date-year"
maxlength="4"
type="number"
v-model="year"
<div v-if='minError' class="usa-input-error-message">
PoP start date must be on or after September 14, 2019.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP start date must be before end date.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
v-on:change="onInput"
/>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div v-if="showValidation">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
max="2022"
min="2019"
v-on:change="onInput"
/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span class="icon icon--ok icon--green" aria-hidden="true"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else>
</div>
<div class="usa-form-group-date-ok" v-else>
<span class="icon icon--alert icon--red" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#fdb81e">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"/>
</svg>
</span>
</div>
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="minEndProp"
:maxdate="initialMaxEndDate"
name-tag='end_date'
initialmonth=""
initialday=""
initialyear=""
v-bind:watch='true'
:optional='false'
inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && showValidation }">
<legend>
<div class="usa-input__title">
End Date
</div>
name-tag='end_date'
initialmonth=""
initialday=""
initialyear=""
:optional='false'
v-on:date-change='handleDateChange'
inline-template>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
End Date
</div>
@ -448,86 +469,84 @@
</div>
</div>
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div v-if='minError' class="usa-input-error-message">
PoP end date must be after start date.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP end date must be on or after September 14, 2022.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
</legend>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="date-picker-component">
<input name="end_date" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
id="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div v-if="showValidation">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span class="icon icon--ok icon--green" aria-hidden="true"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else>
</div>
<div class="usa-form-group-date-ok" v-else>
<span class="icon icon--alert icon--red" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#fdb81e">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"/>
</svg>
</span>
</div>
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
</div>
</pop-date-range>
<div class="form-row">
<div class="usa-input-error-message form-has-errors">
<p v-for="error in popErrors" :key="error" v-html='error'></p>
</div>
</div>
</div>
<div v-show="$root.activeModal === removeModalId" v-cloak>

View File

@ -0,0 +1,234 @@
<pop-date-range
initial-min-start-date="2019-09-14"
initial-max-end-date="2022-09-14"
v-bind:clin-index="1"
initial-start-date="None"
initial-end-date="None"
inline-template>
<div>
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="initialMinStartDate"
:maxdate="maxStartProp"
name-tag='start_date'
initialmonth=""
initialday=""
initialyear=""
:optional='true'
v-on:date-change='handleDateChange'
inline-template>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
Start Date
</div>
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div v-if='minError' class="usa-input-error-message">
PoP start date must be on or after September 14, 2019.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP start date must be before end date.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
max="2022"
min="2019"
v-on:change="onInput"
/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span class="icon icon--ok icon--green" aria-hidden="true"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else>
<span class="icon icon--alert icon--red" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#fdb81e">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"/>
</svg>
</span>
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="minEndProp"
:maxdate="initialMaxEndDate"
name-tag='end_date'
initialmonth=""
initialday=""
initialyear=""
:optional='true'
v-on:date-change='handleDateChange'
inline-template>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
End Date
</div>
<div class='usa-alert usa-alert-info' role='alert' aria-live='polite'>
<div class='usa-alert-body'>
<p class='usa-alert-text'>
A CLIN's period of performance must end before September 14, 2022.
</p>
</div>
</div>
<p class='usa-input__help'>
For example: 07 04 1776
</p>
<div v-if='minError' class="usa-input-error-message">
PoP end date must be after start date.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP end date must be on or after September 14, 2022.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span class="icon icon--ok icon--green" aria-hidden="true"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else>
<span class="icon icon--alert icon--red" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#fdb81e">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"/>
</svg>
</span>
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
</div>
</pop-date-range>

View File

@ -199,17 +199,25 @@
Period of Performance
</div>
</div>
<pop-date-range initial-max-end-date="2022-09-14" initial-min-start-date="2019-09-14" inline-template="" v-bind:clin-index="clinIndex">
<div>
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :optional="false" :watch="true" inline-template="">
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; showValidation }">
<date-selector :maxdate="maxStartProp" :mindate="initialMinStartDate" :name-tag="'clins-' + clinIndex + '-start_date'" :optional="false" inline-template="" v-on:date-change="handleDateChange">
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; isDateComplete, 'usa-input--error': !isDateValid &amp;&amp; isDateComplete }">
<legend>
<div class="usa-input__title">
Start Date
</div>
Start Date
</div>
<p class="usa-input__help">
For example: 07 04 1776
</p>
For example: 07 04 1776
</p>
<div class="usa-input-error-message" v-if="minError">
PoP start date must be on or after September 14, 2019.
</div>
<div class="usa-input-error-message" v-if="maxError">
PoP start date must be before end date.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" type="hidden" v-bind:value="formattedDate" v-on:change="onInput"/>
@ -223,11 +231,19 @@
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input maxlength="4" name="date-year" type="number" v-model="year" v-on:change="onInput"/>
<input max="2022" maxlength="4" min="2019" name="date-year" type="number" v-model="year" v-on:change="onInput"/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span aria-hidden="true" class="icon icon--ok icon--green"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else="">
<span aria-hidden="true" class="icon icon--alert icon--red"><svg fill="#fdb81e" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"></path>
</svg>
</span>
</div>
</div>
</div>
</fieldset>
</date-selector>
@ -235,12 +251,12 @@
</div>
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :optional="false" :watch="true" inline-template="">
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; showValidation }">
<date-selector :maxdate="initialMaxEndDate" :mindate="minEndProp" :name-tag="'clins-' + clinIndex + '-end_date'" :optional="false" inline-template="" v-on:date-change="handleDateChange">
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; isDateComplete, 'usa-input--error': !isDateValid &amp;&amp; isDateComplete }">
<legend>
<div class="usa-input__title">
End Date
</div>
End Date
</div>
<div aria-live="polite" class="usa-alert usa-alert-info" role="alert">
<div class="usa-alert-body">
<p class="usa-alert-text">
@ -249,8 +265,14 @@
</div>
</div>
<p class="usa-input__help">
For example: 07 04 1776
</p>
For example: 07 04 1776
</p>
<div class="usa-input-error-message" v-if="minError">
PoP end date must be after start date.
</div>
<div class="usa-input-error-message" v-if="maxError">
PoP end date must be on or after September 14, 2022.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" type="hidden" v-bind:value="formattedDate" v-on:change="onInput"/>
@ -266,19 +288,24 @@
<label>Year</label>
<input maxlength="4" name="date-year" type="number" v-model="year" v-on:change="onInput"/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span aria-hidden="true" class="icon icon--ok icon--green"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</div>
<div class="usa-form-group-date-ok" v-else="">
<span aria-hidden="true" class="icon icon--alert icon--red"><svg fill="#fdb81e" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"></path>
</svg>
</span>
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="usa-input-error-message form-has-errors">
<p :key="error" v-for="error in popErrors" v-html="error"></p>
</div>
</div>
</pop-date-range>
</div>
<div v-cloak="" v-show="$root.activeModal === removeModalId">
<div :id='"modal--" + removeModalId' class="modal modal--dismissable">

View File

@ -1,9 +1,9 @@
{% from "components/clin_dollar_amount.html" import CLINDollarAmount %}
{% from 'components/alert.html' import Alert %}
{% from 'components/date_picker.html' import DatePicker %}
{% from 'components/icon.html' import Icon %}
{% from 'components/options_input.html' import OptionsInput %}
{% from 'components/text_input.html' import TextInput %}
{% from 'components/pop_date_range.html' import PopDateRange %}
{% macro CLINFields(contract_start, contract_end, fields=None, index=None) %}
<clin-fields
@ -128,156 +128,11 @@
{{ 'task_orders.form.pop' | translate }}
</div>
</div>
{% set contract_end_formatted = contract_end | dateFromString(formatter="%Y-%m-%d") | formattedDate(formatter="%B %d, %Y") %}
{% if fields %}
<div class="form-row">
<div class="form-col">
{{ DatePicker(fields.start_date, watch=True, optional=False) }}
</div>
</div>
<div class="form-row">
<div class="form-col">
{% call DatePicker(fields.end_date, watch=True, optional=False) %}
{{ Alert(message="task_orders.form.pop_end_alert" | translate({'end_date': contract_end_formatted})) }}
{% endcall %}
</div>
</div>
{{ PopDateRange(start_field=fields.start_date, end_field=fields.end_date, optional=False, mindate=contract_start, maxdate=contract_end) }}
{% else %}
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :watch='true' :optional='false' inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && showValidation }">
<legend>
<div class="usa-input__title">
{{ 'task_orders.form.pop_start' | translate }}
</div>
<p class='usa-input__help'>
{{ 'task_orders.form.pop_example' | translate }}
</p>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>{{ 'components.date_selector.month' | translate }}</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>{{ 'components.date_selector.day' | translate }}</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>{{ 'components.date_selector.year' | translate }}</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :watch='true' :optional='false' inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && showValidation }">
<legend>
<div class="usa-input__title">
{{ 'task_orders.form.pop_end' | translate }}
</div>
{{ Alert(message="task_orders.form.pop_end_alert" | translate({'end_date': contract_end_formatted})) }}
<p class='usa-input__help'>
{{ 'task_orders.form.pop_example' | translate }}
</p>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>{{ 'components.date_selector.month' | translate }}</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>{{ 'components.date_selector.day' | translate }}</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>{{ 'components.date_selector.year' | translate }}</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
{{ PopDateRange(optional=False, mindate=contract_start, maxdate=contract_end) }}
{% endif %}
<div class="form-row">
<div class="usa-input-error-message form-has-errors">
<p v-for="error in popErrors" :key="error" v-html='error'></p>
</div>
</div>
</div>
<div v-show="$root.activeModal === removeModalId" v-cloak>

View File

@ -7,7 +7,6 @@
description=field.description,
mindate=None,
maxdate=None,
watch=False,
optional=True) -%}
<date-selector
@ -17,11 +16,10 @@
initialmonth="{{ field.data.month }}"
initialday="{{ field.data.day }}"
initialyear="{{ field.data.year }}"
v-bind:watch='{{ watch | string | lower }}'
:optional='{{ optional | string | lower }}'
inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && showValidation }">
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
{{ label }}
@ -83,7 +81,7 @@
</div>
<div v-if="showValidation">
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>

View File

@ -0,0 +1,210 @@
{% from 'components/alert.html' import Alert %}
{% from 'components/icon.html' import Icon %}
{% macro PopDateRange(start_field=None, end_field=None, mindate=mindate, maxdate=maxdate, optional=True, index=None) %}
<pop-date-range
initial-min-start-date="{{ mindate }}"
initial-max-end-date="{{ maxdate }}"
{% if index %}
v-bind:clin-index="{{ index }}"
{% else %}
v-bind:clin-index="clinIndex"
{% endif %}
{% if start_field %}
initial-start-date="{{ start_field.data }}"
{% endif %}
{% if end_field %}
initial-end-date="{{ end_field.data }}"
{% endif %}
inline-template>
<div>
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="initialMinStartDate"
:maxdate="maxStartProp"
{% if start_field %}
name-tag='{{ start_field.name }}'
initialmonth="{{ start_field.data.month }}"
initialday="{{ start_field.data.day }}"
initialyear="{{ start_field.data.year }}"
{% else %}
:name-tag="'clins-' + clinIndex + '-start_date'"
{% endif %}
:optional='{{ optional | string | lower }}'
v-on:date-change='handleDateChange'
inline-template>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
{{ "task_orders.form.pop_start" | translate }}
</div>
<p class='usa-input__help'>
{{ "task_orders.form.pop_example" | translate | safe }}
</p>
<div v-if='minError' class="usa-input-error-message">
PoP start date must be on or after {{ mindate | formattedDate(formatter="%B %d, %Y") }}.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP start date must be before end date.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>{{ 'components.date_selector.month' | translate }}</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>{{ 'components.date_selector.day' | translate }}</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>{{ 'components.date_selector.year' | translate }}</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
{% if maxdate %}max="{{ maxdate.year }}"{% endif %}
{% if mindate %}min="{{ mindate.year }}"{% endif %}
v-on:change="onInput"
/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>
<div class="usa-form-group-date-ok" v-else>
{{ Icon("alert", classes="icon--red")}}
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector
:mindate="minEndProp"
:maxdate="initialMaxEndDate"
{% if end_field %}
name-tag='{{ end_field.name }}'
initialmonth="{{ end_field.data.month }}"
initialday="{{ end_field.data.day }}"
initialyear="{{ end_field.data.year }}"
{% else %}
:name-tag="'clins-' + clinIndex + '-end_date'"
{% endif %}
:optional='{{ optional | string | lower }}'
v-on:date-change='handleDateChange'
inline-template>
<fieldset :name="name" class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid && isDateComplete, 'usa-input--error': !isDateValid && isDateComplete }">
<legend>
<div class="usa-input__title">
{{ 'task_orders.form.pop_end' | translate }}
</div>
{% set formatted_end_date = maxdate | formattedDate(formatter="%B %d, %Y") %}
{{ Alert(message="task_orders.form.pop_end_alert" | translate({'end_date': formatted_end_date })) }}
<p class='usa-input__help'>
{{ 'task_orders.form.pop_example' | translate }}
</p>
<div v-if='minError' class="usa-input-error-message">
PoP end date must be after start date.
</div>
<div v-if='maxError' class="usa-input-error-message">
PoP end date must be on or after {{ formatted_end_date }}.
</div>
</legend>
<div class="date-picker-component">
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>{{ 'components.date_selector.month' | translate }}</label>
<input
name="date-month"
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>{{ 'components.date_selector.day' | translate }}</label>
<input
name="date-day"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
v-bind:max="daysMaxCalculation"
v-model="day"
v-on:change="onInput"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>{{ 'components.date_selector.year' | translate }}</label>
<input
name="date-year"
maxlength="4"
type="number"
v-model="year"
v-on:change="onInput"
/>
</div>
<div v-if="isDateComplete">
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>
<div class="usa-form-group-date-ok" v-else>
{{ Icon("alert", classes="icon--red")}}
</div>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
</div>
</pop-date-range>
{% endmacro %}

View File

@ -38,12 +38,8 @@ def test_clin_form_start_date_before_end_date():
def test_clin_form_pop_dates_within_contract_dates():
CONTRACT_START_DATE = datetime.datetime.strptime(
app.config.get("CONTRACT_START_DATE"), "%Y-%m-%d"
).date()
CONTRACT_END_DATE = datetime.datetime.strptime(
app.config.get("CONTRACT_END_DATE"), "%Y-%m-%d"
).date()
CONTRACT_START_DATE = app.config.get("CONTRACT_START_DATE")
CONTRACT_END_DATE = app.config.get("CONTRACT_END_DATE")
invalid_start = CONTRACT_START_DATE - relativedelta(months=1)
invalid_end = CONTRACT_END_DATE + relativedelta(months=1)

View File

@ -135,3 +135,17 @@ def test_make_clin_fields(env, app):
0,
)
write_template(clin_fields, "clin_fields.html")
def test_make_pop_date_range(env, app):
pop_date_range_template = env.get_template("components/pop_date_range.html")
pop_date_range_macro = getattr(pop_date_range_template.module, "PopDateRange")
form = CLINForm()
pop_date_range = pop_date_range_macro(
start_field=form.start_date,
end_field=form.end_date,
mindate=app.config.get("CONTRACT_START_DATE"),
maxdate=app.config.get("CONTRACT_END_DATE"),
index=1,
)
write_template(pop_date_range, "pop_date_range.html")