Passed
Push — task/experience-skills-batch-r... ( c2c7c9...5d0b10 )
by Tristan
13:25 queued 09:20
created

resources/assets/js/components/AssessmentPlan/RatingGuideClipboard.tsx   A

Complexity

Total Complexity 29
Complexity/F 0

Size

Lines of Code 384
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 308
dl 0
loc 384
rs 10
c 0
b 0
f 0
wmc 29
mnd 29
bc 29
fnc 0
bpm 0
cpm 0
noi 0
1
/* eslint-disable no-lonely-if */
2
import React from "react";
3
import {
4
  injectIntl,
5
  WrappedComponentProps,
6
  FormattedMessage,
7
  MessageDescriptor,
8
} from "react-intl";
9
import { connect } from "react-redux";
10
import { RootState } from "../../store/store";
11
import {
12
  Criteria,
13
  Assessment,
14
  RatingGuideQuestion,
15
  RatingGuideAnswer,
16
  Skill,
17
} from "../../models/types";
18
import {
19
  skillLevelName,
20
  criteriaType,
21
  assessmentType,
22
} from "../../models/localizedConstants";
23
import { getAssessmentsByJob } from "../../store/Assessment/assessmentSelectorComplex";
24
import { getCriteriaByJob } from "../../store/Job/jobSelector";
25
import { getSkills } from "../../store/Skill/skillSelector";
26
import { getRatingGuideQuestionsByJob } from "../../store/RatingGuideQuestion/ratingGuideQuestionSelectors";
27
import { getRatingGuideAnswersByJob } from "../../store/RatingGuideAnswer/ratingGuideAnswerSelectors";
28
import { copyElementContents } from "../../helpers/clipboard";
29
import { Locales, getLocale, localizeFieldNonNull } from "../../helpers/localize";
30
31
export interface ClipboardTableRowProps {
32
  id: string;
33
  title: string;
34
  question: string | null;
35
  skillLevel: string;
36
  criteriaTypeName: string;
37
  skillName: string;
38
  skillDescription: string;
39
  modelAnswer: string | null;
40
}
41
42
export const clipboardData = (
43
  assessments: Assessment[],
44
  criteria: Criteria[],
45
  skills: Skill[],
46
  ratingGuideQuestions: RatingGuideQuestion[],
47
  ratingGuideAnswers: RatingGuideAnswer[],
48
  locale: Locales,
49
  formatMessage: (message: MessageDescriptor) => string,
50
  narrativeReview?: Assessment[],
51
): ClipboardTableRowProps[] => {
52
  let narrativeData: ClipboardTableRowProps[] = [];
53
  if (narrativeReview !== undefined) {
54
    narrativeData = narrativeReview.map(
55
      (narrative: Assessment): ClipboardTableRowProps => {
56
        const narrativeCriterion = criteria.find(
57
          (criterion: Criteria): boolean =>
58
            criterion.id === narrative.criterion_id,
59
        );
60
        const narrativeSkill = skills.find((skill: Skill): boolean => {
61
          if (narrativeCriterion === undefined) return false;
62
          return skill.id === narrativeCriterion.skill_id;
63
        });
64
        return {
65
          title: formatMessage(assessmentType(narrative.assessment_type_id)),
66
          question: null,
67
          skillLevel:
68
            narrativeCriterion === undefined || narrativeSkill === undefined
69
              ? ""
70
              : formatMessage(
71
                  skillLevelName(
72
                    narrativeCriterion.skill_level_id,
73
                    narrativeSkill.skill_type_id,
74
                  ),
75
                ),
76
          criteriaTypeName:
77
            narrativeCriterion === undefined
78
              ? ""
79
              : formatMessage(
80
                  criteriaType(narrativeCriterion.criteria_type_id),
81
                ),
82
          skillName:
83
            narrativeSkill === undefined ? "" : localizeFieldNonNull(locale, narrativeSkill, "name"),
84
          skillDescription:
85
            narrativeSkill === undefined
86
              ? ""
87
              : localizeFieldNonNull(locale, narrativeSkill, "description"),
88
          modelAnswer: "",
89
          id:
90
            narrativeCriterion === undefined
91
              ? ""
92
              : `A${narrative.assessment_type_id}-Q${narrative.id}-T${narrativeCriterion.criteria_type_id}`,
93
        };
94
      },
95
    );
96
  }
97
  let availableAnswers = ratingGuideAnswers.filter(
98
    (answer: RatingGuideAnswer): boolean => answer.criterion_id !== null,
99
  );
100
  availableAnswers = availableAnswers.filter(
101
    (answer: RatingGuideAnswer): boolean => {
102
      const questionByAnswer = ratingGuideQuestions.find(
103
        (question: RatingGuideQuestion): boolean =>
104
          question.id === answer.rating_guide_question_id,
105
      );
106
      return (
107
        questionByAnswer !== undefined &&
108
        assessments.find(
109
          (assessment: Assessment): boolean =>
110
            assessment.criterion_id === answer.criterion_id &&
111
            questionByAnswer.assessment_type_id ===
112
              assessment.assessment_type_id,
113
        ) !== undefined
114
      );
115
    },
116
  );
117
  const ratingData = availableAnswers.map(
118
    (answer): ClipboardTableRowProps => {
119
      const criterionByAnswer = criteria.find(
120
        (criterion: Criteria): boolean => criterion.id === answer.criterion_id,
121
      );
122
      const skillByCriterion = skills.find((skill: Skill): boolean => {
123
        if (criterionByAnswer === undefined) return false;
124
        return skill.id === criterionByAnswer.skill_id;
125
      });
126
      const questionByAnswer = ratingGuideQuestions.find(
127
        (question: RatingGuideQuestion): boolean =>
128
          question.id === answer.rating_guide_question_id,
129
      );
130
      return {
131
        title:
132
          questionByAnswer === undefined
133
            ? ""
134
            : formatMessage(
135
                assessmentType(questionByAnswer.assessment_type_id),
136
              ),
137
        question:
138
          questionByAnswer === undefined ? null : questionByAnswer.question,
139
        skillLevel:
140
          criterionByAnswer === undefined || skillByCriterion === undefined
141
            ? ""
142
            : formatMessage(
143
                skillLevelName(
144
                  criterionByAnswer.skill_level_id,
145
                  skillByCriterion.skill_type_id,
146
                ),
147
              ),
148
        criteriaTypeName:
149
          criterionByAnswer === undefined
150
            ? ""
151
            : formatMessage(criteriaType(criterionByAnswer.criteria_type_id)),
152
        skillName:
153
          skillByCriterion === undefined ? "" : localizeFieldNonNull(locale, skillByCriterion, "name"),
154
        skillDescription:
155
          skillByCriterion === undefined
156
            ? ""
157
            : localizeFieldNonNull(locale, skillByCriterion, "description"),
158
        modelAnswer: answer.expected_answer ? answer.expected_answer : "",
159
        id:
160
          questionByAnswer === undefined || criterionByAnswer === undefined
161
            ? ""
162
            : `A${questionByAnswer.assessment_type_id}-Q${questionByAnswer.id}-T${criterionByAnswer.criteria_type_id}-AN${answer.id}`,
163
      };
164
    },
165
  );
166
  const compareRowProps = (
167
    a: ClipboardTableRowProps,
168
    b: ClipboardTableRowProps,
169
  ): number => {
170
    let num = 0;
171
    if (a.title > b.title) {
172
      num = 1;
173
    } else if (a.title < b.title) {
174
      num = -1;
175
    } else {
176
      if (a.criteriaTypeName > b.criteriaTypeName) {
177
        num = -1; // Essential should be listed before Asset
178
      } else if (a.criteriaTypeName < b.criteriaTypeName) {
179
        num = 1;
180
      } else {
181
        if (a.question === null || b.question === null) {
182
          num = 0;
183
        } else if (a.question > b.question) {
184
          num = 1;
185
        } else if (a.question < b.question) {
186
          num = -1;
187
        }
188
      }
189
    }
190
    return num;
191
  };
192
  let data: ClipboardTableRowProps[] = [];
193
  if (narrativeData.length > 0) {
194
    data = narrativeData.concat(ratingData);
195
  } else {
196
    data = ratingData;
197
  }
198
  data.sort(compareRowProps);
199
  return data;
200
};
201
202
const cloneAndCleanTableRowProps = (
203
  data: ClipboardTableRowProps[],
204
): ClipboardTableRowProps[] => {
205
  const cleanedData: ClipboardTableRowProps[] = JSON.parse(
206
    JSON.stringify(data),
207
  );
208
  const lastIndex: number = cleanedData.length - 1; // Takes out duplicate titles and questions
209
  for (let i: number = lastIndex; i >= 0; i -= 1) {
210
    const j: number = i + 1;
211
    if (j <= lastIndex && cleanedData[j] !== undefined) {
212
      if (cleanedData[i].title === cleanedData[j].title) {
213
        cleanedData[j].title = "";
214
      }
215
      if (cleanedData[i].question === cleanedData[j].question) {
216
        cleanedData[j].question = "";
217
      }
218
    }
219
  }
220
  return cleanedData;
221
};
222
223
const TableRow: React.FunctionComponent<ClipboardTableRowProps> = ({
224
  title,
225
  question,
226
  criteriaTypeName,
227
  skillLevel,
228
  skillName,
229
  skillDescription,
230
  modelAnswer,
231
}): React.ReactElement => (
232
  <tr>
233
    <th scope="row">{title}</th>
234
    <td>{question}</td>
235
    <td>{criteriaTypeName}</td>
236
    <td>{skillLevel}</td>
237
    <td>{skillName}</td>
238
    <td>{skillDescription}</td>
239
    <td>{modelAnswer}</td>
240
  </tr>
241
);
242
243
interface TableProps {
244
  assessments: Assessment[];
245
  criteria: Criteria[];
246
  skills: Skill[];
247
  ratingGuideQuestions: RatingGuideQuestion[];
248
  ratingGuideAnswers: RatingGuideAnswer[];
249
  narrativeReview?: Assessment[];
250
}
251
252
const RatingGuideClipboard: React.FunctionComponent<TableProps &
253
  WrappedComponentProps> = ({
254
  assessments,
255
  criteria,
256
  skills,
257
  ratingGuideQuestions,
258
  ratingGuideAnswers,
259
  intl,
260
  narrativeReview,
261
}): React.ReactElement => {
262
  const rows = cloneAndCleanTableRowProps(
263
    clipboardData(
264
      assessments,
265
      criteria,
266
      skills,
267
      ratingGuideQuestions,
268
      ratingGuideAnswers,
269
      getLocale(intl.locale),
270
      intl.formatMessage,
271
      narrativeReview,
272
    ),
273
  );
274
  const tableRef = React.createRef<HTMLTableElement>();
275
  return (
276
    <div data-c-alignment="center">
277
      <button
278
        data-c-button="solid(c5)"
279
        type="button"
280
        onClick={(): void => {
281
          if (tableRef.current !== null) {
282
            copyElementContents(tableRef.current);
283
          }
284
        }}
285
      >
286
        <FormattedMessage
287
          id="ratingGuideBuilder.copyButton"
288
          defaultMessage="Click to Copy This Ratings Guide to Your Clipboard"
289
          description="Text for the 'copy ratings guide' button."
290
        />
291
      </button>
292
293
      <div className="screening-plan-layout">
294
        <section className="plan-table">
295
          <table ref={tableRef}>
296
            <thead>
297
              <tr>
298
                <th scope="col">
299
                  <FormattedMessage
300
                    id="ratingGuideBuilder.titleHeading"
301
                    defaultMessage="Title"
302
                    description="Text for the Title column heading."
303
                  />
304
                </th>
305
                <th scope="col">
306
                  <FormattedMessage
307
                    id="ratingGuideBuilder.questionHeading"
308
                    defaultMessage="Question"
309
                    description="Text for the Question column heading."
310
                  />
311
                </th>
312
                <th scope="col">
313
                  <FormattedMessage
314
                    id="ratingGuideBuilder.criteriaTypeHeading"
315
                    defaultMessage="Criteria Type"
316
                    description="Text for the Criteria Type column heading."
317
                  />
318
                </th>
319
                <th scope="col">
320
                  <FormattedMessage
321
                    id="ratingGuideBuilder.targetLevelHeading"
322
                    defaultMessage="Target Level"
323
                    description="Text for the Target Level column heading."
324
                  />
325
                </th>
326
                <th scope="col">
327
                  <FormattedMessage
328
                    id="ratingGuideBuilder.skillHeading"
329
                    defaultMessage="Skill"
330
                    description="Text for the Skill column heading."
331
                  />
332
                </th>
333
                <th scope="col">
334
                  <FormattedMessage
335
                    id="ratingGuideBuilder.skillDescriptionHeading"
336
                    defaultMessage="Skill Description"
337
                    description="Text for the Skill Description column heading."
338
                  />
339
                </th>
340
                <th scope="col">
341
                  <FormattedMessage
342
                    id="ratingGuideBuilder.ratingGuideHeading"
343
                    defaultMessage="Rating Guide"
344
                    description="Text for the Rating Guide column heading."
345
                  />
346
                </th>
347
              </tr>
348
            </thead>
349
            <tbody>
350
              {rows.map(
351
                (row): React.ReactElement => (
352
                  <TableRow key={`RatingsGuideTableRow${row.id}`} {...row} />
353
                ),
354
              )}
355
            </tbody>
356
          </table>
357
        </section>
358
      </div>
359
    </div>
360
  );
361
};
362
363
interface RatingGuideClipboardContainerProps {
364
  jobId: number;
365
  narrativeReview?: Assessment[];
366
}
367
368
const mapStateToProps = (
369
  state: RootState,
370
  ownProps: RatingGuideClipboardContainerProps,
371
): TableProps => ({
372
  assessments: getAssessmentsByJob(state, ownProps),
373
  criteria: getCriteriaByJob(state, ownProps),
374
  skills: getSkills(state),
375
  ratingGuideQuestions: getRatingGuideQuestionsByJob(state, ownProps),
376
  ratingGuideAnswers: getRatingGuideAnswersByJob(state, ownProps),
377
});
378
379
const RatingGuideClipboardContainer = connect(mapStateToProps)(
380
  injectIntl(RatingGuideClipboard),
381
);
382
383
export default RatingGuideClipboardContainer;
384