Passed
Push — task/experience-skills-batch-r... ( d261b3...81289a )
by Yonathan
06:24 queued 11s
created

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

Complexity

Total Complexity 24
Complexity/F 0

Size

Lines of Code 977
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 24
eloc 800
mnd 24
bc 24
fnc 0
dl 0
loc 977
rs 9.8
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
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
  manager: Manager | null;
346
  tasks: JobPosterKeyTask[];
347
  criteria: Criteria[];
348
  // List of all possible skills.
349
  skills: Skill[];
350
  // List of all possible departments.
351
  departments: Department[];
352
  user: User | null;
353
  hideBuilderLinks: boolean;
354
}
355
export const JobReviewDisplay: React.FC<JobReviewDisplayProps> = ({
356
  job,
357
  manager,
358
  tasks,
359
  criteria,
360
  skills,
361
  departments,
362
  user,
363
  hideBuilderLinks = false,
364
}): React.ReactElement => {
365
  // Scroll to element specified in the url hash, if possible
366
  useUrlHash();
367
  const intl = useIntl();
368
  const { locale } = intl;
369
  if (locale !== "en" && locale !== "fr") {
370
    throw new Error("Unknown intl.locale");
371
  }
372
373
  const getDeptName = (departmentId: number | null): string => {
374
    const department =
375
      departmentId !== null ? find(departments, departmentId) : null;
376
    return department !== null
377
      ? localizeFieldNonNull(locale, department, "name")
378
      : "MISSING DEPARTMENT";
379
  };
380
  const departmentName = getDeptName(job.department_id);
381
  const userDeptName = user ? getDeptName(user.department_id) : "";
382
383
  // Map the skills into a dictionary for quicker access
384
  const skillsById = mapToObject(skills, getId);
385
  const getSkillOfCriteria = (criterion: Criteria): Skill | null => {
386
    return hasKey(skillsById, criterion.skill_id)
387
      ? skillsById[criterion.skill_id]
388
      : null;
389
  };
390
  const essentialCriteria = criteria.filter(
391
    (criterion): boolean =>
392
      criterion.criteria_type_id === CriteriaTypeId.Essential,
393
  );
394
  const assetCriteria = criteria.filter(
395
    (criterion): boolean => criterion.criteria_type_id === CriteriaTypeId.Asset,
396
  );
397
398
  const selectedEnvOptions: string[] = job.work_env_features
399
    ? Object.entries(job.work_env_features)
400
        .map(([feature, selected]): string | null =>
401
          selected ? feature : null,
402
        )
403
        .filter(notEmpty)
404
    : [];
405
406
  return (
407
    <>
408
      <JobReviewSection
409
        title={intl.formatMessage(messages.titleHeading)}
410
        linkLabel={intl.formatMessage(messages.infoEditLink)}
411
        link={jobBuilderDetails(locale, job.id)}
412
        hideLink={hideBuilderLinks}
413
      >
414
        <p data-c-font-weight="bold" data-c-margin="bottom(half)">
415
          {localizeField(locale, job, "title")}
416
        </p>
417
        <p data-c-margin="bottom(normal)">{departmentName}</p>
418
        <p data-c-margin="bottom(half)">
419
          <i
420
            data-c-colour="c2"
421
            className="fas fa-map-marker-alt"
422
            title="Location Icon."
423
          >
424
            &nbsp;&nbsp;
425
          </i>
426
          {localizeField(locale, job, "city")},{" "}
427
          {job.province_id !== null
428
            ? intl.formatMessage(provinceName(job.province_id))
429
            : intl.formatMessage(messages.nullProvince)}
430
        </p>
431
        <p>
432
          <i
433
            data-c-colour="c2"
434
            className="fas fa-home"
435
            title="Remote Work Icon."
436
          >
437
            &nbsp;&nbsp;
438
          </i>
439
          {job.remote_work_allowed ? (
440
            <FormattedMessage
441
              id="jobBuilder.review.remoteAllowed"
442
              defaultMessage="Remote Work Allowed"
443
              description="Text displayed when remote work is allowed."
444
            />
445
          ) : (
446
            <FormattedMessage
447
              id="jobBuilder.review.remoteNotAllowed"
448
              defaultMessage="Remote Work Not Allowed"
449
              description="Text displayed when remote work is not allowed."
450
            />
451
          )}
452
        </p>
453
      </JobReviewSection>
454
      <JobReviewSection
455
        title={intl.formatMessage(messages.basicHeading)}
456
        linkLabel={intl.formatMessage(messages.infoEditLink)}
457
        link={jobBuilderDetails(locale, job.id)}
458
        hideLink={hideBuilderLinks}
459
      >
460
        <div data-c-grid="gutter">
461
          <div data-c-grid-item="tp(1of2)">
462
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
463
              <FormattedMessage
464
                id="jobBuilder.review.averageAnnualSalary"
465
                defaultMessage="Annual Salary Range"
466
                description="Label for salary information."
467
              />
468
            </p>
469
            <p>
470
              <FormattedMessage
471
                id="jobBuilder.review.tCAdds"
472
                defaultMessage="Talent Cloud will add this."
473
                description="Information will be added placeholder."
474
              />
475
            </p>
476
          </div>
477
          <div data-c-grid-item="tp(1of2)">
478
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
479
              <FormattedMessage
480
                id="jobBuilder.review.languageProfile"
481
                defaultMessage="Language Profile"
482
                description="Information will be added placeholder."
483
              />
484
            </p>
485
            <p>
486
              {job.language_requirement_id
487
                ? intl.formatMessage(
488
                    languageRequirement(job.language_requirement_id),
489
                  )
490
                : ""}
491
            </p>
492
          </div>
493
          <div data-c-grid-item="tp(1of2)">
494
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
495
              <FormattedMessage
496
                id="jobBuilder.review.duration"
497
                defaultMessage="Duration"
498
                description="Label for duration of Term"
499
              />
500
            </p>
501
            <p>
502
              <FormattedMessage
503
                id="jobBuilder.review.months"
504
                defaultMessage="{termMonths, plural, =0 {No Months} one {# Month} other {# Months}}"
505
                description="Length of term in months"
506
                values={{ termMonths: job.term_qty }}
507
              />
508
            </p>
509
          </div>
510
          <div data-c-grid-item="tp(1of2)">
511
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
512
              <FormattedMessage
513
                id="jobBuilder.review.securityClearance"
514
                defaultMessage="Security Clearance"
515
                description="Label for Security Clearance info"
516
              />
517
            </p>
518
            <p>
519
              {job.security_clearance_id
520
                ? intl.formatMessage(
521
                    securityClearance(job.security_clearance_id),
522
                  )
523
                : ""}
524
            </p>
525
          </div>
526
          <div data-c-grid-item="tp(1of2)">
527
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
528
              <FormattedMessage
529
                id="jobBuilder.review.targetStartDate"
530
                defaultMessage="Target Start Date"
531
                description="Label for start date info"
532
              />
533
            </p>
534
            <p>
535
              <FormattedMessage
536
                id="jobBuilder.review.comesLater"
537
                defaultMessage="This comes later."
538
                description="Placeholder for information that comes later"
539
              />
540
            </p>
541
          </div>
542
          <div data-c-grid-item="tp(1of2)">
543
            <p data-c-font-weight="bold" data-c-margin="bottom(quarter)">
544
              <FormattedMessage
545
                id="jobBuilder.review.GovernmentClass"
546
                defaultMessage="Government Classification"
547
                description="Placeholder for information that comes later"
548
              />
549
            </p>
550
            <p>{classificationString(job)}</p>
551
          </div>
552
        </div>
553
      </JobReviewSection>
554
      <JobReviewSection
555
        title={intl.formatMessage(messages.impactHeading)}
556
        linkLabel={intl.formatMessage(messages.impactEditLink)}
557
        link={jobBuilderImpact(locale, job.id)}
558
        hideLink={hideBuilderLinks}
559
      >
560
        <p data-c-margin="bottom(normal)">
561
          {localizeField(locale, job, "dept_impact")}
562
        </p>
563
        <p data-c-margin="bottom(normal)">
564
          {localizeField(locale, job, "team_impact")}
565
        </p>
566
        <p>{localizeField(locale, job, "hire_impact")}</p>
567
      </JobReviewSection>
568
      <JobReviewSection
569
        title={intl.formatMessage(messages.tasksHeading)}
570
        linkLabel={intl.formatMessage(messages.tasksEditLink)}
571
        link={jobBuilderTasks(locale, job.id)}
572
        hideLink={hideBuilderLinks}
573
      >
574
        <ul>
575
          {tasks.map(
576
            (task: JobPosterKeyTask): React.ReactElement => (
577
              <li key={task.id}>
578
                {localizeField(locale, task, "description")}
579
              </li>
580
            ),
581
          )}
582
        </ul>
583
      </JobReviewSection>
584
      {sectionTitle(intl.formatMessage(messages.criteriaSection))}
585
      <JobReviewSection
586
        title={intl.formatMessage(messages.educationalHeading)}
587
        isSubsection
588
        linkLabel={intl.formatMessage(messages.infoEditLink)}
589
        link={jobBuilderDetails(locale, job.id)}
590
        hideLink={hideBuilderLinks}
591
      >
592
        {textToParagraphs(localizeField(locale, job, "education") || "")}
593
      </JobReviewSection>
594
      <JobReviewSection
595
        title={intl.formatMessage(messages.skillsHeading)}
596
        isSubsection
597
        linkLabel={intl.formatMessage(messages.skillsEditLink)}
598
        link={jobBuilderSkills(locale, job.id)}
599
        hideLink={hideBuilderLinks}
600
      >
601
        {essentialCriteria.length === 0 ? (
602
          <p>
603
            <FormattedMessage
604
              id="jobBuilder.review.skills.nullState"
605
              defaultMessage="You haven't added any Nice to Have skills to this poster."
606
              description="The text displayed for skills when you haven't added any skills."
607
            />
608
          </p>
609
        ) : (
610
          essentialCriteria.map((criterion): React.ReactElement | null => {
611
            const skill = getSkillOfCriteria(criterion);
612
            if (skill === null) {
613
              return null;
614
            }
615
            return (
616
              <Criterion
617
                criterion={criterion}
618
                skill={skill}
619
                key={criterion.id}
620
              />
621
            );
622
          })
623
        )}
624
      </JobReviewSection>
625
      <JobReviewSection
626
        title={intl.formatMessage(messages.assetHeading)}
627
        isSubsection
628
        linkLabel={intl.formatMessage(messages.skillsEditLink)}
629
        link={jobBuilderSkills(locale, job.id)}
630
        hideLink={hideBuilderLinks}
631
      >
632
        {assetCriteria.length === 0 ? (
633
          <p>
634
            <FormattedMessage
635
              id="jobBuilder.review.skills.nullState"
636
              defaultMessage="You haven't added any Nice to Have skills to this poster."
637
              description="The text displayed for skills when you haven't added any skills."
638
            />
639
          </p>
640
        ) : (
641
          assetCriteria.map((criterion): React.ReactElement | null => {
642
            const skill = getSkillOfCriteria(criterion);
643
            if (skill === null) {
644
              return null;
645
            }
646
            return (
647
              <Criterion
648
                criterion={criterion}
649
                skill={skill}
650
                key={criterion.id}
651
              />
652
            );
653
          })
654
        )}
655
      </JobReviewSection>
656
      <JobReviewSection
657
        title={intl.formatMessage(messages.languageHeading)}
658
        linkLabel={intl.formatMessage(messages.infoEditLink)}
659
        link={jobBuilderDetails(locale, job.id)}
660
        hideLink={hideBuilderLinks}
661
      >
662
        {/** TODO: get lang data from job */}
663
        {job.language_requirement_id && (
664
          <>
665
            <p
666
              className="job-builder-review-language"
667
              data-c-margin="bottom(normal)"
668
            >
669
              {languageRequirementIcons(job.language_requirement_id)}
670
              {intl.formatMessage(
671
                languageRequirement(job.language_requirement_id),
672
              )}
673
            </p>
674
            <p data-c-margin="bottom(normal)">
675
              {intl.formatMessage(
676
                languageRequirementDescription(job.language_requirement_id),
677
              )}
678
            </p>
679
            <p data-c-margin="top(normal)">
680
              {intl.formatMessage(
681
                languageRequirementContext(job.language_requirement_id),
682
              )}
683
            </p>
684
          </>
685
        )}
686
      </JobReviewSection>
687
      {sectionTitle(intl.formatMessage(messages.cultureSection))}
688
      <JobReviewSection
689
        title={intl.formatMessage(messages.managerHeading)}
690
        isSubsection
691
        linkLabel={intl.formatMessage(messages.managerProfileLink)}
692
        link={managerEditProfile(locale)}
693
        hideLink={hideBuilderLinks}
694
      >
695
        {renderManagerSection(manager, userDeptName, locale)}
696
      </JobReviewSection>
697
      <JobReviewSection
698
        title={intl.formatMessage(messages.workCultureHeading)}
699
        isSubsection
700
        linkLabel={intl.formatMessage(messages.workEnvEditLink)}
701
        link={jobBuilderEnv(locale, job.id)}
702
        hideLink={hideBuilderLinks}
703
      >
704
        {localizeField(locale, job, "culture_summary") && (
705
          <p>{localizeField(locale, job, "culture_summary")}</p>
706
        )}
707
        {localizeField(locale, job, "culture_special") && (
708
          <p>{localizeField(locale, job, "culture_special")}</p>
709
        )}
710
      </JobReviewSection>
711
      <JobReviewSection
712
        title={intl.formatMessage(messages.workEnvHeading)}
713
        isSubsection
714
        linkLabel={intl.formatMessage(messages.workEnvEditLink)}
715
        link={jobBuilderEnv(locale, job.id)}
716
        description={intl.formatMessage(messages.workEnvDescription)}
717
        hideLink={hideBuilderLinks}
718
      >
719
        <JobWorkEnv
720
          teamSize={job.team_size || 0}
721
          selectedEnvOptions={selectedEnvOptions}
722
          envDescription={
723
            localizeField(locale, job, "work_env_description") || ""
724
          }
725
        />
726
      </JobReviewSection>
727
      <JobReviewSection
728
        title={intl.formatMessage(messages.otherInfoHeading)}
729
        isSubsection
730
        linkLabel={intl.formatMessage(messages.infoEditLink)}
731
        link={jobBuilderDetails(locale, job.id)}
732
        hideLink={hideBuilderLinks}
733
      >
734
        <JobWorkCulture job={job} />
735
      </JobReviewSection>
736
    </>
737
  );
738
};
739
740
interface JobReviewProps {
741
  job: Job;
742
  manager: Manager | null;
743
  tasks: JobPosterKeyTask[];
744
  criteria: Criteria[];
745
  // List of all possible skills.
746
  skills: Skill[];
747
  // List of all possible departments.
748
  departments: Department[];
749
  user: User | null;
750
  validForSubmission?: boolean;
751
  handleSubmit: (job: Job) => Promise<void>;
752
  handleContinue: () => void;
753
  handleReturn: () => void;
754
}
755
756
export const JobReview: React.FunctionComponent<
757
  JobReviewProps & WrappedComponentProps
758
> = ({
759
  job,
760
  manager,
761
  tasks,
762
  criteria,
763
  skills,
764
  departments,
765
  user,
766
  validForSubmission = false,
767
  handleSubmit,
768
  handleReturn,
769
  intl,
770
}): React.ReactElement => {
771
  const { locale } = intl;
772
  if (locale !== "en" && locale !== "fr") {
773
    throw new Error("Unknown intl.locale");
774
  }
775
  const [isModalVisible, setIsModalVisible] = useState(false);
776
  const [isSurveyModalVisible, setIsSurveyModalVisible] = useState(false);
777
  const modalId = "job-review-modal";
778
  const modalParentRef = useRef<HTMLDivElement>(null);
779
780
  const filterComments = useCallback(
781
    (comment: Comment): boolean => hasKey(jobReviewLocations, comment.location),
782
    [],
783
  );
784
785
  return (
786
    <>
787
      <div
788
        data-c-container="form"
789
        data-c-padding="top(triple) bottom(triple)"
790
        ref={modalParentRef}
791
      >
792
        <h3
793
          data-c-font-size="h3"
794
          data-c-font-weight="bold"
795
          data-c-margin="bottom(normal)"
796
        >
797
          <FormattedMessage
798
            id="jobBuilder.review.reviewYourPoster"
799
            defaultMessage="Review Your Job Poster for:"
800
            description="Title for Review Job Poster section."
801
          />{" "}
802
          <span data-c-colour="c2">{localizeField(locale, job, "title")}</span>
803
        </h3>
804
        <p data-c-margin="bottom(double)">
805
          <FormattedMessage
806
            id="jobBuilder.review.headsUp"
807
            defaultMessage="Just a heads up! We've rearranged some of your information to help you
808
            understand how an applicant will see it once published."
809
            description="Description under primary title of review section"
810
          />
811
        </p>
812
        <ActivityFeed
813
          jobId={job.id}
814
          isHrAdvisor={false}
815
          generalLocation={LocationId.jobGeneric}
816
          locationMessages={jobReviewLocations}
817
          filterComments={filterComments}
818
        />
819
        <JobReviewDisplay
820
          job={job}
821
          manager={manager}
822
          tasks={tasks}
823
          criteria={criteria}
824
          skills={skills}
825
          departments={departments}
826
          user={user}
827
          hideBuilderLinks={false}
828
        />
829
        <div data-c-grid="gutter">
830
          <div data-c-grid-item="base(1of1)">
831
            <hr data-c-margin="top(normal) bottom(normal)" />
832
          </div>
833
          <div
834
            data-c-alignment="base(centre) tp(left)"
835
            data-c-grid-item="tp(1of2)"
836
          >
837
            <button
838
              data-c-button="outline(c2)"
839
              data-c-radius="rounded"
840
              type="button"
841
              onClick={(): void => handleReturn()}
842
            >
843
              <FormattedMessage
844
                id="jobBuilder.review.button.return"
845
                defaultMessage="Save &amp; Return to Skills"
846
                description="Label of 'Previous Step' button."
847
              />
848
            </button>
849
          </div>
850
          <div
851
            data-c-alignment="base(centre) tp(right)"
852
            data-c-grid-item="tp(1of2)"
853
          >
854
            <a
855
              href={managerJobSummary(locale, job.id)}
856
              title=""
857
              data-c-button="solid(c1)"
858
              data-c-dialog-action="close"
859
              data-c-radius="rounded"
860
              style={{ textDecoration: "none" }}
861
            >
862
              <FormattedMessage
863
                id="jobBuilder.review.button.submit"
864
                defaultMessage="Save and Return to Summary"
865
                description="Label of Job Review Submission Button"
866
              />
867
            </a>
868
          </div>
869
        </div>
870
      </div>
871
      {/* Congrats modal below is temporarily being removed from the review page, until it can be repurposed on the summary page. */}
872
      <div
873
        data-c-dialog-overlay={
874
          isModalVisible || isSurveyModalVisible ? "active" : ""
875
        }
876
      />
877
      {manager === null || manager.is_demo_manager ? (
878
        <DemoSubmitJobModal
879
          isVisible={isModalVisible}
880
          handleCancel={(): void => setIsModalVisible(false)}
881
          parentElement={modalParentRef.current}
882
        />
883
      ) : (
884
        <Modal
885
          id={modalId}
886
          visible={isModalVisible}
887
          onModalCancel={(): void => setIsModalVisible(false)}
888
          onModalConfirm={async (): Promise<void> => {
889
            try {
890
              await handleSubmit(job);
891
              setIsModalVisible(false);
892
              setIsSurveyModalVisible(true);
893
            } catch {
894
              setIsModalVisible(false);
895
            }
896
          }}
897
          parentElement={modalParentRef.current}
898
        >
899
          <Modal.Header>
900
            <div
901
              data-c-background="c1(100)"
902
              data-c-border="bottom(thin, solid, black)"
903
              data-c-padding="normal"
904
            >
905
              <h5
906
                data-c-colour="white"
907
                data-c-font-size="h4"
908
                id={`${modalId}-title`}
909
              >
910
                <FormattedMessage
911
                  id="jobBuilder.review.confirm.title"
912
                  defaultMessage="Congrats! Are You Ready to Submit?"
913
                  description="Title of Submit Confirmation modal."
914
                />
915
              </h5>
916
            </div>
917
          </Modal.Header>
918
          <Modal.Body>
919
            <div data-c-padding="normal">
920
              <p data-c-margin="bottom(normal)">
921
                <FormattedMessage
922
                  id="jobBuilder.review.readyToSubmit"
923
                  defaultMessage="If you're ready to submit your poster, click the Submit button below."
924
                  description="Instructions on how to submit"
925
                />
926
              </p>
927
              <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
928
                <FormattedMessage
929
                  id="jobBuilder.review.whatHappens"
930
                  defaultMessage="What happens next?"
931
                  description="Rhetorical question"
932
                />
933
              </p>
934
              <p data-c-margin="bottom(normal)">
935
                <FormattedMessage
936
                  id="jobBuilder.review.sendYourDraft"
937
                  defaultMessage="Talent Cloud will send your draft to your department's HR advisor who will notify you with comments."
938
                  description="Information about next steps"
939
                />
940
              </p>
941
              <p>
942
                <FormattedMessage
943
                  id="jobBuilder.review.meantime"
944
                  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."
945
                  description="Suggestion on what to do while you wait."
946
                />
947
              </p>
948
            </div>
949
          </Modal.Body>
950
          <Modal.Footer>
951
            <Modal.FooterCancelBtn>
952
              <FormattedMessage
953
                id="jobBuilder.review.confirm.cancel"
954
                defaultMessage="Cancel"
955
                description="Cancel button of Job Review confirmation modal."
956
              />
957
            </Modal.FooterCancelBtn>
958
            <Modal.FooterConfirmBtn>
959
              <FormattedMessage
960
                id="jobBuilder.review.confirm.submit"
961
                defaultMessage="Yes, Submit"
962
                description="Submit button of Job Review confirmation modal."
963
              />
964
            </Modal.FooterConfirmBtn>
965
          </Modal.Footer>
966
        </Modal>
967
      )}
968
      <ManagerSurveyModal
969
        isVisible={isSurveyModalVisible}
970
        parentElement={modalParentRef.current}
971
      />
972
    </>
973
  );
974
};
975
976
export default injectIntl(JobReview);
977