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

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

Complexity

Total Complexity 4
Complexity/F 0

Size

Lines of Code 375
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 304
mnd 4
bc 4
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, Field, 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 CheckboxInput from "../../Form/CheckboxInput";
13
import { validationMessages } from "../../Form/Messages";
14
import SkillSubform, {
15
  SkillFormValues,
16
  validationShape as skillValidationShape,
17
} from "./SkillSubform";
18
import { Skill, ExperienceCommunity } from "../../../models/types";
19
import {
20
  ExperienceModalHeader,
21
  ExperienceDetailsIntro,
22
  ExperienceModalFooter,
23
} from "./ExperienceModalCommon";
24
import Modal from "../../Modal";
25
import DateInput from "../../Form/DateInput";
26
import { toInputDateString, fromInputDateString } from "../../../helpers/dates";
27
import {
28
  Locales,
29
  localizeFieldNonNull,
30
  getLocale,
31
  matchValueToModel,
32
} from "../../../helpers/localize";
33
import { notEmpty } from "../../../helpers/queries";
34
35
interface CommunityExperienceModalProps {
36
  modalId: string;
37
  experienceCommunity: ExperienceCommunity | null;
38
  jobId: number;
39
  requiredSkills: Skill[];
40
  savedRequiredSkills: Skill[];
41
  optionalSkills: Skill[];
42
  savedOptionalSkills: Skill[];
43
  experienceRequirments: EducationSubformProps;
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
const messages = defineMessages({
53
  modalTitle: {
54
    id: "communityExperienceModal.modalTitle",
55
    defaultMessage: "Add Community Experience",
56
  },
57
  modalDescription: {
58
    id: "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: "communityExperienceModal.titleLabel",
64
    defaultMessage: "My Role / Job Title",
65
  },
66
  titlePlaceholder: {
67
    id: "communityExperienceModal.titlePlaceholder",
68
    defaultMessage: "e.g. Front-end Development",
69
  },
70
  groupLabel: {
71
    id: "communityExperienceModal.groupLabel",
72
    defaultMessage: "Group / Organization / Community",
73
  },
74
  groupPlaceholder: {
75
    id: "communityExperienceModal.groupPlaceholder",
76
    defaultMessage: "e.g. Government of Canada",
77
  },
78
  projectLabel: {
79
    id: "communityExperienceModal.projectLabel",
80
    defaultMessage: "Project / Product Name",
81
  },
82
  projectPlaceholder: {
83
    id: "communityExperienceModal.projectPlaceholder",
84
    defaultMessage: "e.g. Talent Cloud",
85
  },
86
  startDateLabel: {
87
    id: "communityExperienceModal.startDateLabel",
88
    defaultMessage: "Select a Start Date",
89
  },
90
  datePlaceholder: {
91
    id: "communityExperienceModal.datePlaceholder",
92
    defaultMessage: "yyyy-mm-dd",
93
  },
94
  isActiveLabel: {
95
    id: "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: "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
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
});
221
/* eslint-enable @typescript-eslint/camelcase */
222
223
export const CommunityExperienceModal: React.FC<CommunityExperienceModalProps> = ({
224
  modalId,
225
  experienceCommunity,
226
  jobId,
227
  requiredSkills,
228
  savedRequiredSkills,
229
  optionalSkills,
230
  savedOptionalSkills,
231
  experienceRequirments,
232
  experienceableId,
233
  experienceableType,
234
  parentElement,
235
  visible,
236
  onModalCancel,
237
  onModalConfirm,
238
}) => {
239
  const intl = useIntl();
240
  const locale = getLocale(intl.locale);
241
242
  const originalExperience =
243
    experienceCommunity ??
244
    newCommunityExperience(experienceableId, experienceableType);
245
246
  const skillToName = (skill: Skill): string =>
247
    localizeFieldNonNull(locale, skill, "name");
248
249
  const initialFormValues = dataToFormValues(
250
    {
251
      experienceCommunity: originalExperience,
252
      savedRequiredSkills,
253
      savedOptionalSkills,
254
    },
255
    locale,
256
  );
257
258
  const validationSchema = Yup.object().shape({
259
    ...skillValidationShape,
260
    ...educationValidationShape,
261
    ...validationShape(intl),
262
  });
263
264
  const detailsSubform = (
265
    <div data-c-container="medium">
266
      <div data-c-grid="gutter(all, 1) middle">
267
        <FastField
268
          id="title"
269
          name="title"
270
          type="text"
271
          grid="base(1of1)"
272
          component={TextInput}
273
          required
274
          label={intl.formatMessage(messages.titleLabel)}
275
          placeholder={intl.formatMessage(messages.titlePlaceholder)}
276
        />
277
        <FastField
278
          id="group"
279
          type="text"
280
          name="group"
281
          component={TextInput}
282
          required
283
          grid="tl(1of2)"
284
          label={intl.formatMessage(messages.groupLabel)}
285
          placeholder={intl.formatMessage(messages.groupPlaceholder)}
286
        />
287
        <FastField
288
          id="project"
289
          type="text"
290
          name="project"
291
          component={TextInput}
292
          required
293
          grid="tl(1of2)"
294
          label={intl.formatMessage(messages.projectLabel)}
295
          placeholder={intl.formatMessage(messages.projectPlaceholder)}
296
        />
297
        <FastField
298
          id="startDate"
299
          name="startDate"
300
          component={DateInput}
301
          required
302
          grid="base(1of1)"
303
          label={intl.formatMessage(messages.startDateLabel)}
304
          placeholder={intl.formatMessage(messages.datePlaceholder)}
305
        />
306
        <Field
307
          id="isActive"
308
          name="isActive"
309
          component={CheckboxInput}
310
          grid="tl(1of2)"
311
          label={intl.formatMessage(messages.isActiveLabel)}
312
        />
313
        <Field
314
          id="endDate"
315
          name="endDate"
316
          component={DateInput}
317
          grid="base(1of2)"
318
          label={intl.formatMessage(messages.endDateLabel)}
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-people-carry"
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 CommunityExperienceModal;
375