Passed
Push — feature/add-2fa-support ( 23e818...85b1f3 )
by Chris
24:19 queued 11:16
created

ApplicationReview.handleSaveClicked   B

Complexity

Conditions 5

Size

Total Lines 43
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 36
dl 0
loc 43
rs 8.5493
c 0
b 0
f 0
1
import React from "react";
2
import {
3
  injectIntl,
4
  WrappedComponentProps,
5
  FormattedMessage,
6
  defineMessages,
7
} from "react-intl";
8
import className from "classnames";
9
import Swal from "sweetalert2";
10
import * as routes from "../../helpers/routes";
11
import Select, { SelectOption } from "../Select";
12
import { Application } from "../../models/types";
13
import { ReviewStatusId } from "../../models/lookupConstants";
14
15
const messages = defineMessages({
16
  priorityLogo: {
17
    id: "priorityStatus.priorityLogoTitle",
18
    defaultMessage: "Talent cloud priority logo",
19
    description: "Title for Priority Logo Img",
20
  },
21
  veteranLogo: {
22
    id: "veteranStatus.veteranLogoAlt",
23
    defaultMessage: "Talent cloud veteran logo",
24
    description: "Alt Text for Veteran Logo Img",
25
  },
26
  emailCandidate: {
27
    id: "apl.emailCandidateLinkTitle",
28
    defaultMessage: "Email this candidate.",
29
    description: "Title, hover text, for email link.",
30
  },
31
  viewApplicationTitle: {
32
    id: "apl.viewApplicationLinkTitle",
33
    defaultMessage: "View this applicant's application.",
34
    description: "Title, hover text, for View Application Link",
35
  },
36
  viewProfileTitle: {
37
    id: "apl.viewProfileLinkTitle",
38
    defaultMessage: "View this applicant's profile.",
39
    description: "Title, hover text, for View Profile Link",
40
  },
41
  decision: {
42
    id: "apl.decision",
43
    defaultMessage: "Decision",
44
    description: "Decision dropdown label",
45
  },
46
  notReviewed: {
47
    id: "reviewStatus.notReviewed",
48
    defaultMessage: "Not Reviewed",
49
    description: "Decision dropdown label",
50
  },
51
  saving: {
52
    id: "button.saving",
53
    defaultMessage: "Saving...",
54
    description: "Dynamic Save button label",
55
  },
56
  save: {
57
    id: "button.save",
58
    defaultMessage: "Save",
59
    description: "Dynamic Save button label",
60
  },
61
  saved: {
62
    id: "button.saved",
63
    defaultMessage: "Saved",
64
    description: "Dynamic Save button label",
65
  },
66
  addNote: {
67
    id: "apl.addNote",
68
    defaultMessage: "+ Add a Note",
69
    description: "Dynamic Note button label",
70
  },
71
  editNote: {
72
    id: "apl.editNote",
73
    defaultMessage: "Edit Note",
74
    description: "Dynamic Note button label",
75
  },
76
  screenedOut: {
77
    id: "reviewStatus.screenedOut",
78
    defaultMessage: "Screened Out",
79
    description: "Dynamic Note button label",
80
  },
81
  stillThinking: {
82
    id: "reviewStatus.stillThinking",
83
    defaultMessage: "Still Thinking",
84
    description: "Dynamic Note button label",
85
  },
86
  stillIn: {
87
    id: "reviewStatus.stillIn",
88
    defaultMessage: "Still In",
89
    description: "Dynamic Note button label",
90
  },
91
  cancelButton: {
92
    id: "button.cancel",
93
    defaultMessage: "Cancel",
94
    description: "Cancel button label",
95
  },
96
  confirmButton: {
97
    id: "button.confirm",
98
    defaultMessage: "Confirm",
99
    description: "Confirm button for modal dialogue boxes",
100
  },
101
  screenOutConfirm: {
102
    id: "apl.screenOutConfirm",
103
    defaultMessage: "Screen out the candidate?",
104
    description: "Are you sure you want to screen out the candidate worning",
105
  },
106
  screenInConfirm: {
107
    id: "apl.screenInConfirm",
108
    defaultMessage: "Screen the candidate back in?",
109
    description: "Are you sure you want to screen in the candidate warning",
110
  },
111
});
112
113
interface ApplicationReviewProps {
114
  application: Application;
115
  reviewStatusOptions: SelectOption[];
116
  onStatusChange: (applicationId: number, statusId: number | null) => void;
117
  onNotesChange: (applicationId: number, notes: string | null) => void;
118
  isSaving: boolean;
119
}
120
121
interface ApplicationReviewState {
122
  selectedStatusId: number | undefined;
123
}
124
125
class ApplicationReview extends React.Component<
126
  ApplicationReviewProps & WrappedComponentProps,
127
  ApplicationReviewState
128
> {
129
  public constructor(props: ApplicationReviewProps & WrappedComponentProps) {
130
    super(props);
131
    this.state = {
132
      selectedStatusId:
133
        props.application.application_review &&
134
        props.application.application_review.review_status_id
135
          ? props.application.application_review.review_status_id
136
          : undefined,
137
    };
138
    this.handleStatusChange = this.handleStatusChange.bind(this);
139
    this.handleSaveClicked = this.handleSaveClicked.bind(this);
140
    this.showNotes = this.showNotes.bind(this);
141
  }
142
143
  protected handleStatusChange(
144
    event: React.ChangeEvent<HTMLSelectElement>,
145
  ): void {
146
    const value =
147
      event.target.value && !Number.isNaN(Number(event.target.value))
148
        ? Number(event.target.value)
149
        : undefined;
150
    this.setState({ selectedStatusId: value });
151
  }
152
153
  /**
154
   * When save is clicked, it is only necessary to save the status
155
   * @param event
156
   */
157
  protected handleSaveClicked(): void {
158
    const { selectedStatusId } = this.state;
159
    const { application, onStatusChange, intl } = this.props;
160
    const status = selectedStatusId || null;
161
162
    const sectionChange = (
163
      oldStatus: number | null,
164
      newStatus: number | null,
165
    ): boolean => {
166
      const oldIsScreenedOut: boolean =
167
        oldStatus === ReviewStatusId.ScreenedOut;
168
      const newIsScreenedOut: boolean =
169
        newStatus === ReviewStatusId.ScreenedOut;
170
      return oldIsScreenedOut !== newIsScreenedOut;
171
    };
172
    const oldStatus = application.application_review
173
      ? application.application_review.review_status_id
174
      : null;
175
    if (sectionChange(oldStatus, status)) {
176
      const confirmText =
177
        status === ReviewStatusId.ScreenedOut
178
          ? intl.formatMessage(messages.screenOutConfirm)
179
          : intl.formatMessage(messages.screenInConfirm);
180
      Swal.fire({
181
        title: confirmText,
182
        icon: "question",
183
        showCancelButton: true,
184
        confirmButtonColor: "#0A6CBC",
185
        cancelButtonColor: "#F94D4D",
186
        confirmButtonText: intl.formatMessage(messages.confirmButton),
187
        cancelButtonText: intl.formatMessage(messages.cancelButton),
188
      }).then(result => {
189
        if (result.value) {
190
          onStatusChange(application.id, status);
191
        }
192
      });
193
    } else {
194
      onStatusChange(application.id, status);
195
    }
196
  }
197
198
  protected showNotes(): void {
199
    const { application, onNotesChange, intl } = this.props;
200
    const notes =
201
      application.application_review && application.application_review.notes
202
        ? application.application_review.notes
203
        : "";
204
    Swal.fire({
205
      title: intl.formatMessage(messages.editNote),
206
      icon: "question",
207
      input: "textarea",
208
      showCancelButton: true,
209
      confirmButtonColor: "#0A6CBC",
210
      cancelButtonColor: "#F94D4D",
211
      cancelButtonText: intl.formatMessage(messages.cancelButton),
212
      confirmButtonText: intl.formatMessage(messages.save),
213
      inputValue: notes,
214
    }).then(result => {
215
      if (result && result.value !== undefined) {
216
        const value = result.value ? result.value : null;
217
        onNotesChange(application.id, value);
218
      }
219
    });
220
  }
221
222
  public render(): React.ReactElement {
223
    const { application, reviewStatusOptions, isSaving, intl } = this.props;
224
    const l10nReviewStatusOptions = reviewStatusOptions.map(status => ({
225
      value: status.value,
226
      label: intl.formatMessage(messages[status.label]),
227
    }));
228
    const { selectedStatusId } = this.state;
229
    const reviewStatus =
230
      application.application_review &&
231
      application.application_review.review_status
232
        ? application.application_review.review_status.name
233
        : null;
234
    const statusIconClass = className("fas", {
235
      "fa-ban": reviewStatus === "screened_out",
236
      "fa-question-circle": reviewStatus === "still_thinking",
237
      "fa-check-circle": reviewStatus === "still_in",
238
      "fa-exclamation-circle": reviewStatus === null,
239
    });
240
241
    /**
242
     * Returns true only if selectedStatusId matches the review
243
     * status of props.application
244
     */
245
    const isUnchanged = (): boolean => {
246
      if (
247
        application.application_review &&
248
        application.application_review.review_status_id
249
      ) {
250
        return (
251
          application.application_review.review_status_id === selectedStatusId
252
        );
253
      }
254
      return selectedStatusId === undefined;
255
    };
256
257
    const getSaveButtonText = (): string => {
258
      if (isSaving) {
259
        return intl.formatMessage(messages.saving);
260
      }
261
      if (isUnchanged()) {
262
        return intl.formatMessage(messages.saved);
263
      }
264
      return intl.formatMessage(messages.save);
265
    };
266
    const saveButtonText = getSaveButtonText();
267
    const noteButtonText =
268
      application.application_review && application.application_review.notes
269
        ? intl.formatMessage(messages.editNote)
270
        : intl.formatMessage(messages.addNote);
271
272
    return (
273
      <form className="applicant-summary">
274
        <div className="flex-grid middle gutter">
275
          <div className="box lg-1of11 applicant-status">
276
            <i className={statusIconClass} />
277
          </div>
278
279
          <div className="box lg-2of11 applicant-information">
280
            <span
281
              className="name"
282
              data-name={`${application.applicant.user.first_name} ${application.applicant.user.last_name}`}
283
            >
284
              {application.applicant.user.first_name}{" "}
285
              {application.applicant.user.last_name}
286
            </span>
287
            <a
288
              href={`mailto: ${application.applicant.user.email}`}
289
              title={intl.formatMessage(messages.emailCandidate)}
290
              data-email={`${application.applicant.user.email}`}
291
              className="email"
292
            >
293
              {application.applicant.user.email}
294
            </a>
295
            {/* This span only shown for priority applicants */}
296
            {application.applicant.user.is_priority && (
297
              <span className="priority-status">
298
                <i
299
                  aria-hidden="true"
300
                  className="fab fa-product-hunt"
301
                  title={intl.formatMessage(messages.priorityLogo)}
302
                />
303
                <FormattedMessage
304
                  id="priorityStatus.priority"
305
                  defaultMessage="Priority"
306
                  description="Priority"
307
                />
308
              </span>
309
            )}
310
            {/* This span only shown for veterans */}
311
            {(application.veteran_status.name === "current" ||
312
              application.veteran_status.name === "past") && (
313
              <span className="veteran-status">
314
                <img
315
                  alt={intl.formatMessage(messages.veteranLogo)}
316
                  src="/images/icon_veteran.svg"
317
                />{" "}
318
                <FormattedMessage
319
                  id="veteranStatus.veteran"
320
                  defaultMessage="Veteran"
321
                  description="Veteran"
322
                />
323
              </span>
324
            )}
325
          </div>
326
327
          <div className="box lg-2of11 applicant-links">
328
            <a
329
              title={intl.formatMessage(messages.viewApplicationTitle)}
330
              href={routes.managerApplicationShow(intl.locale, application.id)}
331
            >
332
              <i className="fas fa-file-alt" />
333
              <FormattedMessage
334
                id="apl.viewApplication"
335
                defaultMessage="View Application"
336
                description="Button text View Application"
337
              />
338
            </a>
339
            <a
340
              title={intl.formatMessage(messages.viewProfileTitle)}
341
              href={routes.managerApplicantShow(
342
                intl.locale,
343
                application.applicant_id,
344
              )}
345
            >
346
              <i className="fas fa-user" />
347
              <FormattedMessage
348
                id="apl.viewProfile"
349
                defaultMessage="View Profile"
350
                description="Button text View Profile"
351
              />
352
            </a>
353
          </div>
354
355
          <div className="box lg-2of11 applicant-decision" data-clone>
356
            <Select
357
              id={`review_status_${application.id}`}
358
              name="review_status"
359
              label={intl.formatMessage(messages.decision)}
360
              required={false}
361
              selected={selectedStatusId || null}
362
              nullSelection={intl.formatMessage(messages.notReviewed)}
363
              options={l10nReviewStatusOptions}
364
              onChange={this.handleStatusChange}
365
            />
366
          </div>
367
368
          <div className="box lg-2of11 applicant-notes">
369
            <button
370
              className="button--outline"
371
              type="button"
372
              onClick={this.showNotes}
373
            >
374
              {noteButtonText}
375
            </button>
376
          </div>
377
378
          <div className="box lg-2of11 applicant-save-button">
379
            <button
380
              className="button--blue light-bg"
381
              type="button"
382
              onClick={() => this.handleSaveClicked()}
383
            >
384
              <span>{saveButtonText}</span>
385
            </button>
386
          </div>
387
        </div>
388
      </form>
389
    );
390
  }
391
}
392
393
export default injectIntl(ApplicationReview);
394