Passed
Push — feature/application-review-ui ( a2990f...389138 )
by Chris
05:33
created

resources/assets/js/components/Application/Experience/Experience.tsx   F

Complexity

Total Complexity 93
Complexity/F 0

Size

Lines of Code 816
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 93
eloc 691
mnd 93
bc 93
fnc 0
dl 0
loc 816
rs 1.909
bpm 0
cpm 0
noi 0
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like resources/assets/js/components/Application/Experience/Experience.tsx often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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