Passed
Push — feature/application-fit-functi... ( bf99e7...8cf105 )
by Yonathan
05:01
created

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

Complexity

Total Complexity 24
Complexity/F 0

Size

Lines of Code 975
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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