Passed
Push — feature/timeline ( d18b1c...ddfcdc )
by Yonathan
06:15 queued 11s
created

resources/assets/js/components/JobBuilder/Impact/JobImpact.tsx   A

Complexity

Total Complexity 9
Complexity/F 0

Size

Lines of Code 484
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 9
eloc 389
mnd 9
bc 9
fnc 0
dl 0
loc 484
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/* eslint-disable @typescript-eslint/camelcase */
2
import React, { useState, useRef, useEffect } from "react";
3
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
4
import * as Yup from "yup";
5
import { Formik, Form, Field, FastField } from "formik";
6
import nprogress from "nprogress";
7
import { Job, Department } from "../../../models/types";
8
import { emptyJob } from "../../../models/jobUtil";
9
import Modal from "../../Modal";
10
import { validationMessages } from "../../Form/Messages";
11
import TextAreaInput from "../../Form/TextAreaInput";
12
import { find } from "../../../helpers/queries";
13
import { getLocale } from "../../../helpers/localize";
14
15
interface JobImpactProps {
16
  /** Optional Job to prepopulate form values from. */
17
  job: Job | null;
18
  /** The list of known departments. Used to determine the Department statement. */
19
  departments: Department[];
20
  /** Function to run after successful form validation.
21
   *  It must return true if the submission was successful, false otherwise.
22
   */
23
  handleSubmit: (values: Job) => Promise<boolean>;
24
  // The function to run when user clicks Prev Page
25
  handleReturn: () => void;
26
  /** Function to run when modal cancel is clicked. */
27
  handleModalCancel: () => void;
28
  /** Function to run when modal confirm is clicked. */
29
  handleModalConfirm: () => void;
30
31
  jobIsComplete: boolean;
32
  handleSkipToReview: () => Promise<void>;
33
}
34
35
interface ImpactFormValues {
36
  teamImpact: string;
37
  hireImpact: string;
38
}
39
40
const messages = defineMessages({
41
  hireLabel: {
42
    id: "jobBuilder.impact.hireLabel",
43
    defaultMessage: "Hire Impact Statement",
44
    description: "Label for hire impact statement text area",
45
  },
46
  teamLabel: {
47
    id: "jobBuilder.impact.teamLabel",
48
    defaultMessage: "Team Impact Statement",
49
    description: "Label for team impact statement text area",
50
  },
51
  hirePlaceholder: {
52
    id: "jobBuilder.impact.hirePlaceholder",
53
    defaultMessage: "Remember, don't use Government speak...",
54
    description: "",
55
  },
56
  teamPlaceholder: {
57
    id: "jobBuilder.impact.teamPlaceholder",
58
    defaultMessage: "Try for a casual, frank, friendly tone...",
59
    description: "",
60
  },
61
});
62
63
const updateJobWithValues = (
64
  initialJob: Job,
65
  locale: "en" | "fr",
66
  { teamImpact, hireImpact }: ImpactFormValues,
67
  deptImpacts: { en: string; fr: string },
68
): Job => ({
69
  ...initialJob,
70
  dept_impact: deptImpacts,
71
  team_impact: {
72
    ...initialJob.team_impact,
73
    [locale]: teamImpact,
74
  },
75
  hire_impact: {
76
    ...initialJob.hire_impact,
77
    [locale]: hireImpact,
78
  },
79
});
80
81
const determineDeptImpact = (
82
  departments: Department[],
83
  job: Job | null,
84
): { en: string; fr: string } => {
85
  if (job === null || job.department_id === null) {
86
    return { en: "", fr: "" };
87
  }
88
  const dept = find(departments, job.department_id);
89
  if (dept === null) {
90
    return { en: "", fr: "" };
91
  }
92
  return {
93
    en: dept.impact.en,
94
    fr: dept.impact.fr,
95
  };
96
};
97
98
const deptImpactStatement = (
99
  departments: Department[],
100
  job: Job | null,
101
  deptImpacts: { en: string; fr: string },
102
  locale: "en" | "fr",
103
): React.ReactElement => {
104
  if (job === null || job.department_id === null) {
105
    return (
106
      <p data-c-margin="bottom(double)">
107
        <FormattedMessage
108
          id="jobBuilder.impact.selectDepartment"
109
          defaultMessage="You must select a Department for this Job."
110
          description="Message warning user that they must have a department selected to complete impact statements."
111
        />
112
      </p>
113
    );
114
  }
115
  if (departments.length === 0) {
116
    return (
117
      <p data-c-margin="bottom(double)">
118
        <i
119
          aria-hidden="true"
120
          className="fa fa-spinner fa-spin"
121
          data-c-margin="right"
122
        />
123
        <FormattedMessage
124
          id="jobBuilder.impact.departmentsLoading"
125
          defaultMessage="Loading department data..."
126
          description="Placeholder message while department data is being retrieved from the server."
127
        />
128
      </p>
129
    );
130
  }
131
132
  return (
133
    <p id="deptImpactStatement" data-c-margin="bottom(double)">
134
      {deptImpacts[locale] || (
135
        <FormattedMessage
136
          id="jobBuilder.impact.unknownDepartment"
137
          defaultMessage="Error: Unknown Department selected."
138
          description="Error message shown when the job has a department selected for which data has not been passed to this component."
139
        />
140
      )}
141
    </p>
142
  );
143
};
144
145
const JobImpact: React.FunctionComponent<JobImpactProps> = ({
146
  departments,
147
  job,
148
  handleSubmit,
149
  handleReturn,
150
  handleModalCancel,
151
  handleModalConfirm,
152
  jobIsComplete,
153
  handleSkipToReview,
154
}): React.ReactElement => {
155
  const intl = useIntl();
156
  const locale = getLocale(intl.locale);
157
  const modalId = "impact-dialog";
158
  const [isModalVisible, setIsModalVisible] = useState(false);
159
  const modalParentRef = useRef<HTMLDivElement>(null);
160
161
  const initialTeamImpact = job ? job.team_impact[locale] : null;
162
  const initialHireImpact = job ? job.hire_impact[locale] : null;
163
  const initialValues: ImpactFormValues = {
164
    teamImpact: initialTeamImpact || "",
165
    hireImpact: initialHireImpact || "",
166
  };
167
168
  const validationSchema = Yup.object().shape({
169
    teamImpact: Yup.string().required(
170
      intl.formatMessage(validationMessages.required),
171
    ),
172
    hireImpact: Yup.string().required(
173
      intl.formatMessage(validationMessages.required),
174
    ),
175
  });
176
177
  const deptImpacts: { en: string; fr: string } = determineDeptImpact(
178
    departments,
179
    job,
180
  );
181
182
  const updateValuesAndReturn = (values: ImpactFormValues): void => {
183
    // The following only triggers after validations pass
184
    nprogress.start();
185
    handleSubmit(
186
      updateJobWithValues(job || emptyJob(), locale, values, deptImpacts),
187
    ).then((isSuccessful: boolean): void => {
188
      if (isSuccessful) {
189
        nprogress.done();
190
        handleReturn();
191
      }
192
    });
193
  };
194
195
  return (
196
    <section ref={modalParentRef}>
197
      <div data-c-container="form" data-c-padding="top(triple) bottom(triple)">
198
        <h3
199
          data-c-font-size="h3"
200
          data-c-font-weight="bold"
201
          data-c-margin="bottom(double)"
202
        >
203
          <FormattedMessage
204
            id="jobBuilder.impact.title"
205
            defaultMessage="Create an Impact Statement"
206
            description="Header of Job Poster Builder Impact Step"
207
          />
208
        </h3>
209
        <ul data-c-margin="bottom(double)">
210
          <li>
211
            <FormattedMessage
212
              id="jobBuilder.impact.points.opportunity"
213
              defaultMessage="Working in the federal government offers an important opportunity to have a broad impact for Canadians."
214
              description="Bullet Point on Job Poster Builder Impact Step"
215
            />
216
          </li>
217
          <li>
218
            <FormattedMessage
219
              id="jobBuilder.impact.points.highlight"
220
              defaultMessage="This is your chance to highlight what makes your work valuable and interesting."
221
              description="Bullet Point on Job Poster Builder Impact Step"
222
            />
223
          </li>
224
          <li>
225
            <FormattedMessage
226
              id="jobBuilder.impact.points.counts"
227
              defaultMessage="Your impact statement is the first thing that applicants will see when they click your job poster so make sure it counts!"
228
              description="Bullet Point on Job Poster Builder Impact Step"
229
            />
230
          </li>
231
        </ul>
232
        <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
233
          <FormattedMessage
234
            id="jobBuilder.impact.header.department"
235
            defaultMessage="How our department makes an impact:"
236
            description="Header of Department Impact Section on Job Poster Builder Impact Step"
237
          />
238
        </p>
239
        {deptImpactStatement &&
240
          deptImpactStatement(departments, job, deptImpacts, locale)}
241
        <Formik
242
          enableReinitialize
243
          initialValues={initialValues}
244
          validationSchema={validationSchema}
245
          onSubmit={(values, actions): void => {
246
            nprogress.start();
247
            // The following only triggers after validations pass
248
            handleSubmit(
249
              updateJobWithValues(
250
                job || emptyJob(),
251
                locale,
252
                values,
253
                deptImpacts,
254
              ),
255
            )
256
              .then((isSuccessful: boolean): void => {
257
                if (isSuccessful) {
258
                  nprogress.done();
259
                  setIsModalVisible(true);
260
                }
261
              })
262
              .finally((): void => {
263
                actions.setSubmitting(false); // Required by Formik to finish the submission cycle
264
              });
265
          }}
266
        >
267
          {({ values, isSubmitting }): React.ReactElement => (
268
            <>
269
              <Form id="form" data-c-grid="gutter">
270
                <div data-c-grid-item="base(1of1)" data-c-input="textarea">
271
                  <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
272
                    <FormattedMessage
273
                      id="jobBuilder.impact.teamHeader"
274
                      defaultMessage="How our team makes an impact:"
275
                      description="Header of Job Poster Builder Team Impact Section"
276
                    />
277
                  </p>
278
                  <p data-c-margin="bottom(normal)">
279
                    <FormattedMessage
280
                      id="jobBuilder.impact.teamBody"
281
                      defaultMessage="Describe the value your team/service/initiative brings to Canadians. It doesn’t matter if your work is direct to citizens or back office, innovative or maintenance, top priority or ongoing. Describe how it contributes to making Canada better the way you would to someone who knows nothing about your work."
282
                      description="Body of Job Poster Builder Team Impact Section"
283
                    />
284
                  </p>
285
                  <div>
286
                    <FastField
287
                      name="teamImpact"
288
                      id="TeamImpact"
289
                      placeholder={intl.formatMessage(messages.teamPlaceholder)}
290
                      label={intl.formatMessage(messages.teamLabel)}
291
                      required
292
                      component={TextAreaInput}
293
                    />
294
                  </div>
295
                </div>
296
                <div data-c-grid-item="base(1of1)" data-c-input="textarea">
297
                  <p data-c-font-weight="bold" data-c-margin="bottom(normal)">
298
                    <FormattedMessage
299
                      id="jobBuilder.impact.hireHeader"
300
                      defaultMessage="How the new hire makes an impact:"
301
                      description="Header of Job Poster Builder Hire Impact Section"
302
                    />
303
                  </p>
304
                  <p data-c-margin="bottom(normal)">
305
                    <FormattedMessage
306
                      id="jobBuilder.impact.hireBody"
307
                      defaultMessage="Describe how the new hire will contribute in this role. Focus on the value they’ll bring, not on specific tasks (you’ll provide these later on). For example “In this role, you’ll contribute to…” or, “As a member of this team, you’ll be responsible for helping us…”"
308
                      description="Body of Job Poster Builder Hire Impact Section"
309
                    />
310
                  </p>
311
                  <div>
312
                    <FastField
313
                      id="HireImpact"
314
                      name="hireImpact"
315
                      label={intl.formatMessage(messages.hireLabel)}
316
                      placeholder={intl.formatMessage(messages.hirePlaceholder)}
317
                      required
318
                      component={TextAreaInput}
319
                    />
320
                  </div>
321
                </div>
322
                <div data-c-grid="gutter" data-c-grid-item="base(1of1)">
323
                  <div data-c-grid-item="base(1of1)">
324
                    <hr data-c-margin="top(normal) bottom(normal)" />
325
                  </div>
326
                  <div
327
                    data-c-alignment="base(centre) tp(left)"
328
                    data-c-grid-item="tp(1of2)"
329
                  >
330
                    <button
331
                      data-c-button="outline(c2)"
332
                      data-c-radius="rounded"
333
                      type="button"
334
                      disabled={isSubmitting}
335
                      onClick={(): void => {
336
                        updateValuesAndReturn(values);
337
                      }}
338
                    >
339
                      <FormattedMessage
340
                        id="jobBuilder.impact.button.return"
341
                        defaultMessage="Save & Return to Work Environment"
342
                        description="Label for Save & Return button on Impact form."
343
                      />
344
                    </button>
345
                  </div>
346
                  <div
347
                    data-c-alignment="base(centre) tp(right)"
348
                    data-c-grid-item="tp(1of2)"
349
                  >
350
                    <button
351
                      data-c-button="solid(c1)"
352
                      data-c-radius="rounded"
353
                      type="submit"
354
                      disabled={isSubmitting}
355
                    >
356
                      <FormattedMessage
357
                        id="jobBuilder.impact.button.next"
358
                        defaultMessage="Save & Preview"
359
                        description="Label for Save & Preview button on Impact form."
360
                      />
361
                    </button>
362
                  </div>
363
                </div>
364
              </Form>
365
              <Modal
366
                id={modalId}
367
                parentElement={modalParentRef.current}
368
                visible={isModalVisible}
369
                onModalConfirm={(): void => {
370
                  handleModalConfirm();
371
                  setIsModalVisible(false);
372
                }}
373
                onModalCancel={(): void => {
374
                  handleModalCancel();
375
                  setIsModalVisible(false);
376
                }}
377
                onModalMiddle={(): void => {
378
                  handleSkipToReview().finally((): void =>
379
                    setIsModalVisible(false),
380
                  );
381
                }}
382
              >
383
                <Modal.Header>
384
                  <div
385
                    data-c-background="c1(100)"
386
                    data-c-border="bottom(thin, solid, black)"
387
                    data-c-padding="normal"
388
                  >
389
                    <h5
390
                      data-c-colour="white"
391
                      data-c-font-size="h4"
392
                      id={`${modalId}-title`}
393
                    >
394
                      <FormattedMessage
395
                        id="jobBuilder.impact.modalTitle"
396
                        defaultMessage="Awesome work!"
397
                        description="Title of modal dialog for Impact review."
398
                      />
399
                    </h5>
400
                  </div>
401
                </Modal.Header>
402
                <Modal.Body>
403
                  <div
404
                    data-c-border="bottom(thin, solid, black)"
405
                    data-c-padding="normal"
406
                    id={`${modalId}-description`}
407
                  >
408
                    <p>
409
                      <FormattedMessage
410
                        id="jobBuilder.impact.modalDescription"
411
                        defaultMessage="Here's a preview of the Impact Statement you just entered. Feel free to go back and edit things or move to the next step if you're happy with it."
412
                        description="Description of modal dialog for Impact review."
413
                      />
414
                    </p>
415
                  </div>
416
                  <div
417
                    data-c-background="grey(20)"
418
                    data-c-border="bottom(thin, solid, black)"
419
                    data-c-padding="normal"
420
                  >
421
                    <div
422
                      className="manager-job-card"
423
                      data-c-background="white(100)"
424
                      data-c-padding="normal"
425
                      data-c-radius="rounded"
426
                    >
427
                      <h4
428
                        data-c-border="bottom(thin, solid, black)"
429
                        data-c-font-size="h4"
430
                        data-c-font-weight="600"
431
                        data-c-margin="bottom(normal)"
432
                        data-c-padding="bottom(normal)"
433
                      >
434
                        <FormattedMessage
435
                          id="jobBuilder.impactPreview.title"
436
                          defaultMessage="Impact"
437
                          description="Heading for Impact preview on modal dialog."
438
                        />
439
                      </h4>
440
                      <p id="deptImpactPreview" data-c-margin="bottom(normal)">
441
                        {deptImpacts[locale]}
442
                      </p>
443
                      <p data-c-margin="bottom(normal)">{values.teamImpact}</p>
444
                      <p>{values.hireImpact}</p>
445
                    </div>
446
                  </div>
447
                </Modal.Body>
448
                <Modal.Footer>
449
                  <Modal.FooterCancelBtn>
450
                    <FormattedMessage
451
                      id="jobBuilder.impact.button.goBack"
452
                      defaultMessage="Go Back"
453
                      description="Label for Go Back button on Impact review modal."
454
                    />
455
                  </Modal.FooterCancelBtn>
456
                  {jobIsComplete && (
457
                    <Modal.FooterMiddleBtn>
458
                      <FormattedMessage
459
                        id="jobBuilder.impact.button.skipToReview"
460
                        defaultMessage="Skip to Review"
461
                        description="Label for Skip to Review button on Impact review modal."
462
                      />
463
                    </Modal.FooterMiddleBtn>
464
                  )}
465
                  <Modal.FooterConfirmBtn>
466
                    <FormattedMessage
467
                      id="jobBuilder.impact.button.nextStep"
468
                      defaultMessage="Next Step"
469
                      description="Label for Next Step button on Impact review modal."
470
                    />
471
                  </Modal.FooterConfirmBtn>
472
                </Modal.Footer>
473
              </Modal>
474
            </>
475
          )}
476
        </Formik>
477
      </div>
478
      <div data-c-dialog-overlay={isModalVisible ? "active" : ""} />
479
    </section>
480
  );
481
};
482
483
export default JobImpact;
484