Passed
Push — feature/application-review-ui ( afbf92 )
by Chris
06:55
created

resources/assets/js/components/Application/Review/Review.tsx   A

Complexity

Total Complexity 7
Complexity/F 0

Size

Lines of Code 652
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 7
eloc 539
mnd 7
bc 7
fnc 0
dl 0
loc 652
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import React, { useState } from "react";
2
import { useIntl, FormattedMessage, defineMessages } from "react-intl";
3
import {
4
  Application,
5
  User,
6
  Job,
7
  Criteria,
8
  Experience,
9
  Skill,
10
  ExperienceSkill,
11
  JobPosterQuestion,
12
  JobApplicationAnswer,
13
} from "../../../models/types";
14
import { languageRequirementDescription } from "../../../models/localizedConstants";
15
import { LanguageRequirementId } from "../../../models/lookupConstants";
16
import {
17
  basicInfoMessages,
18
  experienceMessages,
19
  fitMessages,
20
} from "../applicationMessages";
21
import ExperienceAwardAccordion from "../Experience/ExperienceAwardAccordion";
22
import ExperienceCommunityAccordion from "../Experience/ExperienceCommunityAccordion";
23
import ExperienceEducationAccordion from "../Experience/ExperienceEducationAccordion";
24
import ExperiencePersonalAccordion from "../Experience/ExperiencePersonalAccordion";
25
import ExperienceWorkAccordion from "../Experience/ExperienceWorkAccordion";
26
import SkillAccordion from "./SkillAccordion";
27
import {
28
  Locales,
29
  localizeFieldNonNull,
30
  getLocale,
31
  localizeField,
32
} from "../../../helpers/localize";
33
import { getSkillOfCriteria, getIrrelevantSkillCount } from "../helpers";
34
import { getSkillLevelName } from "../../../models/jobUtil";
35
36
const messages = defineMessages({
37
  edit: {
38
    id: "application.review.edit",
39
    defaultMessage: "Edit",
40
    description: "Link text for editing a section.",
41
  },
42
  editTitle: {
43
    id: "application.review.editTitle",
44
    defaultMessage: "Edit this section.",
45
    description: "Link title for editing a section.",
46
  },
47
});
48
49
interface ExperienceAccordionProps {
50
  experience: Experience;
51
  experienceSkills: ExperienceSkill[];
52
  irrelevantSkillCount: number;
53
  skills: Skill[];
54
  locale: Locales;
55
}
56
57
const ExperienceAccordion: React.FC<ExperienceAccordionProps> = ({
58
  experience,
59
  experienceSkills,
60
  irrelevantSkillCount,
61
  skills,
62
  locale,
63
}) => {
64
  switch (experience.type) {
65
    case "experience_award":
66
      return (
67
        <ExperienceAwardAccordion
68
          title={experience.title}
69
          recipient={localizeFieldNonNull(
70
            locale,
71
            experience,
72
            "award_recipient_type",
73
          )}
74
          issuer={experience.issued_by}
75
          scope={localizeFieldNonNull(
76
            locale,
77
            experience,
78
            "award_recognition_type",
79
          )}
80
          awardedDate={experience.awarded_date}
81
          relevantSkills={experienceSkills}
82
          skills={skills}
83
          irrelevantSkillCount={irrelevantSkillCount}
84
          isEducationJustification={experience.is_education_requirement}
85
          showSkillDetails
86
          showButtons={false}
87
          handleEdit={(): void => {}}
88
          handleDelete={(): void => {}}
89
        />
90
      );
91
    case "experience_community":
92
      return (
93
        <ExperienceCommunityAccordion
94
          title={experience.title}
95
          group={experience.group}
96
          project={experience.project}
97
          startDate={experience.start_date}
98
          endDate={experience.end_date}
99
          isActive={experience.is_active}
100
          relevantSkills={experienceSkills}
101
          skills={skills}
102
          irrelevantSkillCount={irrelevantSkillCount}
103
          isEducationJustification={experience.is_education_requirement}
104
          showSkillDetails
105
          showButtons={false}
106
          handleEdit={(): void => {}}
107
          handleDelete={(): void => {}}
108
        />
109
      );
110
    case "experience_education":
111
      return (
112
        <ExperienceEducationAccordion
113
          educationType={localizeFieldNonNull(
114
            locale,
115
            experience,
116
            "education_type",
117
          )}
118
          areaOfStudy={experience.area_of_study}
119
          institution={experience.institution}
120
          status={localizeFieldNonNull(locale, experience, "education_status")}
121
          startDate={experience.start_date}
122
          endDate={experience.end_date}
123
          isActive={experience.is_active}
124
          thesisTitle={experience.thesis_title}
125
          relevantSkills={experienceSkills}
126
          skills={skills}
127
          irrelevantSkillCount={irrelevantSkillCount}
128
          isEducationJustification={experience.is_education_requirement}
129
          showSkillDetails
130
          showButtons={false}
131
          handleDelete={(): void => {}}
132
          handleEdit={(): void => {}}
133
        />
134
      );
135
    case "experience_personal":
136
      return (
137
        <ExperiencePersonalAccordion
138
          title={experience.title}
139
          description={experience.description}
140
          isShareable={experience.is_shareable}
141
          startDate={experience.start_date}
142
          endDate={experience.end_date}
143
          isActive={experience.is_active}
144
          relevantSkills={experienceSkills}
145
          skills={skills}
146
          irrelevantSkillCount={irrelevantSkillCount}
147
          isEducationJustification={experience.is_education_requirement}
148
          showSkillDetails
149
          showButtons={false}
150
          handleEdit={(): void => {}}
151
          handleDelete={(): void => {}}
152
        />
153
      );
154
    case "experience_work":
155
      return (
156
        <ExperienceWorkAccordion
157
          title={experience.title}
158
          organization={experience.organization}
159
          group={experience.group}
160
          startDate={experience.start_date}
161
          endDate={experience.end_date}
162
          isActive={experience.is_active}
163
          relevantSkills={experienceSkills}
164
          skills={skills}
165
          irrelevantSkillCount={irrelevantSkillCount}
166
          isEducationJustification={experience.is_education_requirement}
167
          showSkillDetails
168
          showButtons={false}
169
          handleEdit={(): void => {}}
170
          handleDelete={(): void => {}}
171
        />
172
      );
173
    default:
174
      return null;
175
  }
176
};
177
178
type ExperienceView = "experience" | "skills" | "education";
179
180
interface ReviewProps {
181
  application: Application;
182
  criteria: Criteria[];
183
  experiences: Experience[];
184
  experienceSkills: ExperienceSkill[];
185
  job: Job;
186
  jobQuestions: JobPosterQuestion[];
187
  jobApplicationAnswers: JobApplicationAnswer[];
188
  skills: Skill[];
189
  user: User;
190
}
191
192
const Review: React.FC<ReviewProps> = ({
193
  application,
194
  criteria,
195
  experiences,
196
  experienceSkills,
197
  job,
198
  jobQuestions,
199
  jobApplicationAnswers,
200
  skills,
201
  user,
202
}) => {
203
  const intl = useIntl();
204
  const locale = getLocale(intl.locale);
205
  const [experienceView, setExperienceView] = useState<ExperienceView>(
206
    "experience",
207
  );
208
209
  const handleViewClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
210
    const viewType: ExperienceView = e.currentTarget.getAttribute(
211
      "data-experience-view",
212
    ) as ExperienceView;
213
    if (viewType !== null) {
214
      setExperienceView(viewType);
215
    }
216
  };
217
218
  return (
219
    <div data-c-container="medium">
220
      <h2 data-c-heading="h2" data-c-margin="top(3) bottom(1)">
221
        <FormattedMessage
222
          id="application.review.heading"
223
          defaultMessage="Review Your Application"
224
          description="Main page heading for the Review page."
225
        />
226
      </h2>
227
      <p data-c-margin="bottom(1)">
228
        <FormattedMessage
229
          id="application.review.subheadingOne"
230
          defaultMessage="Take one last look at your information before you submit it."
231
          description="First line of the subheading for the Review page."
232
        />
233
      </p>
234
      <p data-c-margin="bottom(1)">
235
        <FormattedMessage
236
          id="application.review.subheadingTwo"
237
          defaultMessage="Make sure everything you've said is as honest and accurate as possible."
238
          description="Second line of the subheading for the Review page."
239
        />
240
      </p>
241
      <p>
242
        <FormattedMessage
243
          id="application.review.subheadingThree"
244
          defaultMessage={`Ask yourself, "If I was a manager, and I knew nothing about the applicant other than this application, would I think they could do a good job?"`}
245
          description="Third line of the subheading for the Review page."
246
        />
247
      </p>
248
      <div data-c-grid="gutter(all, 1) middle" data-c-padding="top(3)">
249
        <div data-c-grid-item="tp(2of3) tl(4of5)">
250
          <h3 data-c-font-size="h3">
251
            {intl.formatMessage(basicInfoMessages.heading)}
252
          </h3>
253
        </div>
254
        <div
255
          data-c-grid-item="tp(1of3) tl(1of5)"
256
          data-c-align="base(center) tp(right)"
257
        >
258
          <a
259
            href="https://talent.test/demo/application-02"
260
            title={intl.formatMessage(messages.editTitle)}
261
            data-c-color="c2"
262
            data-c-font-weight="bold"
263
          >
264
            {intl.formatMessage(messages.edit)}
265
          </a>
266
        </div>
267
      </div>
268
      <hr data-c-hr="thin(gray)" data-c-margin="top(1)" />
269
      <p
270
        data-c-font-weight="bold"
271
        data-c-color="c2"
272
        data-c-margin="top(1) bottom(.5)"
273
      >
274
        {intl.formatMessage(basicInfoMessages.citizenshipLabel)}
275
      </p>
276
      <p>{application.citizenship_declaration_id}</p>
277
      <p
278
        data-c-font-weight="bold"
279
        data-c-color="c2"
280
        data-c-margin="top(1) bottom(.5)"
281
      >
282
        {intl.formatMessage(basicInfoMessages.veteranStatusLabel)}
283
      </p>
284
      <p>{application.veteran_status_id}</p>
285
      <p
286
        data-c-font-weight="bold"
287
        data-c-color="c2"
288
        data-c-margin="top(1) bottom(.5)"
289
      >
290
        {intl.formatMessage(basicInfoMessages.languageRequirementsHeading)}
291
      </p>
292
      <p data-c-margin="bottom(.5)">
293
        {job.language_requirement_id &&
294
          intl.formatMessage(
295
            languageRequirementDescription(job.language_requirement_id),
296
          )}
297
      </p>
298
      {job.language_requirement_id &&
299
        application.language_requirement_confirmed && (
300
          <p data-c-margin="bottom(.5)">
301
            <i
302
              className="fas fa-check"
303
              data-c-color="go"
304
              data-c-margin="right(.25)"
305
            />
306
            {job.language_requirement_id}
307
          </p>
308
        )}
309
      {(job.language_requirement_id ===
310
        LanguageRequirementId.bilingualIntermediate ||
311
        job.language_requirement_id ===
312
          LanguageRequirementId.bilingualAdvanced) &&
313
        application.language_test_confirmed && (
314
          <p>
315
            <i
316
              className="fas fa-check"
317
              data-c-color="go"
318
              data-c-margin="right(.25)"
319
            />
320
            I understand that my second language proficiency will be tested as
321
            part of this process.
322
          </p>
323
        )}
324
      <div data-c-grid="gutter(all, 1) middle" data-c-padding="top(3)">
325
        <div data-c-grid-item="tp(2of3) tl(4of5)">
326
          <h3 data-c-font-size="h3">
327
            {intl.formatMessage(experienceMessages.heading)}
328
          </h3>
329
        </div>
330
        <div
331
          data-c-grid-item="tp(1of3) tl(1of5)"
332
          data-c-align="base(center) tp(right)"
333
        >
334
          <a
335
            href="https://talent.test/demo/application-04"
336
            title={intl.formatMessage(messages.editTitle)}
337
            data-c-color="c2"
338
            data-c-font-weight="bold"
339
          >
340
            {intl.formatMessage(messages.edit)}
341
          </a>
342
        </div>
343
      </div>
344
      <hr data-c-hr="thin(gray)" data-c-margin="tb(1)" />
345
      <p data-c-padding="bottom(.5)" data-c-font-weight="bold">
346
        <FormattedMessage
347
          id="application.review.changeViewHeading"
348
          defaultMessage="Change Your View:"
349
          description="Heading for the Review section with the buttons to change the layout."
350
        />
351
      </p>
352
      <div data-c-padding="bottom(1)">
353
        <button
354
          data-c-button={`${
355
            experienceView === "experience" ? "solid" : "outline"
356
          }(c1)`}
357
          type="button"
358
          data-c-radius="rounded"
359
          className="gtag-application-review-all-experience"
360
          data-experience-view="experience"
361
          onClick={handleViewClick}
362
        >
363
          <FormattedMessage
364
            id="application.review.experienceViewButton"
365
            defaultMessage="All Experience"
366
            description="Button text for the experience view of the Review page."
367
          />
368
        </button>{" "}
369
        <button
370
          data-c-button={`${
371
            experienceView === "skills" ? "solid" : "outline"
372
          }(c1)`}
373
          type="button"
374
          data-c-radius="rounded"
375
          className="gtag-application-review-skill-experience"
376
          data-experience-view="skills"
377
          onClick={handleViewClick}
378
        >
379
          <FormattedMessage
380
            id="application.review.skillsViewButton"
381
            defaultMessage="Skills for This Job"
382
            description="Button text for the skills view of the Review page."
383
          />
384
        </button>{" "}
385
        <button
386
          data-c-button={`${
387
            experienceView === "education" ? "solid" : "outline"
388
          }(c1)`}
389
          type="button"
390
          data-c-radius="rounded"
391
          className="gtag-application-review-education-experience"
392
          data-experience-view="education"
393
          onClick={handleViewClick}
394
        >
395
          <FormattedMessage
396
            id="application.review.educationViewButton"
397
            defaultMessage="Education Requirements for This Job"
398
            description="Button text for the education view of the Review page."
399
          />
400
        </button>
401
      </div>
402
      {experienceView === "experience" && (
403
        <div className="experience-list">
404
          <p data-c-margin="bottom(1)">
405
            <FormattedMessage
406
              id="application.review.experienceViewHeading"
407
              defaultMessage="This view is a summary of all the experiences you will be sending as a part of your application."
408
              description="Heading for the experience view section of the Review page."
409
            />
410
          </p>
411
          <div data-c-accordion-group="">
412
            {experiences.map((experience) => {
413
              const irrelevantSkillCount = getIrrelevantSkillCount(
414
                criteria,
415
                experience,
416
                experienceSkills,
417
              );
418
              const relevantSkills = experienceSkills.filter(
419
                (experienceSkill) =>
420
                  experienceSkill.experience_type === experience.type &&
421
                  experienceSkill.experience_id === experience.id,
422
              );
423
              return (
424
                <ExperienceAccordion
425
                  key={`${experience.type}-${experience.id}`}
426
                  experience={experience}
427
                  experienceSkills={relevantSkills}
428
                  skills={skills}
429
                  irrelevantSkillCount={irrelevantSkillCount}
430
                  locale={locale}
431
                />
432
              );
433
            })}
434
          </div>
435
        </div>
436
      )}
437
      {experienceView === "skills" && (
438
        <div className="experience-list">
439
          <p data-c-margin="bottom(1)">
440
            <FormattedMessage
441
              id="application.review.skillsViewHeading"
442
              defaultMessage="This view organizes your experiences by the skills required for this job."
443
              description="Heading for the skills view section of the Review page."
444
            />
445
          </p>
446
          <div data-c-accordion-group="">
447
            {criteria.map((criterion) => {
448
              const skillOfCriterion = getSkillOfCriteria(criterion, skills);
449
450
              if (skillOfCriterion !== null) {
451
                const skillLevel = intl.formatMessage(
452
                  getSkillLevelName(criterion, skillOfCriterion),
453
                );
454
455
                const experiencesOfCriterion = experienceSkills.filter(
456
                  (experienceSkill) =>
457
                    experienceSkill.skill_id === criterion.skill_id,
458
                );
459
460
                const relevantExperiences = experiences.filter((experience) =>
461
                  experiencesOfCriterion.some(
462
                    (experienceSkill) =>
463
                      experienceSkill.experience_id === experience.id &&
464
                      experienceSkill.experience_type === experience.type,
465
                  ),
466
                );
467
468
                return (
469
                  <SkillAccordion
470
                    key={criterion.id}
471
                    skill={skillOfCriterion}
472
                    skillLevel={skillLevel}
473
                    experiences={relevantExperiences}
474
                    experienceSkills={experiencesOfCriterion}
475
                  />
476
                );
477
              }
478
              return null;
479
            })}
480
          </div>
481
        </div>
482
      )}
483
      {experienceView === "education" && (
484
        <div className="experience-list">
485
          <p data-c-margin="bottom(1)">
486
            <FormattedMessage
487
              id="application.review.educationViewHeading"
488
              defaultMessage="This view is a summary of all the experiences you have selected that help you meet the education requirements outlined below."
489
              description="Heading for the education view section of the Review page."
490
            />
491
          </p>
492
          <div
493
            data-c-background="gray(20)"
494
            data-c-radius="rounded"
495
            data-c-padding="all(1)"
496
            data-c-margin="bottom(1)"
497
          >
498
            <p>{localizeField(locale, job, "education")}</p>
499
          </div>
500
          <div data-c-accordion-group="">
501
            {experiences
502
              .filter((experience) => experience.is_education_requirement)
503
              .map((educationExperience) => {
504
                const irrelevantSkillCount = getIrrelevantSkillCount(
505
                  criteria,
506
                  educationExperience,
507
                  experienceSkills,
508
                );
509
                const relevantSkills = experienceSkills.filter(
510
                  (experienceSkill) =>
511
                    experienceSkill.experience_type ===
512
                      educationExperience.type &&
513
                    experienceSkill.experience_id === educationExperience.id,
514
                );
515
                return (
516
                  <ExperienceAccordion
517
                    key={`${educationExperience.type}-${educationExperience.id}-edu`}
518
                    experience={educationExperience}
519
                    experienceSkills={relevantSkills}
520
                    skills={skills}
521
                    irrelevantSkillCount={irrelevantSkillCount}
522
                    locale={locale}
523
                  />
524
                );
525
              })}
526
          </div>
527
        </div>
528
      )}
529
      <div data-c-grid="gutter(all, 1) middle" data-c-padding="top(3)">
530
        <div data-c-grid-item="tp(2of3) tl(4of5)">
531
          <h3 data-c-font-size="h3">
532
            {intl.formatMessage(fitMessages.heading)}
533
          </h3>
534
        </div>
535
        <div
536
          data-c-grid-item="tp(1of3) tl(1of5)"
537
          data-c-align="base(center) tp(right)"
538
        >
539
          <a
540
            href="https://talent.test/demo/application-07"
541
            title={intl.formatMessage(messages.editTitle)}
542
            data-c-color="c2"
543
            data-c-font-weight="bold"
544
          >
545
            {intl.formatMessage(messages.edit)}
546
          </a>
547
        </div>
548
      </div>
549
      <hr data-c-hr="thin(gray)" data-c-margin="top(1)" />
550
      {jobQuestions.map((jobQuestion, index) => {
551
        const answer = jobApplicationAnswers.find(
552
          (appAnswer) => appAnswer.job_poster_questions_id === jobQuestion.id,
553
        );
554
        return (
555
          <>
556
            <p
557
              data-c-font-weight="bold"
558
              data-c-color="c2"
559
              data-c-margin="top(1) bottom(.5)"
560
            >
561
              {intl.formatMessage(fitMessages.questionLabel, {
562
                index: index + 1,
563
              })}{" "}
564
              {localizeField(locale, jobQuestion, "question")}
565
            </p>
566
            <p>
567
              {answer ? (
568
                answer.answer
569
              ) : (
570
                <FormattedMessage
571
                  id="application.review.missingAnswer"
572
                  defaultMessage="Not yet answered."
573
                  description="Message displayed if the applicant did not yet answer one of the Fit questions."
574
                />
575
              )}
576
            </p>
577
          </>
578
        );
579
      })}
580
      <div data-c-grid="gutter(all, 1) middle" data-c-padding="top(3)">
581
        <div data-c-grid-item="tp(2of3) tl(4of5)">
582
          <h3 data-c-font-size="h3">
583
            <FormattedMessage
584
              id="application.review.accountSettingsHeading"
585
              defaultMessage="Account Settings"
586
            />
587
          </h3>
588
        </div>
589
        <div
590
          data-c-grid-item="tp(1of3) tl(1of5)"
591
          data-c-align="base(center) tp(right)"
592
        >
593
          <a
594
            href="https://talent.test/demo/application-07"
595
            title={intl.formatMessage(messages.editTitle)}
596
            data-c-color="c2"
597
            data-c-font-weight="bold"
598
          >
599
            {intl.formatMessage(messages.edit)}
600
          </a>
601
        </div>
602
      </div>
603
      <hr data-c-hr="thin(gray)" data-c-margin="top(1)" />
604
      <p
605
        data-c-font-weight="bold"
606
        data-c-color="c2"
607
        data-c-margin="top(1) bottom(.5)"
608
      >
609
        <FormattedMessage
610
          id="application.review.contactLabel"
611
          defaultMessage="Contact & Communication"
612
        />
613
      </p>
614
      <p data-c-margin="bottom(.5)">
615
        <i
616
          className="fas fa-check"
617
          data-c-color="go"
618
          data-c-margin="right(.25)"
619
        />
620
        {application.preferred_language_id}
621
      </p>
622
      <p data-c-margin="bottom(.5)">
623
        <i
624
          className="fas fa-check"
625
          data-c-color="go"
626
          data-c-margin="right(.25)"
627
        />
628
        <FormattedMessage
629
          id="application.review.userContact"
630
          defaultMessage="I would like Talent Cloud to contact me at {email} about related jobs."
631
          values={{
632
            email: user.email,
633
          }}
634
        />
635
      </p>
636
      <p>
637
        <i
638
          className="fas fa-check"
639
          data-c-color="go"
640
          data-c-margin="right(.25)"
641
        />
642
        <FormattedMessage
643
          id="application.review.userShare"
644
          defaultMessage="I would like Talent Cloud to share my application with other Government of Canada managers looking for similar sets of skills."
645
        />
646
      </p>
647
    </div>
648
  );
649
};
650
651
export default Review;
652