resources/assets/js/components/JobBuilder/Review/JobReview.tsx   A
last analyzed

Complexity

Total Complexity 24
Complexity/F 0

Size

Lines of Code 982
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 24
eloc 803
mnd 24
bc 24
fnc 0
dl 0
loc 982
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
rs 9.797
1
import React, { useState, useRef, useCallback } from "react";
2
import {
3
  injectIntl,
4
  WrappedComponentProps,
5
  FormattedMessage,
6
  defineMessages,
7
  useIntl,
8
} from "react-intl";
9
import {
10
  JobPosterKeyTask,
11
  Criteria,
12
  Job,
13
  Skill,
14
  Department,
15
  Manager,
16
  User,
17
  Comment,
18
} from "../../../models/types";
19
import {
20
  jobBuilderDetails,
21
  jobBuilderTasks,
22
  jobBuilderImpact,
23
  jobBuilderSkills,
24
  managerEditProfile,
25
  jobBuilderEnv,
26
  imageUrl,
27
  managerJobSummary,
28
} from "../../../helpers/routes";
29
import {
30
  find,
31
  mapToObject,
32
  hasKey,
33
  getId,
34
  notEmpty,
35
} from "../../../helpers/queries";
36
import {
37
  provinceName,
38
  securityClearance,
39
  languageRequirement,
40
  languageRequirementDescription,
41
  languageRequirementContext,
42
  jobReviewLocations,
43
} from "../../../models/localizedConstants";
44
import {
45
  CriteriaTypeId,
46
  LanguageRequirementId,
47
  LocationId,
48
} from "../../../models/lookupConstants";
49
import Criterion from "../Criterion";
50
import JobWorkEnv from "../WorkEnv/WorkEnvFeatures";
51
import JobWorkCulture from "../JobWorkCulture";
52
import Modal from "../../Modal";
53
import { textToParagraphs } from "../../../helpers/textToParagraphs";
54
import { useUrlHash, Link } from "../../../helpers/router";
55
import { classificationString } from "../../../models/jobUtil";
56
import DemoSubmitJobModal from "./DemoSubmitJobModal";
57
import ManagerSurveyModal from "./ManagerSurveyModal";
58
import ActivityFeed from "../../ActivityFeed";
59
import { localizeFieldNonNull, localizeField } from "../../../helpers/localize";
60
61
interface JobReviewSectionProps {
62
  title: string;
63
  isSubsection?: boolean;
64
  link: string;
65
  linkLabel: string;
66
  description?: string;
67
  hideLink: boolean;
68
}
69
70
const messages = defineMessages({
71
  titleHeading: {
72
    id: "jobBuilder.review.jobPageHeading",
73
    defaultMessage: "Job Page Heading",
74
    description: "Section title.",
75
  },
76
  infoEditLink: {
77
    id: "jobBuilder.review.infoEditLink",
78
    defaultMessage: "Edit This in Step 01: Job Info",
79
    description: "Link to edit job details.",
80
  },
81
  impactEditLink: {
82
    id: "jobBuilder.review.impactEditLink",
83
    defaultMessage: "Edit This in Step 03: Impact",
84
    description: "Link to edit impact statements.",
85
  },
86
  tasksEditLink: {
87
    id: "jobBuilder.review.tasksEditLink",
88
    defaultMessage: "Edit This in Step 04: Tasks",
89
    description: "Link to edit tasks.",
90
  },
91
  skillsEditLink: {
92
    id: "jobBuilder.review.skillsEditLink",
93
    defaultMessage: "Edit This in Step 05: Skills",
94
    description: "Link to edit skills.",
95
  },
96
  workEnvEditLink: {
97
    id: "jobBuilder.review.workEnvEditLink",
98
    defaultMessage: "Edit This in Step 02: Work Environment",
99
    description: "Link to edit work environment.",
100
  },
101
  managerProfileLink: {
102
    id: "jobBuilder.review.managerProfileLink",
103
    defaultMessage: "Edit This in Your Profile",
104
    description: "Link to edit a manager's profile.",
105
  },
106
  nullProvince: {
107
    id: "jobBuilder.review.nullProvince",
108
    defaultMessage: "MISSING PROVINCE",
109
    description: "Error text for missing province information.",
110
  },
111
  basicHeading: {
112
    id: "jobBuilder.review.basicInformationHeading",
113
    defaultMessage: "Basic Information",
114
    description: "Heading for Basic Information section",
115
  },
116
  impactHeading: {
117
    id: "jobBuilder.review.impactHeading",
118
    defaultMessage: "Impact",
119
    description: "Heading for Impact section",
120
  },
121
  tasksHeading: {
122
    id: "jobBuilder.review.tasksHeading",
123
    defaultMessage: "Tasks",
124
    description: "Heading for Tasks section",
125
  },
126
  criteriaSection: {
127
    id: "jobBuilder.review.criteriaSection",
128
    defaultMessage: "Criteria",
129
    description: "Title for criteria section",
130
  },
131
  educationalHeading: {
132
    id: "jobBuilder.review.educationalHeading",
133
    defaultMessage: "Education Requirements",
134
    description: "Heading for Educational section",
135
  },
136
  skillsHeading: {
137
    id: "jobBuilder.review.skillsHeading",
138
    defaultMessage: "Skills You Need to Have",
139
    description: "Heading for Skills section",
140
  },
141
  assetHeading: {
142
    id: "jobBuilder.review.assetHeading",
143
    defaultMessage: "Skills That Are Nice to Have",
144
    description: "Heading for Asset Skills section",
145
  },
146
  languageHeading: {
147
    id: "jobBuilder.review.languageHeading",
148
    defaultMessage: "Language Requirements",
149
    description: "Heading for Language section",
150
  },
151
  cultureSection: {
152
    id: "jobBuilder.review.cultureSection",
153
    defaultMessage: "Environment & Culture",
154
    description: "Title for culture section",
155
  },
156
  managerHeading: {
157
    id: "jobBuilder.review.managerHeading",
158
    defaultMessage: "Manager Information",
159
    description: "Heading for Manager section",
160
  },
161
  workCultureHeading: {
162
    id: "jobBuilder.review.workCultureHeading",
163
    defaultMessage: "Work Culture",
164
    description: "Heading for Work Culture section",
165
  },
166
  workEnvHeading: {
167
    id: "jobBuilder.review.workEnvHeading",
168
    defaultMessage: "Work Environment",
169
    description: "Heading for Work Environment section",
170
  },
171
  workEnvDescription: {
172
    id: "jobBuilder.review.workDescription",
173
    defaultMessage:
174
      "Please note that some Work Environment information is only presented to the applicant after they've clicked the \"View the team's work environment and culture\" button that appears on the job poster.",
175
    description: "A note about the information in the work description section",
176
  },
177
  otherInfoHeading: {
178
    id: "jobBuilder.review.otherInfoHeading",
179
    defaultMessage: "Other Team Information",
180
    description: "Heading for other info section",
181
  },
182
});
183
184
const JobReviewSection: React.FunctionComponent<JobReviewSectionProps> = ({
185
  title,
186
  isSubsection,
187
  link,
188
  linkLabel,
189
  description,
190
  children,
191
  hideLink,
192
}): React.ReactElement => {
193
  return (
194
    <>
195
      <div
196
        data-c-margin={
197
          isSubsection ? "tb(normal)" : "top(triple) bottom(normal)"
198
        }
199
      >
200
        <div data-c-grid="gutter middle">
201
          <div
202
            data-c-grid-item="tp(1of2)"
203
            data-c-alignment="base(centre) tp(left)"
204
          >
205
            {isSubsection ? (
206
              <h5 data-c-font-weight="bold" data-c-font-size="h5">
207
                {title}
208
              </h5>
209
            ) : (
210
              <h4 data-c-colour="c2" data-c-font-size="h4">
211
                {title}
212
              </h4>
213
            )}
214
          </div>
215
          {!hideLink && (
216
            <div
217
              data-c-grid-item="tp(1of2)"
218
              data-c-alignment="base(centre) tp(right)"
219
            >
220
              <Link href={link} title={linkLabel}>
221
                <i data-c-colour="c2" className="fas fa-edit" />
222
                {linkLabel}
223
              </Link>
224
            </div>
225
          )}
226
        </div>
227
      </div>
228
      {description && <p data-c-margin="bottom(normal)">{description}</p>}
229
      <div
230
        data-c-border="all(thin, solid, grey)"
231
        data-c-padding="normal"
232
        data-c-radius="rounded"
233
      >
234
        {children}
235
      </div>
236
    </>
237
  );
238
};
239
240
const sectionTitle = (title: string): React.ReactElement => {
241
  return (
242
    <div data-c-margin="top(triple) bottom(normal)">
243
      <div data-c-grid="gutter middle">
244
        <div
245
          data-c-grid-item="base(1of1)"
246
          data-c-alignment="base(centre) tp(left)"
247
        >
248
          <h4 data-c-colour="c2" data-c-font-size="h4">
249
            {title}
250
          </h4>
251
        </div>
252
      </div>
253
    </div>
254
  );
255
};
256
257
const languageRequirementIcons = (
258
  languageRequirementId: number,
259
): React.ReactElement => {
260
  const enIcon = <img src={imageUrl("icon_english_requirement.svg")} alt="" />;
261
  const frIcon = <img src={imageUrl("icon_french_requirement.svg")} alt="" />;
262
  switch (languageRequirementId) {
263
    case LanguageRequirementId.bilingualIntermediate:
264
    case LanguageRequirementId.bilingualAdvanced:
265
      return (
266
        <>
267
          {enIcon}
268
          &amp;&nbsp;&nbsp;
269
          {frIcon}
270
        </>
271
      );
272
    case LanguageRequirementId.englishOrFrench:
273
      return (
274
        <>
275
          {enIcon}
276
          <FormattedMessage
277
            id="jobBuilder.review.or"
278
            defaultMessage="or"
279
            description="Displayed between language icons for the English Or French option."
280
          />
281
          &nbsp;&nbsp;
282
          {frIcon}
283
        </>
284
      );
285
    case LanguageRequirementId.english:
286
      return enIcon;
287
    case LanguageRequirementId.french:
288
      return frIcon;
289
    default:
290
      return enIcon;
291
  }
292
};
293
294
const renderManagerSection = (
295
  manager: Manager | null,
296
  managerDeptName: string,
297
  locale: "en" | "fr",
298
): React.ReactElement => {
299
  if (manager === null) {
300
    return (
301
      <p>
302
        <FormattedMessage
303
          id="jobBuilder.review.managerDataLoading"
304
          defaultMessage="Manager data is loading..."
305
          description="Placeholder text as Manager data loads."
306
        />
307
      </p>
308
    );
309
  }
310
  const leadershipStyle = localizeField(locale, manager, "leadership_style");
311
  const position = localizeField(locale, manager, "position");
312
313
  if (leadershipStyle !== null && position !== null) {
314
    return (
315
      <>
316
        <p data-c-margin="bottom(normal)">{manager.full_name}</p>
317
        <p data-c-margin={`${leadershipStyle && "{bottom(normal)"}`}>
318
          <FormattedMessage
319
            id="jobBuilder.review.managerPosition"
320
            defaultMessage="{position} at {department}"
321
            description="Description of the Manager's position & department."
322
            values={{
323
              position,
324
              department: managerDeptName,
325
            }}
326
          />
327
        </p>
328
        {leadershipStyle && <p>{leadershipStyle}</p>}
329
      </>
330
    );
331
  }
332
  return (
333
    <p>
334
      <FormattedMessage
335
        id="jobBuilder.review.managerIncomplete"
336
        defaultMessage="Please complete your manager profile."
337
        description="Note that the Manager's profile is incomplete and should be edited before continuing."
338
      />
339
    </p>
340
  );
341
};
342
343
interface JobReviewDisplayProps {
344
  job: Job;
345
  classificationKey: string;
346
  manager: Manager | null;
347
  tasks: JobPosterKeyTask[];
348
  criteria: Criteria[];
349
  // List of all possible skills.
350
  skills: Skill[];
351
  // List of all possible departments.
352
  departments: Department[];
353
  user: User | null;
354
  hideBuilderLinks: boolean;
355
}
356
export const JobReviewDisplay: React.FC<JobReviewDisplayProps> = ({
357
  job,
358
  classificationKey,
359
  manager,
360
  tasks,
361
  criteria,
362
  skills,
363
  departments,
364
  user,
365
  hideBuilderLinks = false,
366
}): React.ReactElement => {
367
  // Scroll to element specified in the url hash, if possible
368
  useUrlHash();
369
  const intl = useIntl();
370
  const { locale } = intl;
371
  if (locale !== "en" && locale !== "fr") {
372
    throw new Error("Unknown intl.locale");
373
  }
374
375
  const getDeptName = (departmentId: number | null): string => {
376
    const department =
377
      departmentId !== null ? find(departments, departmentId) : null;
378
    return department !== null
379
      ? localizeFieldNonNull(locale, department, "name")
380
      : "MISSING DEPARTMENT";
381
  };
382
  const departmentName = getDeptName(job.department_id);
383
  const userDeptName = user ? getDeptName(user.department_id) : "";
384
385
  // Map the skills into a dictionary for quicker access
386
  const skillsById = mapToObject(skills, getId);
387
  const getSkillOfCriteria = (criterion: Criteria): Skill | null => {
388
    return hasKey(skillsById, criterion.skill_id)
389
      ? skillsById[criterion.skill_id]
390
      : null;
391
  };
392
  const essentialCriteria = criteria.filter(
393
    (criterion): boolean =>
394
      criterion.criteria_type_id === CriteriaTypeId.Essential,
395
  );
396
  const assetCriteria = criteria.filter(
397
    (criterion): boolean => criterion.criteria_type_id === CriteriaTypeId.Asset,
398
  );
399
400
  const selectedEnvOptions: string[] = job.work_env_features
401
    ? Object.entries(job.work_env_features)
402
        .map(([feature, selected]): string | null =>
403
          selected ? feature : null,
404
        )
405
        .filter(notEmpty)
406
    : [];
407
408
  return (
409
    <>
410
      <JobReviewSection
411
        title={intl.formatMessage(messages.titleHeading)}
412
        linkLabel={intl.formatMessage(messages.infoEditLink)}
413
        link={jobBuilderDetails(locale, job.id)}
414
        hideLink={hideBuilderLinks}
415
      >
416
        <p data-c-font-weight="bold" data-c-margin="bottom(half)">
417
          {localizeField(locale, job, "title")}
418
        </p>
419
        <p data-c-margin="bottom(normal)">{departmentName}</p>
420
        <p data-c-margin="bottom(half)">
421
          <i
422
            data-c-colour="c2"
423
            className="fas fa-map-marker-alt"
424
            title="Location Icon."
425
          >
426
            &nbsp;&nbsp;
427
          </i>
428
          {localizeField(locale, job, "city")},{" "}
429
          {job.province_id !== null
430
            ? intl.formatMessage(provinceName(job.province_id))
431
            : intl.formatMessage(messages.nullProvince)}
432
        </p>
433
        <p>
434
          <i
435
            data-c-colour="c2"
436
            className="fas fa-home"
437
            title="Remote Work Icon."
438
          >
439
            &nbsp;&nbsp;
440
          </i>
441
          {job.remote_work_allowed ? (
442
            <FormattedMessage
443
              id="jobBuilder.review.remoteAllowed"
444
              defaultMessage="Remote Work Allowed"
445
              description="Text displayed when remote work is allowed."
446
            />
447
          ) : (
448
            <FormattedMessage
449
              id="jobBuilder.review.remoteNotAllowed"
450
              defaultMessage="Remote Work Not Allowed"
451
              description="Text displayed when remote work is not allowed."
452
            />
453
          )}
454
        </p>
455
      </JobReviewSection>
456
      <JobReviewSection
457
        title={intl.formatMessage(messages.basicHeading)}
458
        linkLabel={intl.formatMessage(messages.infoEditLink)}
459
        link={jobBuilderDetails(locale, job.id)}
460
        hideLink={hideBuilderLinks}
461
      >
462
        <div data-c-grid="gutter">
463
          <div data-c-grid-item="tp(1of2)">
464
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
465
              <FormattedMessage
466
                id="jobBuilder.review.averageAnnualSalary"
467
                defaultMessage="Annual Salary Range"
468
                description="Label for salary information."
469
              />
470
            </p>
471
            <p>
472
              <FormattedMessage
473
                id="jobBuilder.review.tCAdds"
474
                defaultMessage="Talent Cloud will add this."
475
                description="Information will be added placeholder."
476
              />
477
            </p>
478
          </div>
479
          <div data-c-grid-item="tp(1of2)">
480
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
481
              <FormattedMessage
482
                id="jobBuilder.review.languageProfile"
483
                defaultMessage="Language Profile"
484
                description="Information will be added placeholder."
485
              />
486
            </p>
487
            <p>
488
              {job.language_requirement_id
489
                ? intl.formatMessage(
490
                    languageRequirement(job.language_requirement_id),
491
                  )
492
                : ""}
493
            </p>
494
          </div>
495
          <div data-c-grid-item="tp(1of2)">
496
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
497
              <FormattedMessage
498
                id="jobBuilder.review.duration"
499
                defaultMessage="Duration"
500
                description="Label for duration of Term"
501
              />
502
            </p>
503
            <p>
504
              <FormattedMessage
505
                id="jobBuilder.review.months"
506
                defaultMessage="{termMonths, plural, =0 {No Months} one {# Month} other {# Months}}"
507
                description="Length of term in months"
508
                values={{ termMonths: job.term_qty }}
509
              />
510
            </p>
511
          </div>
512
          <div data-c-grid-item="tp(1of2)">
513
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
514
              <FormattedMessage
515
                id="jobBuilder.review.securityClearance"
516
                defaultMessage="Security Clearance"
517
                description="Label for Security Clearance info"
518
              />
519
            </p>
520
            <p>
521
              {job.security_clearance_id
522
                ? intl.formatMessage(
523
                    securityClearance(job.security_clearance_id),
524
                  )
525
                : ""}
526
            </p>
527
          </div>
528
          <div data-c-grid-item="tp(1of2)">
529
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
530
              <FormattedMessage
531
                id="jobBuilder.review.targetStartDate"
532
                defaultMessage="Target Start Date"
533
                description="Label for start date info"
534
              />
535
            </p>
536
            <p>
537
              <FormattedMessage
538
                id="jobBuilder.review.comesLater"
539
                defaultMessage="This comes later."
540
                description="Placeholder for information that comes later"
541
              />
542
            </p>
543
          </div>
544
          <div data-c-grid-item="tp(1of2)">
545
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
546
              <FormattedMessage
547
                id="jobBuilder.review.GovernmentClass"
548
                defaultMessage="Government Classification"
549
                description="Placeholder for information that comes later"
550
              />
551
            </p>
552
            <p>{classificationString(classificationKey, job)}</p>
553
          </div>
554
        </div>
555
      </JobReviewSection>
556
      <JobReviewSection
557
        title={intl.formatMessage(messages.impactHeading)}
558
        linkLabel={intl.formatMessage(messages.impactEditLink)}
559
        link={jobBuilderImpact(locale, job.id)}
560
        hideLink={hideBuilderLinks}
561
      >
562
        <p data-c-margin="bottom(normal)">
563
          {localizeField(locale, job, "dept_impact")}
564
        </p>
565
        <p data-c-margin="bottom(normal)">
566
          {localizeField(locale, job, "team_impact")}
567
        </p>
568
        <p>{localizeField(locale, job, "hire_impact")}</p>
569
      </JobReviewSection>
570
      <JobReviewSection
571
        title={intl.formatMessage(messages.tasksHeading)}
572
        linkLabel={intl.formatMessage(messages.tasksEditLink)}
573
        link={jobBuilderTasks(locale, job.id)}
574
        hideLink={hideBuilderLinks}
575
      >
576
        <ul>
577
          {tasks.map(
578
            (task: JobPosterKeyTask): React.ReactElement => (
579
              <li key={task.id}>
580
                {localizeField(locale, task, "description")}
581
              </li>
582
            ),
583
          )}
584
        </ul>
585
      </JobReviewSection>
586
      {sectionTitle(intl.formatMessage(messages.criteriaSection))}
587
      <JobReviewSection
588
        title={intl.formatMessage(messages.educationalHeading)}
589
        isSubsection
590
        linkLabel={intl.formatMessage(messages.infoEditLink)}
591
        link={jobBuilderDetails(locale, job.id)}
592
        hideLink={hideBuilderLinks}
593
      >
594
        {textToParagraphs(localizeField(locale, job, "education") || "")}
595
      </JobReviewSection>
596
      <JobReviewSection
597
        title={intl.formatMessage(messages.skillsHeading)}
598
        isSubsection
599
        linkLabel={intl.formatMessage(messages.skillsEditLink)}
600
        link={jobBuilderSkills(locale, job.id)}
601
        hideLink={hideBuilderLinks}
602
      >
603
        {essentialCriteria.length === 0 ? (
604
          <p>
605
            <FormattedMessage
606
              id="jobBuilder.review.skills.nullState"
607
              defaultMessage="You haven't added any Nice to Have skills to this poster."
608
              description="The text displayed for skills when you haven't added any skills."
609
            />
610
          </p>
611
        ) : (
612
          essentialCriteria.map((criterion): React.ReactElement | null => {
613
            const skill = getSkillOfCriteria(criterion);
614
            if (skill === null) {
615
              return null;
616
            }
617
            return (
618
              <Criterion
619
                criterion={criterion}
620
                skill={skill}
621
                key={criterion.id}
622
              />
623
            );
624
          })
625
        )}
626
      </JobReviewSection>
627
      <JobReviewSection
628
        title={intl.formatMessage(messages.assetHeading)}
629
        isSubsection
630
        linkLabel={intl.formatMessage(messages.skillsEditLink)}
631
        link={jobBuilderSkills(locale, job.id)}
632
        hideLink={hideBuilderLinks}
633
      >
634
        {assetCriteria.length === 0 ? (
635
          <p>
636
            <FormattedMessage
637
              id="jobBuilder.review.skills.nullState"
638
              defaultMessage="You haven't added any Nice to Have skills to this poster."
639
              description="The text displayed for skills when you haven't added any skills."
640
            />
641
          </p>
642
        ) : (
643
          assetCriteria.map((criterion): React.ReactElement | null => {
644
            const skill = getSkillOfCriteria(criterion);
645
            if (skill === null) {
646
              return null;
647
            }
648
            return (
649
              <Criterion
650
                criterion={criterion}
651
                skill={skill}
652
                key={criterion.id}
653
              />
654
            );
655
          })
656
        )}
657
      </JobReviewSection>
658
      <JobReviewSection
659
        title={intl.formatMessage(messages.languageHeading)}
660
        linkLabel={intl.formatMessage(messages.infoEditLink)}
661
        link={jobBuilderDetails(locale, job.id)}
662
        hideLink={hideBuilderLinks}
663
      >
664
        {/** TODO: get lang data from job */}
665
        {job.language_requirement_id && (
666
          <>
667
            <p
668
              className="job-builder-review-language"
669
              data-c-margin="bottom(normal)"
670
            >
671
              {languageRequirementIcons(job.language_requirement_id)}
672
              {intl.formatMessage(
673
                languageRequirement(job.language_requirement_id),
674
              )}
675
            </p>
676
            <p data-c-margin="bottom(normal)">
677
              {intl.formatMessage(
678
                languageRequirementDescription(job.language_requirement_id),
679
              )}
680
            </p>
681
            <p data-c-margin="top(normal)">
682
              {intl.formatMessage(
683
                languageRequirementContext(job.language_requirement_id),
684
              )}
685
            </p>
686
          </>
687
        )}
688
      </JobReviewSection>
689
      {sectionTitle(intl.formatMessage(messages.cultureSection))}
690
      <JobReviewSection
691
        title={intl.formatMessage(messages.managerHeading)}
692
        isSubsection
693
        linkLabel={intl.formatMessage(messages.managerProfileLink)}
694
        link={managerEditProfile(locale)}
695
        hideLink={hideBuilderLinks}
696
      >
697
        {renderManagerSection(manager, userDeptName, locale)}
698
      </JobReviewSection>
699
      <JobReviewSection
700
        title={intl.formatMessage(messages.workCultureHeading)}
701
        isSubsection
702
        linkLabel={intl.formatMessage(messages.workEnvEditLink)}
703
        link={jobBuilderEnv(locale, job.id)}
704
        hideLink={hideBuilderLinks}
705
      >
706
        {localizeField(locale, job, "culture_summary") && (
707
          <p>{localizeField(locale, job, "culture_summary")}</p>
708
        )}
709
        {localizeField(locale, job, "culture_special") && (
710
          <p>{localizeField(locale, job, "culture_special")}</p>
711
        )}
712
      </JobReviewSection>
713
      <JobReviewSection
714
        title={intl.formatMessage(messages.workEnvHeading)}
715
        isSubsection
716
        linkLabel={intl.formatMessage(messages.workEnvEditLink)}
717
        link={jobBuilderEnv(locale, job.id)}
718
        description={intl.formatMessage(messages.workEnvDescription)}
719
        hideLink={hideBuilderLinks}
720
      >
721
        <JobWorkEnv
722
          teamSize={job.team_size || 0}
723
          selectedEnvOptions={selectedEnvOptions}
724
          envDescription={
725
            localizeField(locale, job, "work_env_description") || ""
726
          }
727
        />
728
      </JobReviewSection>
729
      <JobReviewSection
730
        title={intl.formatMessage(messages.otherInfoHeading)}
731
        isSubsection
732
        linkLabel={intl.formatMessage(messages.infoEditLink)}
733
        link={jobBuilderDetails(locale, job.id)}
734
        hideLink={hideBuilderLinks}
735
      >
736
        <JobWorkCulture job={job} />
737
      </JobReviewSection>
738
    </>
739
  );
740
};
741
742
interface JobReviewProps {
743
  job: Job;
744
  classificationKey: string;
745
  manager: Manager | null;
746
  tasks: JobPosterKeyTask[];
747
  criteria: Criteria[];
748
  // List of all possible skills.
749
  skills: Skill[];
750
  // List of all possible departments.
751
  departments: Department[];
752
  user: User | null;
753
  validForSubmission?: boolean;
754
  handleSubmit: (job: Job) => Promise<void>;
755
  handleContinue: () => void;
756
  handleReturn: () => void;
757
}
758
759
export const JobReview: React.FunctionComponent<
760
  JobReviewProps & WrappedComponentProps
761
> = ({
762
  job,
763
  classificationKey,
764
  manager,
765
  tasks,
766
  criteria,
767
  skills,
768
  departments,
769
  user,
770
  validForSubmission = false,
771
  handleSubmit,
772
  handleReturn,
773
  intl,
774
}): React.ReactElement => {
775
  const { locale } = intl;
776
  if (locale !== "en" && locale !== "fr") {
777
    throw new Error("Unknown intl.locale");
778
  }
779
  const [isModalVisible, setIsModalVisible] = useState(false);
780
  const [isSurveyModalVisible, setIsSurveyModalVisible] = useState(false);
781
  const modalId = "job-review-modal";
782
  const modalParentRef = useRef<HTMLDivElement>(null);
783
784
  const filterComments = useCallback(
785
    (comment: Comment): boolean => hasKey(jobReviewLocations, comment.location),
786
    [],
787
  );
788
789
  return (
790
    <>
791
      <div
792
        data-c-container="form"
793
        data-c-padding="top(triple) bottom(triple)"
794
        ref={modalParentRef}
795
      >
796
        <h3
797
          data-c-font-size="h3"
798
          data-c-font-weight="bold"
799
          data-c-margin="bottom(normal)"
800
        >
801
          <FormattedMessage
802
            id="jobBuilder.review.reviewYourPoster"
803
            defaultMessage="Review Your Job Poster for:"
804
            description="Title for Review Job Poster section."
805
          />{" "}
806
          <span data-c-colour="c2">{localizeField(locale, job, "title")}</span>
807
        </h3>
808
        <p data-c-margin="bottom(double)">
809
          <FormattedMessage
810
            id="jobBuilder.review.headsUp"
811
            defaultMessage="Just a heads up! We've rearranged some of your information to help you
812
            understand how an applicant will see it once published."
813
            description="Description under primary title of review section"
814
          />
815
        </p>
816
        <ActivityFeed
817
          jobId={job.id}
818
          isHrAdvisor={false}
819
          generalLocation={LocationId.jobGeneric}
820
          locationMessages={jobReviewLocations}
821
          filterComments={filterComments}
822
        />
823
        <JobReviewDisplay
824
          job={job}
825
          classificationKey={classificationKey}
826
          manager={manager}
827
          tasks={tasks}
828
          criteria={criteria}
829
          skills={skills}
830
          departments={departments}
831
          user={user}
832
          hideBuilderLinks={false}
833
        />
834
        <div data-c-grid="gutter">
835
          <div data-c-grid-item="base(1of1)">
836
            <hr data-c-margin="top(normal) bottom(normal)" />
837
          </div>
838
          <div
839
            data-c-alignment="base(centre) tp(left)"
840
            data-c-grid-item="tp(1of2)"
841
          >
842
            <button
843
              data-c-button="outline(c2)"
844
              data-c-radius="rounded"
845
              type="button"
846
              onClick={(): void => handleReturn()}
847
            >
848
              <FormattedMessage
849
                id="jobBuilder.review.button.return"
850
                defaultMessage="Save &amp; Return to Skills"
851
                description="Label of 'Previous Step' button."
852
              />
853
            </button>
854
          </div>
855
          <div
856
            data-c-alignment="base(centre) tp(right)"
857
            data-c-grid-item="tp(1of2)"
858
          >
859
            <a
860
              href={managerJobSummary(locale, job.id)}
861
              title=""
862
              data-c-button="solid(c1)"
863
              data-c-dialog-action="close"
864
              data-c-radius="rounded"
865
              style={{ textDecoration: "none" }}
866
            >
867
              <FormattedMessage
868
                id="jobBuilder.review.button.submit"
869
                defaultMessage="Save and Return to Summary"
870
                description="Label of Job Review Submission Button"
871
              />
872
            </a>
873
          </div>
874
        </div>
875
      </div>
876
      {/* Congrats modal below is temporarily being removed from the review page, until it can be repurposed on the summary page. */}
877
      <div
878
        data-c-dialog-overlay={
879
          isModalVisible || isSurveyModalVisible ? "active" : ""
880
        }
881
      />
882
      {manager === null || manager.is_demo_manager ? (
883
        <DemoSubmitJobModal
884
          isVisible={isModalVisible}
885
          handleCancel={(): void => setIsModalVisible(false)}
886
          parentElement={modalParentRef.current}
887
        />
888
      ) : (
889
        <Modal
890
          id={modalId}
891
          visible={isModalVisible}
892
          onModalCancel={(): void => setIsModalVisible(false)}
893
          onModalConfirm={async (): Promise<void> => {
894
            try {
895
              await handleSubmit(job);
896
              setIsModalVisible(false);
897
              setIsSurveyModalVisible(true);
898
            } catch {
899
              setIsModalVisible(false);
900
            }
901
          }}
902
          parentElement={modalParentRef.current}
903
        >
904
          <Modal.Header>
905
            <div
906
              data-c-background="c1(100)"
907
              data-c-border="bottom(thin, solid, black)"
908
              data-c-padding="normal"
909
            >
910
              <h5
911
                data-c-colour="white"
912
                data-c-font-size="h4"
913
                id={`${modalId}-title`}
914
              >
915
                <FormattedMessage
916
                  id="jobBuilder.review.confirm.title"
917
                  defaultMessage="Congrats! Are You Ready to Submit?"
918
                  description="Title of Submit Confirmation modal."
919
                />
920
              </h5>
921
            </div>
922
          </Modal.Header>
923
          <Modal.Body>
924
            <div data-c-padding="normal">
925
              <p data-c-margin="bottom(normal)">
926
                <FormattedMessage
927
                  id="jobBuilder.review.readyToSubmit"
928
                  defaultMessage="If you're ready to submit your poster, click the Submit button below."
929
                  description="Instructions on how to submit"
930
                />
931
              </p>
932
              <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
933
                <FormattedMessage
934
                  id="jobBuilder.review.whatHappens"
935
                  defaultMessage="What happens next?"
936
                  description="Rhetorical question"
937
                />
938
              </p>
939
              <p data-c-margin="bottom(normal)">
940
                <FormattedMessage
941
                  id="jobBuilder.review.sendYourDraft"
942
                  defaultMessage="Talent Cloud will send your draft to your department's HR advisor who will notify you with comments."
943
                  description="Information about next steps"
944
                />
945
              </p>
946
              <p>
947
                <FormattedMessage
948
                  id="jobBuilder.review.meantime"
949
                  defaultMessage="In the meantime, feel free to go ahead and create a screening plan for your selection process. Alternatively, you can wait for comments to come back from HR before you take the next step."
950
                  description="Suggestion on what to do while you wait."
951
                />
952
              </p>
953
            </div>
954
          </Modal.Body>
955
          <Modal.Footer>
956
            <Modal.FooterCancelBtn>
957
              <FormattedMessage
958
                id="jobBuilder.review.confirm.cancel"
959
                defaultMessage="Cancel"
960
                description="Cancel button of Job Review confirmation modal."
961
              />
962
            </Modal.FooterCancelBtn>
963
            <Modal.FooterConfirmBtn>
964
              <FormattedMessage
965
                id="jobBuilder.review.confirm.submit"
966
                defaultMessage="Yes, Submit"
967
                description="Submit button of Job Review confirmation modal."
968
              />
969
            </Modal.FooterConfirmBtn>
970
          </Modal.Footer>
971
        </Modal>
972
      )}
973
      <ManagerSurveyModal
974
        isVisible={isSurveyModalVisible}
975
        parentElement={modalParentRef.current}
976
      />
977
    </>
978
  );
979
};
980
981
export default injectIntl(JobReview);
982