Passed
Push — feature/update-managers-applca... ( b38314...b92f9a )
by Yonathan
04:52
created

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

Complexity

Total Complexity 6
Complexity/F 0

Size

Lines of Code 375
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 6
eloc 300
mnd 6
bc 6
fnc 0
dl 0
loc 375
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
  EducationSubformProps,
7
  EducationFormValues,
8
  EducationSubform,
9
  validationShape as educationValidationShape,
10
} from "./EducationSubform";
11
import TextInput from "../../Form/TextInput";
12
import { validationMessages } from "../../Form/Messages";
13
import SkillSubform, {
14
  SkillFormValues,
15
  validationShape as skillValidationShape,
16
} from "./SkillSubform";
17
import { Skill, ExperienceAward } from "../../../models/types";
18
import {
19
  ExperienceModalHeader,
20
  ExperienceDetailsIntro,
21
  ExperienceModalFooter,
22
} from "./ExperienceModalCommon";
23
import Modal from "../../Modal";
24
import DateInput from "../../Form/DateInput";
25
import { toInputDateString, fromInputDateString } from "../../../helpers/dates";
26
import {
27
  Locales,
28
  localizeFieldNonNull,
29
  getLocale,
30
  matchValueToModel,
31
} from "../../../helpers/localize";
32
import { notEmpty } from "../../../helpers/queries";
33
import { localizedFieldNonNull } from "../../../models/app";
34
import SelectInput from "../../Form/SelectInput";
35
36
interface AwardRecipientType {
37
  id: number;
38
  name: localizedFieldNonNull;
39
}
40
41
interface AwardRecognitionType {
42
  id: number;
43
  name: localizedFieldNonNull;
44
}
45
46
interface AwardExperienceModalProps {
47
  modalId: string;
48
  experienceAward: ExperienceAward | null;
49
  recipientTypes: AwardRecipientType[];
50
  recognitionTypes: AwardRecognitionType[];
51
  jobId: number;
52
  requiredSkills: Skill[];
53
  savedRequiredSkills: Skill[];
54
  optionalSkills: Skill[];
55
  savedOptionalSkills: Skill[];
56
  experienceRequirments: EducationSubformProps;
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
const messages = defineMessages({
66
  modalTitle: {
67
    id: "awardExperienceModal.modalTitle",
68
    defaultMessage: "Add an Award",
69
  },
70
  modalDescription: {
71
    id: "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: "awardExperienceModal.titleLabel",
77
    defaultMessage: "Award Title",
78
  },
79
  titlePlaceholder: {
80
    id: "awardExperienceModal.titlePlaceholder",
81
    defaultMessage: "e.g. My Award",
82
  },
83
  recipientTypeLabel: {
84
    id: "awardExperienceModal.recipientTypeLabel",
85
    defaultMessage: "Awarded to...",
86
  },
87
  recipientTypePlaceholder: {
88
    id: "awardExperienceModal.recipientTypePlaceholder",
89
    defaultMessage: "Select an option...",
90
    description: "Default selection in the Recipient Type dropdown menu.",
91
  },
92
  issuerLabel: {
93
    id: "awardExperienceModal.issuerLabel",
94
    defaultMessage: "Issuing Organization or Institution",
95
  },
96
  issuerPlaceholder: {
97
    id: "awardExperienceModal.issuerPlaceholder",
98
    defaultMessage: "e.g. Government of Canada",
99
  },
100
  recognitionTypeLabel: {
101
    id: "awardExperienceModal.recognitionTypeLabel",
102
    defaultMessage: "Scope of the Award",
103
  },
104
  recognitionTypePlaceholder: {
105
    id: "awardExperienceModal.recognitionTypePlaceholder",
106
    defaultMessage: "Select a scope...",
107
  },
108
  awardedDateLabel: {
109
    id: "awardExperienceModal.awardedDateLabel",
110
    defaultMessage: "Date Awarded",
111
  },
112
  datePlaceholder: {
113
    id: "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
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
  issued_by: "",
210
  award_recognition_type_id: 0,
211
  awarded_date: new Date(),
212
  is_education_requirement: false,
213
  experienceable_id: experienceableId,
214
  experienceable_type: experienceableType,
215
});
216
/* eslint-enable @typescript-eslint/camelcase */
217
218
export const AwardExperienceModal: React.FC<AwardExperienceModalProps> = ({
219
  modalId,
220
  experienceAward,
221
  recipientTypes,
222
  recognitionTypes,
223
  jobId,
224
  requiredSkills,
225
  savedRequiredSkills,
226
  optionalSkills,
227
  savedOptionalSkills,
228
  experienceRequirments,
229
  experienceableId,
230
  experienceableType,
231
  parentElement,
232
  visible,
233
  onModalCancel,
234
  onModalConfirm,
235
}) => {
236
  const intl = useIntl();
237
  const locale = getLocale(intl.locale);
238
239
  const originalExperience =
240
    experienceAward ?? newExperienceAward(experienceableId, experienceableType);
241
242
  const skillToName = (skill: Skill): string =>
243
    localizeFieldNonNull(locale, skill, "name");
244
245
  const initialFormValues = dataToFormValues(
246
    {
247
      experienceAward: originalExperience,
248
      savedRequiredSkills,
249
      savedOptionalSkills,
250
    },
251
    locale,
252
    experienceAward === null,
253
  );
254
255
  const validationSchema = Yup.object().shape({
256
    ...skillValidationShape,
257
    ...educationValidationShape,
258
    ...validationShape(intl),
259
  });
260
261
  const detailsSubform = (
262
    <div data-c-container="medium">
263
      <div data-c-grid="gutter(all, 1) middle">
264
        <FastField
265
          id="title"
266
          type="text"
267
          name="title"
268
          component={TextInput}
269
          required
270
          grid="base(1of1)"
271
          label={intl.formatMessage(messages.titleLabel)}
272
          placeholder={intl.formatMessage(messages.titlePlaceholder)}
273
        />
274
        <FastField
275
          id="recipientTypeId"
276
          name="recipientTypeId"
277
          label={intl.formatMessage(messages.recipientTypeLabel)}
278
          grid="tl(1of2)"
279
          component={SelectInput}
280
          required
281
          nullSelection={intl.formatMessage(messages.recipientTypePlaceholder)}
282
          options={recipientTypes.map((type) => ({
283
            value: type.id,
284
            label: localizeFieldNonNull(locale, type, "name"),
285
          }))}
286
        />
287
        <FastField
288
          id="issuedBy"
289
          type="text"
290
          name="issuedBy"
291
          component={TextInput}
292
          required
293
          grid="tl(1of2)"
294
          label={intl.formatMessage(messages.issuerLabel)}
295
          placeholder={intl.formatMessage(messages.issuerPlaceholder)}
296
        />
297
        <FastField
298
          id="recognitionTypeId"
299
          name="recognitionTypeId"
300
          label={intl.formatMessage(messages.recognitionTypeLabel)}
301
          grid="tl(1of2)"
302
          component={SelectInput}
303
          required
304
          nullSelection={intl.formatMessage(
305
            messages.recognitionTypePlaceholder,
306
          )}
307
          options={recognitionTypes.map((status) => ({
308
            value: status.id,
309
            label: localizeFieldNonNull(locale, status, "name"),
310
          }))}
311
        />
312
        <FastField
313
          id="awardedDate"
314
          name="awardedDate"
315
          component={DateInput}
316
          required
317
          grid="tl(1of2)"
318
          label={intl.formatMessage(messages.awardedDateLabel)}
319
          placeholder={intl.formatMessage(messages.datePlaceholder)}
320
        />
321
      </div>
322
    </div>
323
  );
324
325
  return (
326
    <Modal
327
      id={modalId}
328
      parentElement={parentElement}
329
      visible={visible}
330
      onModalCancel={onModalCancel}
331
      onModalConfirm={onModalCancel}
332
      className="application-experience-dialog"
333
    >
334
      <ExperienceModalHeader
335
        title={intl.formatMessage(messages.modalTitle)}
336
        iconClass="fa-book"
337
      />
338
      <Formik
339
        enableReinitialize
340
        initialValues={initialFormValues}
341
        onSubmit={async (values, actions): Promise<void> => {
342
          await onModalConfirm(
343
            formValuesToData(values, originalExperience, locale, [
344
              ...requiredSkills,
345
              ...optionalSkills,
346
            ]),
347
          );
348
          actions.setSubmitting(false);
349
        }}
350
        validationSchema={validationSchema}
351
      >
352
        {(formikProps): React.ReactElement => (
353
          <Form>
354
            <Modal.Body>
355
              <ExperienceDetailsIntro
356
                description={intl.formatMessage(messages.modalDescription)}
357
              />
358
              {detailsSubform}
359
              <SkillSubform
360
                jobId={jobId}
361
                jobRequiredSkills={requiredSkills.map(skillToName)}
362
                jobOptionalSkills={optionalSkills.map(skillToName)}
363
              />
364
              <EducationSubform {...experienceRequirments} />
365
            </Modal.Body>
366
            <ExperienceModalFooter buttonsDisabled={formikProps.isSubmitting} />
367
          </Form>
368
        )}
369
      </Formik>
370
    </Modal>
371
  );
372
};
373
374
export default AwardExperienceModal;
375