Passed
Push — task/common-translation-packag... ( dbb970...52324d )
by Tristan
06:50 queued 11s
created

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

Complexity

Total Complexity 14
Complexity/F 0

Size

Lines of Code 298
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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