Passed
Push — dev ( eb6aa7...8074c8 )
by
unknown
06:10
created

Experience.tsx ➔ modalButtonProps   A

Complexity

Conditions 1

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 24
dl 0
loc 29
rs 9.304
c 0
b 0
f 0
cc 1
1
/* eslint-disable camelcase */
2
import React, { useState, useEffect } from "react";
3
import { FormattedMessage, useIntl, IntlShape } from "react-intl";
4
import {
5
  Skill,
6
  ExperienceEducation,
7
  ExperienceWork,
8
  ExperienceCommunity,
9
  Experience,
10
  ExperiencePersonal,
11
  ExperienceAward,
12
  ExperienceSkill,
13
  Criteria,
14
} from "../../../models/types";
15
import { localizeFieldNonNull, getLocale } from "../../../helpers/localize";
16
import { SkillTypeId, CriteriaTypeId } from "../../../models/lookupConstants";
17
import EducationExperienceModal, {
18
  FormEducationStatus,
19
  FormEducationType,
20
  messages as educationMessages,
21
} from "../ExperienceModals/EducationExperienceModal";
22
23
import WorkExperienceModal, {
24
  messages as workMessages,
25
} from "../ExperienceModals/WorkExperienceModal";
26
import CommunityExperienceModal, {
27
  messages as communityMessages,
28
} from "../ExperienceModals/CommunityExperienceModal";
29
import PersonalExperienceModal, {
30
  messages as personalMessages,
31
} from "../ExperienceModals/PersonalExperienceModal";
32
import AwardExperienceModal, {
33
  messages as awardMessages,
34
  FormAwardRecipientType,
35
  FormAwardRecognitionType,
36
} from "../ExperienceModals/AwardExperienceModal";
37
import { ExperienceEducationAccordion } from "../ExperienceAccordions/ExperienceEducationAccordion";
38
import { ExperienceWorkAccordion } from "../ExperienceAccordions/ExperienceWorkAccordion";
39
import { ExperienceCommunityAccordion } from "../ExperienceAccordions/ExperienceCommunityAccordion";
40
import { ExperiencePersonalAccordion } from "../ExperienceAccordions/ExperiencePersonalAccordion";
41
import { ExperienceAwardAccordion } from "../ExperienceAccordions/ExperienceAwardAccordion";
42
import {
43
  getSkillOfCriteria,
44
  getSkillsOfExperience,
45
  getDisconnectedRequiredSkills,
46
} from "../helpers";
47
import { navigationMessages, experienceMessages } from "../applicationMessages";
48
import { notEmpty, removeDuplicatesById } from "../../../helpers/queries";
49
import { focusOnElement } from "../../../helpers/forms";
50
import { ExperienceSubmitData } from "../ExperienceModals/ExperienceModalCommon";
51
52
export function modalButtonProps(
53
  intl: IntlShape,
54
): { [key: string]: { id: Experience["type"]; title: string; icon: string } } {
55
  return {
56
    education: {
57
      id: "experience_education",
58
      title: intl.formatMessage(educationMessages.modalTitle),
59
      icon: "fas fa-book",
60
    },
61
    work: {
62
      id: "experience_work",
63
      title: intl.formatMessage(workMessages.modalTitle),
64
      icon: "fas fa-briefcase",
65
    },
66
    community: {
67
      id: "experience_community",
68
      title: intl.formatMessage(communityMessages.modalTitle),
69
      icon: "fas fa-people-carry",
70
    },
71
    personal: {
72
      id: "experience_personal",
73
      title: intl.formatMessage(personalMessages.modalTitle),
74
      icon: "fas fa-mountain",
75
    },
76
    award: {
77
      id: "experience_award",
78
      title: intl.formatMessage(awardMessages.modalTitle),
79
      icon: "fas fa-trophy",
80
    },
81
  };
82
}
83
84
export const ModalButton: React.FunctionComponent<{
85
  id: Experience["type"];
86
  title: string;
87
  icon: string;
88
  openModal: (id: Experience["type"]) => void;
89
}> = ({ id, title, icon, openModal }) => {
90
  return (
91
    <div key={id} data-c-grid-item="base(1of2) tp(1of3) tl(1of5)">
92
      <button
93
        className="application-experience-trigger"
94
        data-c-card
95
        data-c-background="c1(100)"
96
        data-c-radius="rounded"
97
        title={title}
98
        data-c-dialog-id={id}
99
        data-c-dialog-action="open"
100
        type="button"
101
        onClick={(): void => openModal(id)}
102
      >
103
        <i className={icon} aria-hidden="true" />
104
        <span data-c-font-size="regular" data-c-font-weight="bold">
105
          {title}
106
        </span>
107
      </button>
108
    </div>
109
  );
110
};
111
112
const applicationExperienceAccordion = (
113
  experience: Experience,
114
  irrelevantSkillCount: number,
115
  relevantSkills: ExperienceSkill[],
116
  skills: Skill[],
117
  handleEdit: () => void,
118
  handleDelete: () => Promise<void>,
119
): React.ReactElement | null => {
120
  switch (experience.type) {
121
    case "experience_education":
122
      return (
123
        <ExperienceEducationAccordion
124
          key={`${experience.id}-${experience.type}`}
125
          experience={experience}
126
          handleDelete={handleDelete}
127
          handleEdit={handleEdit}
128
          irrelevantSkillCount={irrelevantSkillCount}
129
          relevantSkills={relevantSkills}
130
          skills={skills}
131
          showButtons
132
          showSkillDetails
133
        />
134
      );
135
    case "experience_work":
136
      return (
137
        <ExperienceWorkAccordion
138
          key={`${experience.id}-${experience.type}`}
139
          experience={experience}
140
          handleDelete={handleDelete}
141
          handleEdit={handleEdit}
142
          irrelevantSkillCount={irrelevantSkillCount}
143
          relevantSkills={relevantSkills}
144
          skills={skills}
145
          showButtons
146
          showSkillDetails
147
        />
148
      );
149
    case "experience_community":
150
      return (
151
        <ExperienceCommunityAccordion
152
          key={`${experience.id}-${experience.type}`}
153
          experience={experience}
154
          handleDelete={handleDelete}
155
          handleEdit={handleEdit}
156
          irrelevantSkillCount={irrelevantSkillCount}
157
          relevantSkills={relevantSkills}
158
          skills={skills}
159
          showButtons
160
          showSkillDetails
161
        />
162
      );
163
    case "experience_personal":
164
      return (
165
        <ExperiencePersonalAccordion
166
          key={`${experience.id}-${experience.type}`}
167
          experience={experience}
168
          handleEdit={handleEdit}
169
          handleDelete={handleDelete}
170
          irrelevantSkillCount={irrelevantSkillCount}
171
          relevantSkills={relevantSkills}
172
          skills={skills}
173
          showButtons
174
          showSkillDetails
175
        />
176
      );
177
    case "experience_award":
178
      return (
179
        <ExperienceAwardAccordion
180
          key={`${experience.id}-${experience.type}`}
181
          experience={experience}
182
          handleDelete={handleDelete}
183
          handleEdit={handleEdit}
184
          irrelevantSkillCount={irrelevantSkillCount}
185
          relevantSkills={relevantSkills}
186
          skills={skills}
187
          showButtons
188
          showSkillDetails
189
        />
190
      );
191
    default:
192
      return null;
193
  }
194
};
195
196
interface ExperienceProps {
197
  assetSkills: Skill[];
198
  disconnectedRequiredSkills: Skill[];
199
  educationStatuses: FormEducationStatus[];
200
  educationTypes: FormEducationType[];
201
  hasError?: boolean;
202
  essentialSkills: Skill[];
203
  experiences: Experience[];
204
  experienceSkills: ExperienceSkill[];
205
  jobId: number;
206
  jobEducationRequirements: string | null;
207
  recipientTypes: FormAwardRecipientType[];
208
  recognitionTypes: FormAwardRecognitionType[];
209
  classificationEducationRequirements: string | null;
210
  skills: Skill[];
211
  handleDeleteExperience: (
212
    id: number,
213
    type: Experience["type"],
214
  ) => Promise<void>;
215
  handleSubmitExperience: (
216
    data: ExperienceSubmitData<Experience>,
217
  ) => Promise<void>;
218
}
219
220
export const MyExperience: React.FunctionComponent<ExperienceProps> = ({
221
  assetSkills,
222
  disconnectedRequiredSkills,
223
  hasError,
224
  educationStatuses,
225
  educationTypes,
226
  essentialSkills,
227
  experiences,
228
  experienceSkills,
229
  jobId,
230
  jobEducationRequirements,
231
  classificationEducationRequirements,
232
  recipientTypes,
233
  recognitionTypes,
234
  skills,
235
  handleSubmitExperience,
236
  handleDeleteExperience,
237
}) => {
238
  const intl = useIntl();
239
  const locale = getLocale(intl.locale);
240
241
  const [experienceData, setExperienceData] = useState<
242
    | (Experience & {
243
        savedOptionalSkills: Skill[];
244
        savedRequiredSkills: Skill[];
245
      })
246
    | null
247
  >(null);
248
249
  const [isModalVisible, setIsModalVisible] = useState<{
250
    id: Experience["type"] | "";
251
    visible: boolean;
252
  }>({
253
    id: "",
254
    visible: false,
255
  });
256
257
  const openModal = (id: Experience["type"]): void => {
258
    setIsModalVisible({ id, visible: true });
259
  };
260
261
  const closeModal = (): void => {
262
    setExperienceData(null);
263
    setIsModalVisible({ id: "", visible: false });
264
  };
265
266
  const submitExperience = (data) =>
267
    handleSubmitExperience(data).then(closeModal);
268
269
  const editExperience = (
270
    experience: Experience,
271
    savedOptionalSkills: Skill[],
272
    savedRequiredSkills: Skill[],
273
  ): void => {
274
    setExperienceData({
275
      ...experience,
276
      savedOptionalSkills,
277
      savedRequiredSkills,
278
    });
279
    setIsModalVisible({ id: experience.type, visible: true });
280
  };
281
282
  const deleteExperience = (experience: Experience): Promise<void> =>
283
    handleDeleteExperience(experience.id, experience.type).then(closeModal);
284
285
  const softSkills = removeDuplicatesById(
286
    [...assetSkills, ...essentialSkills].filter(
287
      (skill) => skill.skill_type_id === SkillTypeId.Soft,
288
    ),
289
  );
290
291
  const modalButtons = modalButtonProps(intl);
292
  const modalRoot = document.getElementById("modal-root");
293
294
  return (
295
    <>
296
      <div data-c-container="medium">
297
        <h2 data-c-heading="h2" data-c-margin="top(3) bottom(1)">
298
          {intl.formatMessage(experienceMessages.heading)}
299
        </h2>
300
        <p data-c-margin="bottom(1)">
301
          <FormattedMessage
302
            id="application.experience.preamble"
303
            defaultMessage="Use the buttons below to add experiences you want to share with the manager. Experiences you have added in the past also appear below, and you can edit them to link them to skills required for this job when necessary."
304
            description="First section of text on the experience step of the Application Timeline."
305
          />
306
        </p>
307
308
        <div data-c-grid="gutter(all, 1)">
309
          {essentialSkills.length > 0 && (
310
            <div data-c-grid-item="tl(1of2)">
311
              <p data-c-margin="bottom(.5)">
312
                <FormattedMessage
313
                  id="application.experience.essentialSkillsListIntro"
314
                  description="Text before the list of essential skills on the experience step of the Application Timeline."
315
                  defaultMessage="This job <span>requires</span> the following skills:"
316
                  values={{
317
                    span: (chunks): React.ReactElement => (
318
                      <span data-c-font-weight="bold" data-c-color="c2">
319
                        {chunks}
320
                      </span>
321
                    ),
322
                  }}
323
                />
324
              </p>
325
              <ul data-c-margin="bottom(1)">
326
                {essentialSkills.map((skill) => (
327
                  <li key={skill.id}>
328
                    {localizeFieldNonNull(locale, skill, "name")}
329
                  </li>
330
                ))}
331
              </ul>
332
            </div>
333
          )}
334
          {assetSkills.length > 0 && (
335
            <div data-c-grid-item="tl(1of2)">
336
              <p data-c-margin="bottom(.5)">
337
                <FormattedMessage
338
                  id="application.experience.assetSkillsListIntro"
339
                  defaultMessage="These skills are beneficial, but not required:"
340
                  description="Text before the list of asset skills on the experience step of the Application Timeline."
341
                />
342
              </p>
343
              <ul data-c-margin="bottom(1)">
344
                {assetSkills.map((skill) => (
345
                  <li key={skill.id}>
346
                    {localizeFieldNonNull(locale, skill, "name")}
347
                  </li>
348
                ))}
349
              </ul>
350
            </div>
351
          )}
352
        </div>
353
        <p data-c-color="gray" data-c-margin="bottom(2)">
354
          <FormattedMessage
355
            id="application.experience.softSkillsList"
356
            defaultMessage="Don't forget, {skill} will be evaluated later in the hiring process."
357
            description="List of soft skills that will be evaluated later."
358
            values={{
359
              skill: (
360
                <>
361
                  {softSkills.map((skill, index) => {
362
                    const and = " and ";
363
                    const lastElement = index === softSkills.length - 1;
364
                    return (
365
                      <React.Fragment key={skill.id}>
366
                        {lastElement && softSkills.length > 1 && and}
367
                        <span key={skill.id} data-c-font-weight="bold">
368
                          {localizeFieldNonNull(locale, skill, "name")}
369
                        </span>
370
                        {!lastElement && softSkills.length > 2 && ", "}
371
                      </React.Fragment>
372
                    );
373
                  })}
374
                </>
375
              ),
376
            }}
377
          />
378
        </p>
379
        {hasError && (
380
          <div
381
            // This alert message needs the tabindex attribute for it to be focusable.
382
            // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
383
            tabIndex={0}
384
            data-c-alert="error"
385
            data-c-radius="rounded"
386
            role="alert"
387
            data-c-margin="bottom(1)"
388
            id="experience-step-form-error"
389
            style={{ display: "block" }}
390
          >
391
            <div data-c-padding="all(.5)">
392
              <p data-c-alignment="base(left)">
393
                <FormattedMessage
394
                  id="application.experience.errorMessage"
395
                  defaultMessage="To continue, please connect the following required skill(s) to an experience:"
396
                  description="Error message displayed to user when experience step validation fails"
397
                />
398
                {` `}
399
                {disconnectedRequiredSkills.map((skill, index) => {
400
                  const and = " and ";
401
                  const lastElement =
402
                    index === disconnectedRequiredSkills.length - 1;
403
                  return (
404
                    <React.Fragment key={skill.id}>
405
                      {lastElement &&
406
                        disconnectedRequiredSkills.length > 1 &&
407
                        and}
408
                      <span key={skill.id} data-c-font-weight="bold">
409
                        {localizeFieldNonNull(locale, skill, "name")}
410
                      </span>
411
                      {!lastElement &&
412
                        disconnectedRequiredSkills.length > 2 &&
413
                        ", "}
414
                    </React.Fragment>
415
                  );
416
                })}
417
              </p>
418
            </div>
419
          </div>
420
        )}
421
        {/* Experience Modal Buttons */}
422
        <div data-c-grid="gutter(all, 1)">
423
          {Object.values(modalButtons).map((buttonProps) => {
424
            const { id, title, icon } = buttonProps;
425
            return (
426
              <ModalButton
427
                key={id}
428
                id={id}
429
                title={title}
430
                icon={icon}
431
                openModal={openModal}
432
              />
433
            );
434
          })}
435
        </div>
436
        {/* Experience Accordion List */}
437
        {experiences && experiences.length > 0 ? (
438
          <div className="experience-list" data-c-margin="top(2)">
439
            <div data-c-accordion-group>
440
              {experiences.map((experience) => {
441
                const savedOptionalSkills = getSkillsOfExperience(
442
                  experienceSkills,
443
                  experience,
444
                  assetSkills,
445
                );
446
                const savedRequiredSkills = getSkillsOfExperience(
447
                  experienceSkills,
448
                  experience,
449
                  essentialSkills,
450
                );
451
                const relevantSkills: ExperienceSkill[] = savedRequiredSkills
452
                  .map((skill) => {
453
                    return experienceSkills.find(
454
                      ({ experience_id, experience_type, skill_id }) =>
455
                        experience_id === experience.id &&
456
                        skill_id === skill.id &&
457
                        experience_type === experience.type,
458
                    );
459
                  })
460
                  .filter(notEmpty);
461
462
                const handleEdit = () =>
463
                  editExperience(
464
                    experience,
465
                    savedOptionalSkills,
466
                    savedRequiredSkills,
467
                  );
468
                const handleDelete = () => deleteExperience(experience);
469
470
                const errorAccordion = () => (
471
                  <div
472
                    data-c-background="gray(10)"
473
                    data-c-radius="rounded"
474
                    data-c-border="all(thin, solid, gray)"
475
                    data-c-margin="top(1)"
476
                    data-c-padding="all(1)"
477
                  >
478
                    <div data-c-align="base(center)">
479
                      <p data-c-color="stop">
480
                        {intl.formatMessage(
481
                          experienceMessages.errorRenderingExperience,
482
                        )}
483
                      </p>
484
                    </div>
485
                  </div>
486
                );
487
488
                // Number of skills attached to Experience but are not part of the jobs skill criteria.
489
                const irrelevantSkillCount =
490
                  experienceSkills.filter(
491
                    (experienceSkill) =>
492
                      experienceSkill.experience_id === experience.id &&
493
                      experienceSkill.experience_type === experience.type,
494
                  ).length -
495
                  (savedOptionalSkills.length + savedRequiredSkills.length);
496
497
                return (
498
                  applicationExperienceAccordion(
499
                    experience,
500
                    irrelevantSkillCount,
501
                    relevantSkills,
502
                    skills,
503
                    handleEdit,
504
                    handleDelete,
505
                  ) ?? errorAccordion()
506
                );
507
              })}
508
            </div>
509
          </div>
510
        ) : (
511
          <div
512
            data-c-background="gray(10)"
513
            data-c-radius="rounded"
514
            data-c-border="all(thin, solid, gray)"
515
            data-c-margin="top(2)"
516
            data-c-padding="all(1)"
517
          >
518
            <div data-c-align="base(center)">
519
              <p data-c-color="gray">
520
                <FormattedMessage
521
                  id="application.experience.noExperiences"
522
                  defaultMessage="Looks like you don't have any experience added yet. Use the buttons above to add experience. Don't forget that experience will always be saved to your profile so that you can use it on future applications!"
523
                  description="Message displayed when application has no experiences."
524
                />
525
              </p>
526
            </div>
527
          </div>
528
        )}
529
        {disconnectedRequiredSkills && disconnectedRequiredSkills.length > 0 && (
530
          <p data-c-color="stop" data-c-margin="top(2)">
531
            <FormattedMessage
532
              id="application.experience.unconnectedSkills"
533
              defaultMessage="The following required skill(s) are not connected to your experience:"
534
              description="Message showing list of required skills that are not connected to a experience."
535
            />{" "}
536
            {disconnectedRequiredSkills.map((skill) => (
537
              <React.Fragment key={skill.id}>
538
                <span
539
                  data-c-tag="stop"
540
                  data-c-radius="pill"
541
                  data-c-font-size="small"
542
                >
543
                  {localizeFieldNonNull(locale, skill, "name")}
544
                </span>{" "}
545
              </React.Fragment>
546
            ))}
547
          </p>
548
        )}
549
      </div>
550
551
      <div data-c-dialog-overlay={isModalVisible.visible ? "active" : ""} />
552
      <EducationExperienceModal
553
        educationStatuses={educationStatuses}
554
        educationTypes={educationTypes}
555
        experienceEducation={experienceData as ExperienceEducation}
556
        experienceableId={experienceData?.experienceable_id ?? 0}
557
        experienceableType={
558
          experienceData?.experienceable_type ?? "application"
559
        }
560
        jobId={jobId}
561
        classificationEducationRequirements={
562
          classificationEducationRequirements
563
        }
564
        jobEducationRequirements={jobEducationRequirements}
565
        modalId={modalButtons.education.id}
566
        onModalCancel={closeModal}
567
        onModalConfirm={submitExperience}
568
        optionalSkills={assetSkills}
569
        parentElement={modalRoot}
570
        requiredSkills={essentialSkills}
571
        savedOptionalSkills={experienceData?.savedOptionalSkills ?? []}
572
        savedRequiredSkills={experienceData?.savedRequiredSkills ?? []}
573
        visible={
574
          isModalVisible.visible &&
575
          isModalVisible.id === modalButtons.education.id
576
        }
577
      />
578
      <WorkExperienceModal
579
        experienceWork={experienceData as ExperienceWork}
580
        experienceableId={experienceData?.experienceable_id ?? 0}
581
        experienceableType={
582
          experienceData?.experienceable_type ?? "application"
583
        }
584
        jobId={jobId}
585
        classificationEducationRequirements={
586
          classificationEducationRequirements
587
        }
588
        jobEducationRequirements={jobEducationRequirements}
589
        modalId={modalButtons.work.id}
590
        onModalCancel={closeModal}
591
        onModalConfirm={submitExperience}
592
        optionalSkills={assetSkills}
593
        parentElement={modalRoot}
594
        requiredSkills={essentialSkills}
595
        savedOptionalSkills={experienceData?.savedOptionalSkills ?? []}
596
        savedRequiredSkills={experienceData?.savedRequiredSkills ?? []}
597
        visible={
598
          isModalVisible.visible && isModalVisible.id === modalButtons.work.id
599
        }
600
      />
601
      <CommunityExperienceModal
602
        experienceCommunity={experienceData as ExperienceCommunity}
603
        experienceableId={experienceData?.experienceable_id ?? 0}
604
        experienceableType={
605
          experienceData?.experienceable_type ?? "application"
606
        }
607
        jobId={jobId}
608
        classificationEducationRequirements={
609
          classificationEducationRequirements
610
        }
611
        jobEducationRequirements={jobEducationRequirements}
612
        modalId={modalButtons.community.id}
613
        onModalCancel={closeModal}
614
        onModalConfirm={submitExperience}
615
        optionalSkills={assetSkills}
616
        parentElement={modalRoot}
617
        requiredSkills={essentialSkills}
618
        savedOptionalSkills={experienceData?.savedOptionalSkills ?? []}
619
        savedRequiredSkills={experienceData?.savedRequiredSkills ?? []}
620
        visible={
621
          isModalVisible.visible &&
622
          isModalVisible.id === modalButtons.community.id
623
        }
624
      />
625
      <PersonalExperienceModal
626
        experiencePersonal={experienceData as ExperiencePersonal}
627
        experienceableId={experienceData?.experienceable_id ?? 0}
628
        experienceableType={
629
          experienceData?.experienceable_type ?? "application"
630
        }
631
        jobId={jobId}
632
        classificationEducationRequirements={
633
          classificationEducationRequirements
634
        }
635
        jobEducationRequirements={jobEducationRequirements}
636
        modalId={modalButtons.personal.id}
637
        onModalCancel={closeModal}
638
        onModalConfirm={submitExperience}
639
        optionalSkills={assetSkills}
640
        parentElement={modalRoot}
641
        requiredSkills={essentialSkills}
642
        savedOptionalSkills={experienceData?.savedOptionalSkills ?? []}
643
        savedRequiredSkills={experienceData?.savedRequiredSkills ?? []}
644
        visible={
645
          isModalVisible.visible &&
646
          isModalVisible.id === modalButtons.personal.id
647
        }
648
      />
649
      <AwardExperienceModal
650
        experienceAward={experienceData as ExperienceAward}
651
        experienceableId={experienceData?.experienceable_id ?? 0}
652
        experienceableType={
653
          experienceData?.experienceable_type ?? "application"
654
        }
655
        jobId={jobId}
656
        classificationEducationRequirements={
657
          classificationEducationRequirements
658
        }
659
        jobEducationRequirements={jobEducationRequirements}
660
        modalId={modalButtons.award.id}
661
        onModalCancel={closeModal}
662
        onModalConfirm={submitExperience}
663
        optionalSkills={assetSkills}
664
        parentElement={modalRoot}
665
        recipientTypes={recipientTypes}
666
        recognitionTypes={recognitionTypes}
667
        requiredSkills={essentialSkills}
668
        savedOptionalSkills={experienceData?.savedOptionalSkills ?? []}
669
        savedRequiredSkills={experienceData?.savedRequiredSkills ?? []}
670
        visible={
671
          isModalVisible.visible && isModalVisible.id === modalButtons.award.id
672
        }
673
      />
674
    </>
675
  );
676
};
677
678
interface ExperienceStepProps {
679
  experiences: Experience[];
680
  educationStatuses: FormEducationStatus[];
681
  educationTypes: FormEducationType[];
682
  experienceSkills: ExperienceSkill[];
683
  criteria: Criteria[];
684
  skills: Skill[];
685
  jobId: number;
686
  jobEducationRequirements: string | null;
687
  recipientTypes: FormAwardRecipientType[];
688
  recognitionTypes: FormAwardRecognitionType[];
689
  classificationEducationRequirements: string | null;
690
  handleDeleteExperience: (
691
    id: number,
692
    type: Experience["type"],
693
  ) => Promise<void>;
694
  handleSubmitExperience: (
695
    data: ExperienceSubmitData<Experience>,
696
  ) => Promise<void>;
697
  handleContinue: () => void;
698
  handleQuit: () => void;
699
  handleReturn: () => void;
700
}
701
702
export const ExperienceStep: React.FunctionComponent<ExperienceStepProps> = ({
703
  experiences,
704
  educationStatuses,
705
  educationTypes,
706
  experienceSkills,
707
  criteria,
708
  skills,
709
  handleSubmitExperience,
710
  handleDeleteExperience,
711
  jobId,
712
  jobEducationRequirements,
713
  classificationEducationRequirements,
714
  recipientTypes,
715
  recognitionTypes,
716
  handleContinue,
717
  handleQuit,
718
  handleReturn,
719
}) => {
720
  const intl = useIntl();
721
  const [hasError, setHasError] = useState(false);
722
723
  const filteredSkills = criteria.reduce(
724
    (result, criterion): { essential: Skill[]; asset: Skill[] } => {
725
      const skillOfCriterion = getSkillOfCriteria(criterion, skills);
726
      if (skillOfCriterion) {
727
        if (criterion.criteria_type_id === CriteriaTypeId.Essential) {
728
          result.essential.push(skillOfCriterion);
729
        }
730
        if (criterion.criteria_type_id === CriteriaTypeId.Asset) {
731
          result.asset.push(skillOfCriterion);
732
        }
733
      }
734
      return result;
735
    },
736
    { essential: [], asset: [] } as { essential: Skill[]; asset: Skill[] },
737
  );
738
739
  const essentialSkills = removeDuplicatesById(filteredSkills.essential);
740
  const assetSkills = removeDuplicatesById(filteredSkills.asset);
741
742
  const disconnectedRequiredSkills = getDisconnectedRequiredSkills(
743
    experiences,
744
    experienceSkills,
745
    essentialSkills,
746
  );
747
748
  // Hack solution for experience step validation error message: focusOnElement is called in the onClick method (line 830) before the element is added to the dom. Therefore, the useEffect hook is needed for the first focus, after hasError triggers re-render.
749
  useEffect(() => {
750
    if (hasError) {
751
      focusOnElement("experience-step-form-error");
752
    }
753
    if (disconnectedRequiredSkills.length === 0) {
754
      setHasError(false);
755
    }
756
  }, [hasError, disconnectedRequiredSkills]);
757
758
  return (
759
    <>
760
      <MyExperience
761
        assetSkills={assetSkills}
762
        disconnectedRequiredSkills={disconnectedRequiredSkills}
763
        hasError={hasError}
764
        experiences={experiences}
765
        educationStatuses={educationStatuses}
766
        educationTypes={educationTypes}
767
        experienceSkills={experienceSkills}
768
        essentialSkills={essentialSkills}
769
        skills={skills}
770
        jobId={jobId}
771
        jobEducationRequirements={jobEducationRequirements}
772
        classificationEducationRequirements={
773
          classificationEducationRequirements
774
        }
775
        recipientTypes={recipientTypes}
776
        recognitionTypes={recognitionTypes}
777
        handleSubmitExperience={handleSubmitExperience}
778
        handleDeleteExperience={handleDeleteExperience}
779
      />
780
      <div data-c-container="medium" data-c-padding="tb(2)">
781
        <hr data-c-hr="thin(c1)" data-c-margin="bottom(2)" />
782
        <div data-c-grid="gutter">
783
          <div
784
            data-c-alignment="base(centre) tp(left)"
785
            data-c-grid-item="tp(1of2)"
786
          >
787
            <button
788
              data-c-button="outline(c2)"
789
              data-c-radius="rounded"
790
              type="button"
791
              onClick={(): void => handleReturn()}
792
            >
793
              {intl.formatMessage(navigationMessages.return)}
794
            </button>
795
          </div>
796
          <div
797
            data-c-alignment="base(centre) tp(right)"
798
            data-c-grid-item="tp(1of2)"
799
          >
800
            <button
801
              data-c-button="outline(c2)"
802
              data-c-radius="rounded"
803
              type="button"
804
              onClick={(): void => handleQuit()}
805
            >
806
              {intl.formatMessage(navigationMessages.quit)}
807
            </button>
808
            <button
809
              data-c-button="solid(c1)"
810
              data-c-radius="rounded"
811
              data-c-margin="left(1)"
812
              type="button"
813
              onClick={(): void => {
814
                // If all required skills have been connected to an experience, then continue.
815
                // Else, show error alert.
816
                if (disconnectedRequiredSkills.length === 0) {
817
                  handleContinue();
818
                } else {
819
                  setHasError(true);
820
                  focusOnElement("experience-step-form-error");
821
                }
822
              }}
823
            >
824
              {intl.formatMessage(navigationMessages.continue)}
825
            </button>
826
          </div>
827
        </div>
828
      </div>
829
    </>
830
  );
831
};
832
833
export default ExperienceStep;
834