Passed
Push — task/remove-default-nginx ( 12a8d3 )
by
unknown
06:43
created

resources/assets/js/components/Application/ExperienceModals/AwardExperienceModal.tsx   A

Complexity

Total Complexity 6
Complexity/F 0

Size

Lines of Code 385
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 6
eloc 308
mnd 6
bc 6
fnc 0
dl 0
loc 385
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import React from "react";
2
import { FastField, Formik, Form } from "formik";
3
import { defineMessages, useIntl, IntlShape } from "react-intl";
4
import * as Yup from "yup";
5
import {
6
  EducationFormValues,
7
  EducationSubform,
8
  validationShape as educationValidationShape,
9
} from "./EducationSubform";
10
import TextInput from "../../Form/TextInput";
11
import { validationMessages } from "../../Form/Messages";
12
import SkillSubform, {
13
  SkillFormValues,
14
  validationShape as skillValidationShape,
15
} from "./SkillSubform";
16
import { Skill, ExperienceAward } from "../../../models/types";
17
import {
18
  ExperienceModalHeader,
19
  ExperienceDetailsIntro,
20
  ExperienceModalFooter,
21
} from "./ExperienceModalCommon";
22
import Modal from "../../Modal";
23
import DateInput from "../../Form/DateInput";
24
import { toInputDateString, fromInputDateString } from "../../../helpers/dates";
25
import {
26
  Locales,
27
  localizeFieldNonNull,
28
  getLocale,
29
  matchValueToModel,
30
} from "../../../helpers/localize";
31
import { notEmpty } from "../../../helpers/queries";
32
import { localizedFieldNonNull } from "../../../models/app";
33
import SelectInput from "../../Form/SelectInput";
34
35
export interface AwardRecipientType {
36
  id: number;
37
  name: localizedFieldNonNull;
38
}
39
40
export interface AwardRecognitionType {
41
  id: number;
42
  name: localizedFieldNonNull;
43
}
44
45
interface AwardExperienceModalProps {
46
  modalId: string;
47
  experienceAward: ExperienceAward | null;
48
  recipientTypes: AwardRecipientType[];
49
  recognitionTypes: AwardRecognitionType[];
50
  jobId: number;
51
  jobClassification: string;
52
  jobEducationRequirements: string | null;
53
  requiredSkills: Skill[];
54
  savedRequiredSkills: Skill[];
55
  optionalSkills: Skill[];
56
  savedOptionalSkills: Skill[];
57
  experienceableId: number;
58
  experienceableType: ExperienceAward["experienceable_type"];
59
  parentElement: Element | null;
60
  visible: boolean;
61
  onModalCancel: () => void;
62
  onModalConfirm: (data: AwardExperienceSubmitData) => Promise<void>;
63
}
64
65
export const messages = defineMessages({
66
  modalTitle: {
67
    id: "application.awardExperienceModal.modalTitle",
68
    defaultMessage: "Add an Award",
69
  },
70
  modalDescription: {
71
    id: "application.awardExperienceModal.modalDescription",
72
    defaultMessage:
73
      "Did you get recognized for going above and beyond? There are many ways to get recognized, awards are just one of them. (Here’s an opportunity to share how you’ve been recognized.)",
74
  },
75
  titleLabel: {
76
    id: "application.awardExperienceModal.titleLabel",
77
    defaultMessage: "Award Title",
78
  },
79
  titlePlaceholder: {
80
    id: "application.awardExperienceModal.titlePlaceholder",
81
    defaultMessage: "e.g. My Award",
82
  },
83
  recipientTypeLabel: {
84
    id: "application.awardExperienceModal.recipientTypeLabel",
85
    defaultMessage: "Awarded to...",
86
  },
87
  recipientTypePlaceholder: {
88
    id: "application.awardExperienceModal.recipientTypePlaceholder",
89
    defaultMessage: "Select an option...",
90
    description: "Default selection in the Recipient Type dropdown menu.",
91
  },
92
  issuerLabel: {
93
    id: "application.awardExperienceModal.issuerLabel",
94
    defaultMessage: "Issuing Organization or Institution",
95
  },
96
  issuerPlaceholder: {
97
    id: "application.awardExperienceModal.issuerPlaceholder",
98
    defaultMessage: "e.g. Government of Canada",
99
  },
100
  recognitionTypeLabel: {
101
    id: "application.awardExperienceModal.recognitionTypeLabel",
102
    defaultMessage: "Scope of the Award",
103
  },
104
  recognitionTypePlaceholder: {
105
    id: "application.awardExperienceModal.recognitionTypePlaceholder",
106
    defaultMessage: "Select a scope...",
107
  },
108
  awardedDateLabel: {
109
    id: "application.awardExperienceModal.awardedDateLabel",
110
    defaultMessage: "Date Awarded",
111
  },
112
  datePlaceholder: {
113
    id: "application.awardExperienceModal.datePlaceholder",
114
    defaultMessage: "yyyy-mm-dd",
115
  },
116
});
117
118
export interface AwardDetailsFormValues {
119
  title: string;
120
  recipientTypeId: number | "";
121
  issuedBy: string;
122
  recognitionTypeId: number | "";
123
  awardedDate: string;
124
}
125
126
type AwardExperienceFormValues = SkillFormValues &
127
  EducationFormValues &
128
  AwardDetailsFormValues;
129
export interface AwardExperienceSubmitData {
130
  experienceAward: ExperienceAward;
131
  savedRequiredSkills: Skill[];
132
  savedOptionalSkills: Skill[];
133
}
134
135
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
136
export const validationShape = (intl: IntlShape) => {
137
  const requiredMsg = intl.formatMessage(validationMessages.required);
138
  const inPastMsg = intl.formatMessage(validationMessages.dateMustBePast);
139
  return {
140
    title: Yup.string().required(requiredMsg),
141
    recipientTypeId: Yup.number().required(requiredMsg),
142
    issuedBy: Yup.string().required(requiredMsg),
143
    recognitionTypeId: Yup.number().required(requiredMsg),
144
    awardedDate: Yup.date().required(requiredMsg).max(new Date(), inPastMsg),
145
  };
146
};
147
148
const dataToFormValues = (
149
  data: AwardExperienceSubmitData,
150
  locale: Locales,
151
  creatingNew: boolean,
152
): AwardExperienceFormValues => {
153
  const { experienceAward, savedRequiredSkills, savedOptionalSkills } = data;
154
  const skillToName = (skill: Skill): string =>
155
    localizeFieldNonNull(locale, skill, "name");
156
  return {
157
    requiredSkills: savedRequiredSkills.map(skillToName),
158
    optionalSkills: savedOptionalSkills.map(skillToName),
159
    useAsEducationRequirement: experienceAward.is_education_requirement,
160
    title: experienceAward.title,
161
    recipientTypeId: creatingNew ? "" : experienceAward.award_recipient_type_id,
162
    issuedBy: experienceAward.issued_by,
163
    recognitionTypeId: creatingNew
164
      ? ""
165
      : experienceAward.award_recognition_type_id,
166
    awardedDate: toInputDateString(experienceAward.awarded_date),
167
  };
168
};
169
170
/* eslint-disable @typescript-eslint/camelcase */
171
const formValuesToData = (
172
  formValues: AwardExperienceFormValues,
173
  originalExperience: ExperienceAward,
174
  locale: Locales,
175
  skills: Skill[],
176
): AwardExperienceSubmitData => {
177
  const nameToSkill = (name: string): Skill | null =>
178
    matchValueToModel(locale, "name", name, skills);
179
  return {
180
    experienceAward: {
181
      ...originalExperience,
182
      title: formValues.title,
183
      award_recipient_type_id: formValues.recipientTypeId
184
        ? Number(formValues.recipientTypeId)
185
        : 1,
186
      issued_by: formValues.issuedBy,
187
      award_recognition_type_id: formValues.recognitionTypeId
188
        ? Number(formValues.recognitionTypeId)
189
        : 1,
190
      awarded_date: fromInputDateString(formValues.awardedDate),
191
      is_education_requirement: formValues.useAsEducationRequirement,
192
    },
193
    savedRequiredSkills: formValues.requiredSkills
194
      .map(nameToSkill)
195
      .filter(notEmpty),
196
    savedOptionalSkills: formValues.optionalSkills
197
      .map(nameToSkill)
198
      .filter(notEmpty),
199
  };
200
};
201
202
const newExperienceAward = (
203
  experienceableId: number,
204
  experienceableType: ExperienceAward["experienceable_type"],
205
): ExperienceAward => ({
206
  id: 0,
207
  title: "",
208
  award_recipient_type_id: 0,
209
  award_recipient_type: { en: "", fr: "" },
210
  issued_by: "",
211
  award_recognition_type_id: 0,
212
  award_recognition_type: { en: "", fr: "" },
213
  awarded_date: new Date(),
214
  is_education_requirement: false,
215
  experienceable_id: experienceableId,
216
  experienceable_type: experienceableType,
217
  type: "experience_award",
218
});
219
/* eslint-enable @typescript-eslint/camelcase */
220
221
export const AwardExperienceModal: React.FC<AwardExperienceModalProps> = ({
222
  modalId,
223
  experienceAward,
224
  recipientTypes,
225
  recognitionTypes,
226
  jobId,
227
  jobClassification,
228
  jobEducationRequirements,
229
  requiredSkills,
230
  savedRequiredSkills,
231
  optionalSkills,
232
  savedOptionalSkills,
233
  experienceableId,
234
  experienceableType,
235
  parentElement,
236
  visible,
237
  onModalCancel,
238
  onModalConfirm,
239
}) => {
240
  const intl = useIntl();
241
  const locale = getLocale(intl.locale);
242
243
  const originalExperience =
244
    experienceAward ?? newExperienceAward(experienceableId, experienceableType);
245
246
  const skillToName = (skill: Skill): string =>
247
    localizeFieldNonNull(locale, skill, "name");
248
249
  const initialFormValues = dataToFormValues(
250
    {
251
      experienceAward: originalExperience,
252
      savedRequiredSkills,
253
      savedOptionalSkills,
254
    },
255
    locale,
256
    experienceAward === null,
257
  );
258
259
  const validationSchema = Yup.object().shape({
260
    ...skillValidationShape,
261
    ...educationValidationShape,
262
    ...validationShape(intl),
263
  });
264
265
  const detailsSubform = (
266
    <div data-c-container="medium">
267
      <div data-c-grid="gutter(all, 1) middle">
268
        <FastField
269
          id="award-title"
270
          type="text"
271
          name="title"
272
          component={TextInput}
273
          required
274
          grid="base(1of1)"
275
          label={intl.formatMessage(messages.titleLabel)}
276
          placeholder={intl.formatMessage(messages.titlePlaceholder)}
277
        />
278
        <FastField
279
          id="award-recipientTypeId"
280
          name="recipientTypeId"
281
          label={intl.formatMessage(messages.recipientTypeLabel)}
282
          grid="tl(1of2)"
283
          component={SelectInput}
284
          required
285
          nullSelection={intl.formatMessage(messages.recipientTypePlaceholder)}
286
          options={recipientTypes.map((type) => ({
287
            value: type.id,
288
            label: localizeFieldNonNull(locale, type, "name"),
289
          }))}
290
        />
291
        <FastField
292
          id="award-issuedBy"
293
          type="text"
294
          name="issuedBy"
295
          component={TextInput}
296
          required
297
          grid="tl(1of2)"
298
          label={intl.formatMessage(messages.issuerLabel)}
299
          placeholder={intl.formatMessage(messages.issuerPlaceholder)}
300
        />
301
        <FastField
302
          id="award-recognitionTypeId"
303
          name="recognitionTypeId"
304
          label={intl.formatMessage(messages.recognitionTypeLabel)}
305
          grid="tl(1of2)"
306
          component={SelectInput}
307
          required
308
          nullSelection={intl.formatMessage(
309
            messages.recognitionTypePlaceholder,
310
          )}
311
          options={recognitionTypes.map((status) => ({
312
            value: status.id,
313
            label: localizeFieldNonNull(locale, status, "name"),
314
          }))}
315
        />
316
        <FastField
317
          id="award-awardedDate"
318
          name="awardedDate"
319
          component={DateInput}
320
          required
321
          grid="tl(1of2)"
322
          label={intl.formatMessage(messages.awardedDateLabel)}
323
          placeholder={intl.formatMessage(messages.datePlaceholder)}
324
        />
325
      </div>
326
    </div>
327
  );
328
329
  return (
330
    <Modal
331
      id={modalId}
332
      parentElement={parentElement}
333
      visible={visible}
334
      onModalCancel={onModalCancel}
335
      onModalConfirm={onModalCancel}
336
      className="application-experience-dialog"
337
    >
338
      <ExperienceModalHeader
339
        title={intl.formatMessage(messages.modalTitle)}
340
        iconClass="fa-book"
341
      />
342
      <Formik
343
        enableReinitialize
344
        initialValues={initialFormValues}
345
        onSubmit={async (values, actions): Promise<void> => {
346
          await onModalConfirm(
347
            formValuesToData(values, originalExperience, locale, [
348
              ...requiredSkills,
349
              ...optionalSkills,
350
            ]),
351
          );
352
          actions.setSubmitting(false);
353
          actions.resetForm();
354
        }}
355
        validationSchema={validationSchema}
356
      >
357
        {(formikProps): React.ReactElement => (
358
          <Form>
359
            <Modal.Body>
360
              <ExperienceDetailsIntro
361
                description={intl.formatMessage(messages.modalDescription)}
362
              />
363
              {detailsSubform}
364
              <SkillSubform
365
                keyPrefix="award"
366
                jobId={jobId}
367
                jobRequiredSkills={requiredSkills.map(skillToName)}
368
                jobOptionalSkills={optionalSkills.map(skillToName)}
369
              />
370
              <EducationSubform
371
                keyPrefix="award"
372
                jobClassification={jobClassification}
373
                jobEducationRequirements={jobEducationRequirements}
374
              />
375
            </Modal.Body>
376
            <ExperienceModalFooter buttonsDisabled={formikProps.isSubmitting} />
377
          </Form>
378
        )}
379
      </Formik>
380
    </Modal>
381
  );
382
};
383
384
export default AwardExperienceModal;
385