Passed
Push — feature/timeline-profile-page ( 98f5ba...dd38c3 )
by Tristan
05:25
created

resources/assets/js/components/Application/BasicInfo/BasicInfo.tsx   A

Complexity

Total Complexity 14
Complexity/F 0

Size

Lines of Code 339
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 14
eloc 279
mnd 14
bc 14
fnc 0
dl 0
loc 339
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/* eslint-disable camelcase */
2
/* eslint-disable @typescript-eslint/camelcase */
3
import * as React from "react";
4
import { useIntl } from "react-intl";
5
import { Formik, Form, FastField } from "formik";
6
import * as Yup from "yup";
7
import messages, {
8
  citizenshipDeclaration,
9
  veteranStatus,
10
  languageRequirementDescription,
11
  languageRequirementLabel,
12
} from "./basicInfoMessages";
13
import {
14
  basicInfoMessages,
15
  navigationMessages,
16
  educationRequirementMessages,
17
} from "../applicationMessages";
18
import SelectInput from "../../Form/SelectInput";
19
import {
20
  CitizenshipId,
21
  VeteranId,
22
  LanguageRequirementId,
23
  ClassificationId,
24
  getKeyByValue,
25
} from "../../../models/lookupConstants";
26
import { validationMessages } from "../../Form/Messages";
27
import { Job, ApplicationNormalized } from "../../../models/types";
28
import CheckboxInput from "../../Form/CheckboxInput";
29
import { educationMessages } from "../../JobBuilder/Details/JobDetailsMessages";
30
import textToParagraphs from "../../../helpers/textToParagraphs";
31
import { getLocale, localizeField } from "../../../helpers/localize";
32
import { hasKey } from "../../../helpers/queries";
33
34
interface BasicInfoProps {
35
  application: ApplicationNormalized;
36
  job: Job;
37
  handleContinue: (values: ApplicationNormalized) => Promise<void>;
38
  handleReturn: (values: ApplicationNormalized) => Promise<void>;
39
  handleQuit: (values: ApplicationNormalized) => Promise<void>;
40
}
41
42
export interface BasicInfoFormValues {
43
  citizenship: number | "";
44
  veteranStatus: number | "";
45
  languageRequirement: boolean;
46
  languageTest: boolean;
47
  educationRequirement: boolean;
48
}
49
50
export const BasicInfo: React.FunctionComponent<BasicInfoProps> = ({
51
  application,
52
  job,
53
  handleContinue,
54
  handleReturn,
55
  handleQuit,
56
}) => {
57
  const intl = useIntl();
58
  const locale = getLocale(intl.locale);
59
  const classification: string = getKeyByValue(
60
    ClassificationId,
61
    job.classification_id,
62
  );
63
64
  const initialValues: BasicInfoFormValues = {
65
    citizenship: application?.citizenship_declaration_id
66
      ? application.citizenship_declaration_id
67
      : "",
68
    veteranStatus: application?.veteran_status_id
69
      ? application.veteran_status_id
70
      : "",
71
    languageRequirement: application?.language_requirement_confirmed
72
      ? application.language_requirement_confirmed
73
      : false,
74
    languageTest: application?.language_test_confirmed
75
      ? application.language_test_confirmed
76
      : false,
77
    educationRequirement: application?.education_requirement_confirmed
78
      ? application.education_requirement_confirmed
79
      : false,
80
  };
81
82
  const jobEducationReq = localizeField(locale, job, "education");
83
  const defaultEducationReq = hasKey(educationMessages, classification)
84
    ? intl.formatMessage(educationMessages[classification])
85
    : intl.formatMessage(educationRequirementMessages.missingClassification);
86
  // If the job is using the default education requirements (for its classification) then we
87
  //  can predictably style it, by setting the right lines to bold. Otherwise, all we can do is
88
  //  split it into paragraphs.
89
  const educationRequirements =
90
    jobEducationReq === null || jobEducationReq === defaultEducationReq
91
      ? textToParagraphs(
92
          defaultEducationReq,
93
          {},
94
          {
95
            0: { "data-c-font-weight": "bold" },
96
            5: { "data-c-font-weight": "bold" },
97
          },
98
        )
99
      : textToParagraphs(jobEducationReq);
100
101
  const validationSchema = Yup.object().shape({
102
    citizenship: Yup.number()
103
      .oneOf(
104
        Object.values(CitizenshipId),
105
        intl.formatMessage(validationMessages.invalidSelection),
106
      )
107
      .required(intl.formatMessage(validationMessages.required)),
108
    veteranStatus: Yup.number()
109
      .oneOf(
110
        Object.values(VeteranId),
111
        intl.formatMessage(validationMessages.invalidSelection),
112
      )
113
      .required(intl.formatMessage(validationMessages.required)),
114
    languageRequirement: Yup.boolean()
115
      .required(intl.formatMessage(validationMessages.required))
116
      .oneOf([true], intl.formatMessage(validationMessages.required)),
117
    languageTest: Yup.boolean().notRequired(),
118
    educationRequirement: Yup.boolean()
119
      .required(intl.formatMessage(validationMessages.required))
120
      .oneOf([true], intl.formatMessage(validationMessages.required)),
121
  });
122
123
  const updateApplication = (
124
    oldApplication: ApplicationNormalized,
125
    values: BasicInfoFormValues,
126
  ): ApplicationNormalized => {
127
    const editedApplication: ApplicationNormalized = {
128
      ...oldApplication,
129
      citizenship_declaration_id: values.citizenship
130
        ? Number(values.citizenship)
131
        : null,
132
      veteran_status_id: values.veteranStatus
133
        ? Number(values.veteranStatus)
134
        : null,
135
      language_requirement_confirmed: values.languageRequirement,
136
      language_test_confirmed: values.languageTest,
137
      education_requirement_confirmed: values.educationRequirement,
138
    };
139
    return editedApplication;
140
  };
141
142
  return (
143
    <div data-c-container="medium" data-c-padding="tb(2)">
144
      <h2 data-c-heading="h2" data-c-margin="top(3) bottom(1)">
145
        {intl.formatMessage(basicInfoMessages.heading)}
146
      </h2>
147
      <Formik
148
        initialValues={initialValues}
149
        validationSchema={validationSchema}
150
        onSubmit={(values, { setSubmitting }): void => {
151
          // Save data to application object, then navigate to the next step
152
          const basicInfoFormValues: BasicInfoFormValues = {
153
            ...values,
154
          };
155
156
          handleContinue(
157
            updateApplication(application, basicInfoFormValues),
158
          ).finally(() => {
159
            setSubmitting(false);
160
          });
161
        }}
162
      >
163
        {({ isSubmitting, values }): React.ReactElement => (
164
          <Form>
165
            <div data-c-grid="gutter(all, 1)">
166
              {/* Citizenship Declaration */}
167
              <FastField
168
                id="citizenship"
169
                name="citizenship"
170
                component={SelectInput}
171
                required
172
                label={intl.formatMessage(basicInfoMessages.citizenshipLabel)}
173
                grid="base(1of1)"
174
                nullSelection={intl.formatMessage(messages.nullSelectOption)}
175
                options={Object.values(CitizenshipId).map((id: number): {
176
                  value: number;
177
                  label: string;
178
                } => ({
179
                  value: id,
180
                  label: intl.formatMessage(citizenshipDeclaration(id)),
181
                }))}
182
              />
183
              {/* Veteran Status */}
184
              <FastField
185
                id="veteranStatus"
186
                name="veteranStatus"
187
                component={SelectInput}
188
                required
189
                label={intl.formatMessage(basicInfoMessages.veteranStatusLabel)}
190
                grid="base(1of1)"
191
                nullSelection={intl.formatMessage(messages.nullSelectOption)}
192
                options={Object.values(VeteranId).map((id: number): {
193
                  value: number;
194
                  label: string;
195
                } => ({
196
                  value: id,
197
                  label: intl.formatMessage(veteranStatus(id)),
198
                }))}
199
              />
200
            </div>
201
            {/* Language Requirement */}
202
            <h3
203
              data-c-heading="h3"
204
              data-c-margin="top(2) bottom(1)"
205
              data-c-padding="bottom(1)"
206
            >
207
              {intl.formatMessage(
208
                basicInfoMessages.languageRequirementsHeading,
209
              )}
210
            </h3>
211
            <p>
212
              {job.language_requirement_id &&
213
                intl.formatMessage(
214
                  languageRequirementDescription(job.language_requirement_id),
215
                )}
216
            </p>
217
            <div data-c-margin="left(2)">
218
              <FastField
219
                id="languageRequirement"
220
                name="languageRequirement"
221
                component={CheckboxInput}
222
                required
223
                label={
224
                  job.language_requirement_id &&
225
                  intl.formatMessage(
226
                    languageRequirementLabel(job.language_requirement_id),
227
                  )
228
                }
229
              />
230
            </div>
231
            {/* Language Test (only displayed for bilingual language requirements) */}
232
            {((job.language_requirement_id &&
233
              job.language_requirement_id ===
234
                LanguageRequirementId.bilingualAdvanced) ||
235
              job.language_requirement_id ===
236
                LanguageRequirementId.bilingualIntermediate) && (
237
              <div data-c-margin="left(2)">
238
                <FastField
239
                  id="languageTest"
240
                  name="languageTest"
241
                  component={CheckboxInput}
242
                  required
243
                  label={intl.formatMessage(messages.languageTestLabel)}
244
                />
245
              </div>
246
            )}
247
            {/* Education Requirement */}
248
            <h3
249
              data-c-heading="h3"
250
              data-c-margin="top(2) bottom(1)"
251
              data-c-padding="bottom(1)"
252
            >
253
              {intl.formatMessage(messages.educationRequirementHeader)}
254
            </h3>
255
            <p data-c-margin="bottom(1)">
256
              {intl.formatMessage(messages.meetEducationRequirement)}
257
            </p>
258
            <div
259
              data-c-background="gray(20)"
260
              data-c-radius="rounded"
261
              data-c-padding="all(1)"
262
              data-c-margin="bottom(1)"
263
            >
264
              {educationRequirements}
265
            </div>
266
            <div data-c-margin="left(2)">
267
              <FastField
268
                id="educationRequirement"
269
                name="educationRequirement"
270
                component={CheckboxInput}
271
                required
272
                label={intl.formatMessage(messages.educationRequirementLabel)}
273
              />
274
            </div>
275
            <hr data-c-hr="thin(c1)" data-c-margin="bottom(2)" />
276
            <div data-c-grid="gutter">
277
              <div
278
                data-c-alignment="base(centre) tp(left)"
279
                data-c-grid-item="tp(1of2)"
280
              >
281
                <button
282
                  data-c-button="outline(c2)"
283
                  data-c-radius="rounded"
284
                  type="button"
285
                  disabled={isSubmitting}
286
                  onClick={(): void => {
287
                    const basicInfoFormValues: BasicInfoFormValues = {
288
                      ...values,
289
                    };
290
                    // Method should save the current data and return user to the previous step
291
                    handleReturn(
292
                      updateApplication(application, basicInfoFormValues),
293
                    );
294
                  }}
295
                >
296
                  {intl.formatMessage(navigationMessages.return)}
297
                </button>
298
              </div>
299
              <div
300
                data-c-alignment="base(centre) tp(right)"
301
                data-c-grid-item="tp(1of2)"
302
              >
303
                <button
304
                  data-c-button="outline(c2)"
305
                  data-c-radius="rounded"
306
                  type="button"
307
                  disabled={isSubmitting}
308
                  onClick={(): void => {
309
                    const basicInfoFormValues: BasicInfoFormValues = {
310
                      ...values,
311
                    };
312
                    // Method should save the current data and return user to My Applications page
313
                    handleQuit(
314
                      updateApplication(application, basicInfoFormValues),
315
                    );
316
                  }}
317
                >
318
                  {intl.formatMessage(navigationMessages.quit)}
319
                </button>
320
                <button
321
                  data-c-button="solid(c1)"
322
                  data-c-radius="rounded"
323
                  data-c-margin="left(1)"
324
                  type="submit"
325
                  disabled={isSubmitting}
326
                >
327
                  {intl.formatMessage(navigationMessages.continue)}
328
                </button>
329
              </div>
330
            </div>
331
          </Form>
332
        )}
333
      </Formik>
334
    </div>
335
  );
336
};
337
338
export default BasicInfo;
339