Passed
Push — task/remove-profile-routes ( d6c56c...6a4b34 )
by Tristan
06:15
created

resources/assets/js/components/ApplicationReview/ReviewApplications.tsx   A

Complexity

Total Complexity 3
Complexity/F 0

Size

Lines of Code 219
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 3
eloc 164
dl 0
loc 219
rs 10
c 0
b 0
f 0
mnd 3
bc 3
fnc 0
bpm 0
cpm 0
noi 0
1
import React, { useCallback } from "react";
2
import dayjs from "dayjs";
3
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
4
import { Application, Comment } from "../../models/types";
5
import { SelectOption } from "../Select";
6
import { applicationCategory } from "./helpers";
7
import ReviewCategory from "./ReviewCategory";
8
import ActivityFeed from "../ActivityFeed";
9
import { applicantReviewLocations } from "../../models/localizedConstants";
10
import { LocationId } from "../../models/lookupConstants";
11
import { Portal } from "../../models/app";
12
import { hasKey } from "../../helpers/queries";
13
14
const messages = defineMessages({
15
  underConsiderationTitle: {
16
    id: "review.applications.underConsideration.title",
17
    defaultMessage: "Under Consideration",
18
    description: "Under consideration category title",
19
  },
20
  underConsiderationDescription: {
21
    id: "review.applications.underConsideration.description",
22
    defaultMessage:
23
      "Review the applicants in the Veterans and Canadian Citizens section. If none or very few of these applicants meet the requirements, you can still consider non-Canadian Citizen applications in the Optional Consideration section",
24
    description: "Under consideration category description",
25
  },
26
  optionalConsiderationTitle: {
27
    id: "review.applications.optionalConsideration.title",
28
    defaultMessage: "Optional Consideration",
29
    description: "Optional consideration category title",
30
  },
31
  optionalConsiderationDescription: {
32
    id: "review.applications.optionalConsideration.description",
33
    defaultMessage:
34
      "In this group you will find the applicants who are not Canadian Citizens or do not claim to meet the essential criteria.",
35
    description: "Optional consideration category description",
36
  },
37
  screenedOutTitle: {
38
    id: "review.applications.screenedOut.title",
39
    defaultMessage: "No Longer Under Consideration",
40
    description: "Screened out category title",
41
  },
42
  screenedOutDescription: {
43
    id: "review.applications.screenedOut.description",
44
    defaultMessage: "These applications have already been screened out.",
45
    description: "Screened out category description",
46
  },
47
});
48
49
interface ReviewApplicationsProps {
50
  jobId: number;
51
  title: string;
52
  classification: string;
53
  closeDateTime: Date | null;
54
  applications: Application[];
55
  reviewStatusOptions: SelectOption[];
56
  onStatusChange: (applicationId: number, statusId: number | null) => void;
57
  onBulkStatusChange: (
58
    applicationIds: number[],
59
    statusId: number | null,
60
  ) => void;
61
  onNotesChange: (applicationId: number, notes: string | null) => void;
62
  savingStatuses: { applicationId: number; isSaving: boolean }[];
63
  portal: Portal;
64
}
65
66
const ReviewApplications: React.StatelessComponent<ReviewApplicationsProps> = ({
67
  jobId,
68
  title,
69
  classification,
70
  closeDateTime,
71
  applications,
72
  reviewStatusOptions,
73
  onStatusChange,
74
  onBulkStatusChange,
75
  onNotesChange,
76
  savingStatuses,
77
  portal,
78
}: ReviewApplicationsProps): React.ReactElement => {
79
  const intl = useIntl();
80
  const categories = [
81
    {
82
      id: messages.underConsiderationTitle.id,
83
      title: intl.formatMessage(messages.underConsiderationTitle),
84
      description: intl.formatMessage(messages.underConsiderationDescription),
85
      showScreenOutAll: false,
86
      applications: applications.filter(
87
        (application) => applicationCategory(application) === "primary",
88
      ),
89
      prioritizeVeterans: false,
90
    },
91
    {
92
      id: messages.optionalConsiderationTitle.id,
93
      title: intl.formatMessage(messages.optionalConsiderationTitle),
94
      description: intl.formatMessage(
95
        messages.optionalConsiderationDescription,
96
      ),
97
      showScreenOutAll: true,
98
      applications: applications.filter(
99
        (application): boolean =>
100
          applicationCategory(application) === "optional",
101
      ),
102
      prioritizeVeterans: true,
103
    },
104
    {
105
      id: messages.screenedOutTitle.id,
106
      title: intl.formatMessage(messages.screenedOutTitle),
107
      description: intl.formatMessage(messages.screenedOutDescription),
108
      showScreenOutAll: false,
109
      applications: applications.filter(
110
        (application): boolean =>
111
          applicationCategory(application) === "screened-out",
112
      ),
113
      prioritizeVeterans: true,
114
    },
115
  ];
116
117
  const filterComments = useCallback(
118
    (comment: Comment): boolean =>
119
      hasKey(applicantReviewLocations, comment.location),
120
    [],
121
  );
122
123
  // TODO: Think more carefully about how to handle null fields
124
  const dayCount = dayjs().diff(
125
    closeDateTime ? dayjs(closeDateTime) : dayjs(),
126
    "day",
127
  );
128
129
  return (
130
    <section className="applicant-review container--layout-xl">
131
      <div className="flex-grid gutter">
132
        <div className="box med-1of2 job-title-wrapper">
133
          <span>
134
            {dayCount > 0 ? (
135
              <FormattedMessage
136
                id="review.applications.applicationsAfterClosed"
137
                defaultMessage="Applications for: {jobTitle} {jobClassification}"
138
                description="Welcome header on Job Applications index page"
139
                values={{
140
                  jobTitle: title,
141
                  jobClassification: classification,
142
                }}
143
              />
144
            ) : (
145
              <FormattedMessage
146
                id="review.applications.applicationsBeforeClosed"
147
                defaultMessage="Applications to date: {jobTitle} {jobClassification}"
148
                description="Welcome header on Job Applications index page"
149
                values={{
150
                  jobTitle: title,
151
                  jobClassification: classification,
152
                }}
153
              />
154
            )}
155
          </span>
156
        </div>
157
158
        <div className="box med-1of2 timer-wrapper">
159
          <i className="fas fa-stopwatch" />
160
          {` `}
161
          {dayCount >= 0 ? (
162
            <FormattedMessage
163
              id="job.daysAfterClosed"
164
              defaultMessage="{dayCount, plural,
165
              =0 {Closes Today}
166
            one {# Day Since Close}
167
          other {# Days Since Close}
168
        }"
169
              description="Shows the number of days after the job closed."
170
              values={{
171
                dayCount: Math.abs(dayCount),
172
              }}
173
            />
174
          ) : (
175
            <FormattedMessage
176
              id="job.daysBeforeClosed"
177
              defaultMessage="{dayCount, plural,
178
            one {# Day}
179
          other {# Days}
180
          } Until Close"
181
              description="Shows the number of days before the job closes."
182
              values={{
183
                dayCount: Math.abs(dayCount),
184
              }}
185
            />
186
          )}
187
        </div>
188
      </div>
189
      <div data-clone>
190
        <div data-c-margin="bottom(1)">
191
          <ActivityFeed
192
            jobId={jobId}
193
            isHrAdvisor={portal === "hr"}
194
            generalLocation={LocationId.applicantsGeneric}
195
            locationMessages={applicantReviewLocations}
196
            filterComments={filterComments}
197
          />
198
        </div>
199
      </div>
200
      {categories.map(
201
        (category): React.ReactElement => (
202
          <ReviewCategory
203
            key={category.id}
204
            {...category}
205
            reviewStatusOptions={reviewStatusOptions}
206
            onStatusChange={onStatusChange}
207
            onNotesChange={onNotesChange}
208
            savingStatuses={savingStatuses}
209
            onBulkStatusChange={onBulkStatusChange}
210
            portal={portal}
211
          />
212
        ),
213
      )}
214
    </section>
215
  );
216
};
217
218
export default ReviewApplications;
219