Passed
Push — dev ( 66cf75...b07ec4 )
by
unknown
04:38
created

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

Complexity

Total Complexity 4
Complexity/F 0

Size

Lines of Code 383
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 310
mnd 4
bc 4
fnc 0
dl 0
loc 383
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import React from "react";
2
import { FastField, Field, 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 CheckboxInput from "../../Form/CheckboxInput";
12
import { validationMessages } from "../../Form/Messages";
13
import SkillSubform, {
14
  SkillFormValues,
15
  validationShape as skillValidationShape,
16
} from "./SkillSubform";
17
import { Skill, ExperienceCommunity } 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
34
interface CommunityExperienceModalProps {
35
  modalId: string;
36
  experienceCommunity: ExperienceCommunity | null;
37
  jobId: number;
38
  jobClassification: string;
39
  jobEducationRequirements: string | null;
40
  requiredSkills: Skill[];
41
  savedRequiredSkills: Skill[];
42
  optionalSkills: Skill[];
43
  savedOptionalSkills: Skill[];
44
  experienceableId: number;
45
  experienceableType: ExperienceCommunity["experienceable_type"];
46
  parentElement: Element | null;
47
  visible: boolean;
48
  onModalCancel: () => void;
49
  onModalConfirm: (data: CommunityExperienceSubmitData) => Promise<void>;
50
}
51
52
export const messages = defineMessages({
53
  modalTitle: {
54
    id: "application.communityExperienceModal.modalTitle",
55
    defaultMessage: "Add Community Experience",
56
  },
57
  modalDescription: {
58
    id: "application.communityExperienceModal.modalDescription",
59
    defaultMessage:
60
      "Gained experience by being part of or giving back to a community? People learn skills from a wide range of experiences like volunteering or being part of non-profit organizations, indigenous communities, or virtual collaborations. (Here’s an opportunity to share the skills that your community has helped you develop.)",
61
  },
62
  titleLabel: {
63
    id: "application.communityExperienceModal.titleLabel",
64
    defaultMessage: "My Role / Job Title",
65
  },
66
  titlePlaceholder: {
67
    id: "application.communityExperienceModal.titlePlaceholder",
68
    defaultMessage: "e.g. Front-end Development",
69
  },
70
  groupLabel: {
71
    id: "application.communityExperienceModal.groupLabel",
72
    defaultMessage: "Group / Organization / Community",
73
  },
74
  groupPlaceholder: {
75
    id: "application.communityExperienceModal.groupPlaceholder",
76
    defaultMessage: "e.g. Government of Canada",
77
  },
78
  projectLabel: {
79
    id: "application.communityExperienceModal.projectLabel",
80
    defaultMessage: "Project / Product Name",
81
  },
82
  projectPlaceholder: {
83
    id: "application.communityExperienceModal.projectPlaceholder",
84
    defaultMessage: "e.g. Talent Cloud",
85
  },
86
  startDateLabel: {
87
    id: "application.communityExperienceModal.startDateLabel",
88
    defaultMessage: "Select a Start Date",
89
  },
90
  datePlaceholder: {
91
    id: "application.communityExperienceModal.datePlaceholder",
92
    defaultMessage: "yyyy-mm-dd",
93
  },
94
  isActiveLabel: {
95
    id: "application.communityExperienceModal.isActiveLabel",
96
    defaultMessage: "This experience is still ongoing, or...",
97
    description: "Label for checkbox that indicates work is still ongoing.",
98
  },
99
  endDateLabel: {
100
    id: "application.communityExperienceModal.endDateLabel",
101
    defaultMessage: "Select an End Date",
102
  },
103
});
104
105
export interface CommunityDetailsFormValues {
106
  title: string;
107
  group: string;
108
  project: string;
109
  startDate: string;
110
  isActive: boolean;
111
  endDate: string;
112
}
113
114
type CommunityExperienceFormValues = SkillFormValues &
115
  EducationFormValues &
116
  CommunityDetailsFormValues;
117
export interface CommunityExperienceSubmitData {
118
  experienceCommunity: ExperienceCommunity;
119
  savedRequiredSkills: Skill[];
120
  savedOptionalSkills: Skill[];
121
}
122
123
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
124
export const validationShape = (intl: IntlShape) => {
125
  const requiredMsg = intl.formatMessage(validationMessages.required);
126
  const conditionalRequiredMsg = intl.formatMessage(
127
    validationMessages.endDateRequiredIfNotOngoing,
128
  );
129
  const inPastMsg = intl.formatMessage(validationMessages.dateMustBePast);
130
  const afterStartDateMsg = intl.formatMessage(
131
    validationMessages.endDateAfterStart,
132
  );
133
  return {
134
    title: Yup.string().required(requiredMsg),
135
    group: Yup.string().required(requiredMsg),
136
    project: Yup.string().required(requiredMsg),
137
    startDate: Yup.date().required(requiredMsg).max(new Date(), inPastMsg),
138
    isActive: Yup.boolean(),
139
    endDate: Yup.date().when("isActive", {
140
      is: false,
141
      then: Yup.date()
142
        .required(conditionalRequiredMsg)
143
        .min(Yup.ref("startDate"), afterStartDateMsg),
144
      otherwise: Yup.date().min(Yup.ref("startDate"), afterStartDateMsg),
145
    }),
146
  };
147
};
148
149
const dataToFormValues = (
150
  data: CommunityExperienceSubmitData,
151
  locale: Locales,
152
): CommunityExperienceFormValues => {
153
  const {
154
    experienceCommunity,
155
    savedRequiredSkills,
156
    savedOptionalSkills,
157
  } = data;
158
  const skillToName = (skill: Skill): string =>
159
    localizeFieldNonNull(locale, skill, "name");
160
  return {
161
    requiredSkills: savedRequiredSkills.map(skillToName),
162
    optionalSkills: savedOptionalSkills.map(skillToName),
163
    useAsEducationRequirement: experienceCommunity.is_education_requirement,
164
    title: experienceCommunity.title,
165
    group: experienceCommunity.group,
166
    project: experienceCommunity.project,
167
    startDate: toInputDateString(experienceCommunity.start_date),
168
    isActive: experienceCommunity.is_active,
169
    endDate: experienceCommunity.end_date
170
      ? toInputDateString(experienceCommunity.end_date)
171
      : "",
172
  };
173
};
174
175
/* eslint-disable @typescript-eslint/camelcase */
176
const formValuesToData = (
177
  formValues: CommunityExperienceFormValues,
178
  originalExperience: ExperienceCommunity,
179
  locale: Locales,
180
  skills: Skill[],
181
): CommunityExperienceSubmitData => {
182
  const nameToSkill = (name: string): Skill | null =>
183
    matchValueToModel(locale, "name", name, skills);
184
  return {
185
    experienceCommunity: {
186
      ...originalExperience,
187
      title: formValues.title,
188
      group: formValues.group,
189
      project: formValues.project,
190
      start_date: fromInputDateString(formValues.startDate),
191
      is_active: formValues.isActive,
192
      end_date: formValues.endDate
193
        ? fromInputDateString(formValues.endDate)
194
        : null,
195
      is_education_requirement: formValues.useAsEducationRequirement,
196
    },
197
    savedRequiredSkills: formValues.requiredSkills
198
      .map(nameToSkill)
199
      .filter(notEmpty),
200
    savedOptionalSkills: formValues.optionalSkills
201
      .map(nameToSkill)
202
      .filter(notEmpty),
203
  };
204
};
205
206
const newCommunityExperience = (
207
  experienceableId: number,
208
  experienceableType: ExperienceCommunity["experienceable_type"],
209
): ExperienceCommunity => ({
210
  id: 0,
211
  title: "",
212
  group: "",
213
  project: "",
214
  is_active: false,
215
  start_date: new Date(),
216
  end_date: null,
217
  is_education_requirement: false,
218
  experienceable_id: experienceableId,
219
  experienceable_type: experienceableType,
220
  type: "experience_community",
221
});
222
/* eslint-enable @typescript-eslint/camelcase */
223
224
export const CommunityExperienceModal: React.FC<CommunityExperienceModalProps> = ({
225
  modalId,
226
  experienceCommunity,
227
  jobId,
228
  jobClassification,
229
  jobEducationRequirements,
230
  requiredSkills,
231
  savedRequiredSkills,
232
  optionalSkills,
233
  savedOptionalSkills,
234
  experienceableId,
235
  experienceableType,
236
  parentElement,
237
  visible,
238
  onModalCancel,
239
  onModalConfirm,
240
}) => {
241
  const intl = useIntl();
242
  const locale = getLocale(intl.locale);
243
244
  const originalExperience =
245
    experienceCommunity ??
246
    newCommunityExperience(experienceableId, experienceableType);
247
248
  const skillToName = (skill: Skill): string =>
249
    localizeFieldNonNull(locale, skill, "name");
250
251
  const initialFormValues = dataToFormValues(
252
    {
253
      experienceCommunity: originalExperience,
254
      savedRequiredSkills,
255
      savedOptionalSkills,
256
    },
257
    locale,
258
  );
259
260
  const validationSchema = Yup.object().shape({
261
    ...skillValidationShape,
262
    ...educationValidationShape,
263
    ...validationShape(intl),
264
  });
265
266
  const detailsSubform = (
267
    <div data-c-container="medium">
268
      <div data-c-grid="gutter(all, 1) middle">
269
        <FastField
270
          id="community-title"
271
          name="title"
272
          type="text"
273
          grid="base(1of1)"
274
          component={TextInput}
275
          required
276
          label={intl.formatMessage(messages.titleLabel)}
277
          placeholder={intl.formatMessage(messages.titlePlaceholder)}
278
        />
279
        <FastField
280
          id="community-group"
281
          type="text"
282
          name="group"
283
          component={TextInput}
284
          required
285
          grid="tl(1of2)"
286
          label={intl.formatMessage(messages.groupLabel)}
287
          placeholder={intl.formatMessage(messages.groupPlaceholder)}
288
        />
289
        <FastField
290
          id="community-project"
291
          type="text"
292
          name="project"
293
          component={TextInput}
294
          required
295
          grid="tl(1of2)"
296
          label={intl.formatMessage(messages.projectLabel)}
297
          placeholder={intl.formatMessage(messages.projectPlaceholder)}
298
        />
299
        <FastField
300
          id="community-startDate"
301
          name="startDate"
302
          component={DateInput}
303
          required
304
          grid="base(1of1)"
305
          label={intl.formatMessage(messages.startDateLabel)}
306
          placeholder={intl.formatMessage(messages.datePlaceholder)}
307
        />
308
        <Field
309
          id="community-isActive"
310
          name="isActive"
311
          component={CheckboxInput}
312
          grid="tl(1of2)"
313
          label={intl.formatMessage(messages.isActiveLabel)}
314
        />
315
        <Field
316
          id="community-endDate"
317
          name="endDate"
318
          component={DateInput}
319
          grid="base(1of2)"
320
          label={intl.formatMessage(messages.endDateLabel)}
321
          placeholder={intl.formatMessage(messages.datePlaceholder)}
322
        />
323
      </div>
324
    </div>
325
  );
326
327
  return (
328
    <Modal
329
      id={modalId}
330
      parentElement={parentElement}
331
      visible={visible}
332
      onModalCancel={onModalCancel}
333
      onModalConfirm={onModalCancel}
334
      className="application-experience-dialog"
335
    >
336
      <ExperienceModalHeader
337
        title={intl.formatMessage(messages.modalTitle)}
338
        iconClass="fa-people-carry"
339
      />
340
      <Formik
341
        enableReinitialize
342
        initialValues={initialFormValues}
343
        onSubmit={async (values, actions): Promise<void> => {
344
          await onModalConfirm(
345
            formValuesToData(values, originalExperience, locale, [
346
              ...requiredSkills,
347
              ...optionalSkills,
348
            ]),
349
          );
350
          actions.setSubmitting(false);
351
          actions.resetForm();
352
        }}
353
        validationSchema={validationSchema}
354
      >
355
        {(formikProps): React.ReactElement => (
356
          <Form>
357
            <Modal.Body>
358
              <ExperienceDetailsIntro
359
                description={intl.formatMessage(messages.modalDescription)}
360
              />
361
              {detailsSubform}
362
              <SkillSubform
363
                keyPrefix="community"
364
                jobId={jobId}
365
                jobRequiredSkills={requiredSkills.map(skillToName)}
366
                jobOptionalSkills={optionalSkills.map(skillToName)}
367
              />
368
              <EducationSubform
369
                keyPrefix="community"
370
                jobClassification={jobClassification}
371
                jobEducationRequirements={jobEducationRequirements}
372
              />
373
            </Modal.Body>
374
            <ExperienceModalFooter buttonsDisabled={formikProps.isSubmitting} />
375
          </Form>
376
        )}
377
      </Formik>
378
    </Modal>
379
  );
380
};
381
382
export default CommunityExperienceModal;
383