Passed
Push — dependabot/npm_and_yarn/dev/st... ( 790070...2dbd00 )
by
unknown
17:44 queued 12:22
created

resources/assets/js/components/HRPortal/JobIndexHrPage.tsx   A

Complexity

Total Complexity 12
Complexity/F 0

Size

Lines of Code 289
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 12
eloc 233
mnd 12
bc 12
fnc 0
dl 0
loc 289
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/* eslint-disable camelcase */
2
import React, { useEffect, useMemo } from "react";
3
import ReactDOM from "react-dom";
4
import { defineMessages, IntlShape, useIntl } from "react-intl";
5
import { useDispatch, useSelector } from "react-redux";
6
import JobIndexHr from "./JobIndexHr";
7
import { JobCardProps } from "../JobCard";
8
import { Job, Manager } from "../../models/types";
9
import { classificationString, jobStatus } from "../../models/jobUtil";
10
import { localizeField, Locales } from "../../helpers/localize";
11
import {
12
  hrJobSummary,
13
  hrJobReview,
14
  hrJobPreview,
15
  hrScreeningPlan,
16
} from "../../helpers/routes";
17
import { UnclaimedJobCardProps } from "../UnclaimedJobCard";
18
import { readableDateTime } from "../../helpers/dates";
19
import { find, stringNotEmpty } from "../../helpers/queries";
20
import {
21
  getHrAdvisor as fetchHrAdvisor,
22
  claimJob,
23
} from "../../store/HrAdvisor/hrAdvisorActions";
24
import { getHrAdvisor } from "../../store/HrAdvisor/hrAdvisorSelector";
25
import { RootState } from "../../store/store";
26
import { fetchJobIndex } from "../../store/Job/jobActions";
27
import { getAllJobs } from "../../store/Job/jobSelector";
28
import { departmentName } from "../../models/localizedConstants";
29
import RootContainer from "../RootContainer";
30
import {
31
  getManagers,
32
  getManagerIsUpdatingById,
33
} from "../../store/Manager/managerSelector";
34
import { fetchManager } from "../../store/Manager/managerActions";
35
36
const buttonMessages = defineMessages({
37
  reviewDraft: {
38
    id: "hrJobIndex.reviewDraft",
39
    defaultMessage: "Review Draft",
40
    description: "Text for the link to review job draft.",
41
  },
42
  preview: {
43
    id: "hrJobIndex.preview",
44
    defaultMessage: "Preview Poster",
45
    description: "Text for the link to preview the job poster.",
46
  },
47
  screeningPlan: {
48
    id: "hrJobIndex.viewScreeningPlan",
49
    defaultMessage: "View Assessment Plan",
50
    description: "Text for the link to view the Screening Plan.",
51
  },
52
  viewActivity: {
53
    id: "hrJobIndex.viewActivity",
54
    defaultMessage: "View Activity",
55
    description: "Text for the link to view new entries in the activity feed.",
56
  },
57
  viewSummary: {
58
    id: "hrJobIndex.viewSummary",
59
    defaultMessage: "View Summary",
60
    description: "Text for the link to the Job Summary page.",
61
  },
62
});
63
64
const messages = defineMessages({
65
  loadingManager: {
66
    id: "hrJobIndex.managerLoading",
67
    defaultMessage: "Loading...",
68
    description: "Placehold text for a manager's name, while data is loading.",
69
  },
70
  titleMissing: {
71
    id: "hrJobIndex.jobTitleMissing",
72
    defaultMessage: "Title Missing",
73
    description: "Placeholder text for a missing Job title.",
74
  },
75
  departmentPlaceholder: {
76
    id: "hrJobIndex.departmentPlaceholder",
77
    defaultMessage: "[Department loading]",
78
    description: "Placeholder for department name while it hasn't been loaded.",
79
  },
80
});
81
82
const makeJobAction = (
83
  intl: IntlShape,
84
  locale: Locales,
85
  job: Job,
86
): JobCardProps => {
87
  const jobTitle = localizeField(locale, job, "title");
88
  return {
89
    applicants: 0, // TODO: find real number of applicants.
90
    // TODO: is this intended to be a link as well, like activity?
91
    classification: classificationString(job),
92
    managerTime: 0, // TODO: This isn't recorded yet.
93
    userTime: 0, // TODO: This isn't recorded yet.
94
    owned: true,
95
    title: stringNotEmpty(jobTitle)
96
      ? jobTitle
97
      : intl.formatMessage(messages.titleMissing),
98
    status: jobStatus(job),
99
    activity: {
100
      count: 0, // TODO: requires tracking which comments are "new"
101
      new: {
102
        url: hrJobSummary(locale, job.id), // TODO: this should include a #link
103
        text: intl.formatMessage(buttonMessages.viewActivity),
104
        title: "",
105
      },
106
    },
107
    draft: {
108
      url: hrJobReview(locale, job.id),
109
      text: intl.formatMessage(buttonMessages.reviewDraft),
110
      title: "",
111
    },
112
    preview: {
113
      url: hrJobPreview(locale, job.id),
114
      text: intl.formatMessage(buttonMessages.preview),
115
      title: "",
116
    },
117
    screeningPlan: {
118
      url: hrScreeningPlan(locale, job.id),
119
      text: intl.formatMessage(buttonMessages.screeningPlan),
120
      title: "",
121
    },
122
    summary: {
123
      url: hrJobSummary(locale, job.id),
124
      text: intl.formatMessage(buttonMessages.viewSummary),
125
      title: "",
126
    },
127
  };
128
};
129
130
const makeUnclaimedJob = (
131
  intl: IntlShape,
132
  locale: Locales,
133
  handleClaimJob: () => void,
134
  manager: Manager | null,
135
  job: Job,
136
): UnclaimedJobCardProps => {
137
  const jobTitle = localizeField(locale, job, "title");
138
  return {
139
    jobLink: {
140
      url: hrJobPreview(locale, job.id),
141
      text: stringNotEmpty(jobTitle)
142
        ? jobTitle
143
        : intl.formatMessage(messages.titleMissing),
144
      title: "",
145
    },
146
    createdAt: readableDateTime(locale, job.created_at),
147
    status: jobStatus(job),
148
    hiringManagers: [
149
      manager !== null
150
        ? manager.full_name
151
        : intl.formatMessage(messages.loadingManager),
152
    ],
153
    hrAdvisors: [], // TODO: We can get all claims of an advisor, but don't have an api route for gettings advisors for a job!
154
    handleClaimJob,
155
  };
156
};
157
158
interface JobIndexHrPageProps {
159
  claimedJobIds: number[];
160
  department: string;
161
  jobs: Job[];
162
  managers: Manager[];
163
  handleClaimJob: (jobId: number) => void;
164
}
165
166
const JobIndexHrPage: React.FC<JobIndexHrPageProps> = ({
167
  claimedJobIds,
168
  department,
169
  jobs,
170
  managers,
171
  handleClaimJob,
172
}) => {
173
  const intl = useIntl();
174
  const { locale } = intl;
175
  if (locale !== "en" && locale !== "fr") {
176
    throw new Error("Unknown intl.locale");
177
  }
178
179
  const isClaimed = (job: Job): boolean => claimedJobIds.includes(job.id);
180
  const isUnclaimed = (job: Job): boolean => !isClaimed(job);
181
182
  const jobToAction = (job: Job): JobCardProps =>
183
    makeJobAction(intl, locale, job);
184
185
  const jobToUnclaimed = (job: Job): UnclaimedJobCardProps =>
186
    makeUnclaimedJob(
187
      intl,
188
      locale,
189
      () => handleClaimJob(job.id),
190
      find(managers, job.manager_id),
191
      job,
192
    );
193
194
  const jobActions = jobs.filter(isClaimed).map(jobToAction);
195
  const unclaimedJobs = jobs.filter(isUnclaimed).map(jobToUnclaimed);
196
197
  return (
198
    <JobIndexHr
199
      jobActions={jobActions}
200
      unclaimedJobs={unclaimedJobs}
201
      departmentName={department}
202
    />
203
  );
204
};
205
206
interface JobIndexHrDataFetcherProps {
207
  hrAdvisorId: number;
208
}
209
210
const JobIndexHrDataFetcher: React.FC<JobIndexHrDataFetcherProps> = ({
211
  hrAdvisorId,
212
}) => {
213
  const intl = useIntl();
214
  const dispatch = useDispatch();
215
216
  // Request and select hrAdvisor
217
  useEffect(() => {
218
    dispatch(fetchHrAdvisor(hrAdvisorId));
219
  }, [dispatch, hrAdvisorId]);
220
  const hrAdvisor = useSelector((state: RootState) =>
221
    getHrAdvisor(state, { hrAdvisorId }),
222
  );
223
224
  // Request and select all jobs in department
225
  const departmentId = hrAdvisor?.department_id;
226
  useEffect(() => {
227
    if (departmentId) {
228
      const filters = new Map();
229
      filters.set("department_id", departmentId);
230
      dispatch(fetchJobIndex(filters));
231
    }
232
  }, [departmentId, dispatch]);
233
  const allJobs = useSelector(getAllJobs);
234
  const deptJobs = useMemo(
235
    () =>
236
      hrAdvisor !== null
237
        ? allJobs.filter(
238
            (job: Job): boolean =>
239
              job.department_id === hrAdvisor.department_id,
240
          )
241
        : [],
242
    [allJobs, hrAdvisor],
243
  );
244
245
  // Request and select all managers belonging to the dept jobs
246
  const managers = useSelector(getManagers);
247
  const managersUpdating = useSelector(getManagerIsUpdatingById);
248
  deptJobs.forEach((job: Job): void => {
249
    if (
250
      find(managers, job.manager_id) === null &&
251
      managersUpdating[job.manager_id] !== true
252
    ) {
253
      dispatch(fetchManager(job.manager_id));
254
    }
255
  });
256
257
  // Make claim job function
258
  const claimJobForAdvisor = (jobId: number): any =>
259
    dispatch(claimJob(hrAdvisorId, jobId));
260
261
  const department =
262
    hrAdvisor !== null
263
      ? intl.formatMessage(departmentName(hrAdvisor.department_id))
264
      : intl.formatMessage(messages.departmentPlaceholder);
265
266
  return (
267
    <JobIndexHrPage
268
      claimedJobIds={hrAdvisor !== null ? hrAdvisor.claimed_job_ids : []}
269
      department={department}
270
      jobs={deptJobs}
271
      managers={managers}
272
      handleClaimJob={claimJobForAdvisor}
273
    />
274
  );
275
};
276
277
const container = document.getElementById("job-index-hr");
278
if (container !== null) {
279
  if ("hrAdvisorId" in container.dataset) {
280
    const hrAdvisorId = Number(container.dataset.hrAdvisorId as string);
281
    ReactDOM.render(
282
      <RootContainer>
283
        <JobIndexHrDataFetcher hrAdvisorId={hrAdvisorId} />
284
      </RootContainer>,
285
      container,
286
    );
287
  }
288
}
289