@plusscommunities/pluss-maintenance-web-forms 1.2.4-beta.0 → 1.2.4-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{index.esm.js → index.js} +3161 -2629
  2. package/dist/index.js.map +1 -0
  3. package/package.json +13 -20
  4. package/.babelrc +0 -3
  5. package/dist/index.cjs.js +0 -6232
  6. package/dist/index.umd.js +0 -6225
  7. package/rollup.config.js +0 -59
  8. package/src/actions/JobsActions.js +0 -150
  9. package/src/actions/index.js +0 -1
  10. package/src/actions/types.js +0 -8
  11. package/src/apis/index.js +0 -10
  12. package/src/apis/maintenanceActions.js +0 -203
  13. package/src/apis/reactionActions.js +0 -46
  14. package/src/components/ActivityText.js +0 -57
  15. package/src/components/AnalyticsHub.js +0 -167
  16. package/src/components/Configuration.js +0 -392
  17. package/src/components/JobList.js +0 -1108
  18. package/src/components/JobTypes.js +0 -198
  19. package/src/components/PreviewFull.js +0 -33
  20. package/src/components/PreviewGrid.js +0 -29
  21. package/src/components/PreviewWidget.js +0 -35
  22. package/src/components/ViewFull.js +0 -25
  23. package/src/components/ViewWidget.js +0 -23
  24. package/src/feature.config.js +0 -127
  25. package/src/helper/index.js +0 -26
  26. package/src/images/forms/full.png +0 -0
  27. package/src/images/forms/fullNoTitle.png +0 -0
  28. package/src/images/forms/previewWidget.png +0 -0
  29. package/src/images/forms/widget.png +0 -0
  30. package/src/images/full.png +0 -0
  31. package/src/images/fullNoTitle.png +0 -0
  32. package/src/images/previewWidget.png +0 -0
  33. package/src/images/widget.png +0 -0
  34. package/src/index.js +0 -29
  35. package/src/maintenancePriority.json +0 -5
  36. package/src/maintenanceStatus.json +0 -20
  37. package/src/reducers/MaintenanceReducer.js +0 -49
  38. package/src/screens/AddJob.js +0 -1138
  39. package/src/screens/AddJobType.js +0 -865
  40. package/src/screens/Job.js +0 -1531
  41. package/src/screens/RequestsHub.js +0 -237
  42. package/src/values.config.a.js +0 -63
  43. package/src/values.config.default.js +0 -75
  44. package/src/values.config.enquiry.js +0 -76
  45. package/src/values.config.feedback.js +0 -74
  46. package/src/values.config.food.js +0 -74
  47. package/src/values.config.forms.js +0 -74
  48. package/src/values.config.js +0 -74
@@ -1,1531 +0,0 @@
1
- import React, { Component } from "react";
2
- import { withRouter } from "react-router";
3
- import { Link } from "react-router-dom";
4
- import moment from "moment";
5
- import _ from "lodash";
6
- import FontAwesome from "react-fontawesome";
7
- import Textarea from "react-textarea-autosize";
8
- import { connect } from "react-redux";
9
- import { jobsLoaded, jobStatusesUpdate } from "../actions";
10
- import Config, { PlussCore } from "../feature.config";
11
- import { maintenanceActions, reactionActions } from "../apis";
12
- import {
13
- STATUS_NOT_ACTIONED,
14
- jobPriorityOptions,
15
- getJobPriority,
16
- } from "../helper";
17
- import { values } from "../values.config";
18
-
19
- const { Apis, Helper, Session, Colours, Components } = PlussCore;
20
- const IMAGE_SIZE_NOTE = 72;
21
-
22
- class Job extends Component {
23
- constructor(props) {
24
- super(props);
25
- this.state = {
26
- jobId: Helper.safeReadParams(props, "jobId")
27
- ? props.match.params.jobId
28
- : null,
29
- job: null,
30
- showingSelector: false,
31
- updating: false,
32
- comments: [],
33
- commentInput: "",
34
- loadingComments: false,
35
- priorityChangerOpen: false,
36
- statusChangerOpen: false,
37
- addNoteOpen: false,
38
- noteAttachments: [],
39
- noteInput: "",
40
- noteImages: [],
41
- assignees: [],
42
- externalSync: null,
43
- loadingExternalSync: false,
44
- retryingSync: false,
45
- retrySyncError: null,
46
- retrySyncInitiated: false,
47
- };
48
- }
49
-
50
- UNSAFE_componentWillReceiveProps(nextProps) {
51
- Session.checkLoggedIn(this, this.props.auth);
52
- }
53
-
54
- componentDidMount() {
55
- this.props.jobStatusesUpdate(this.props.auth.site);
56
- if (this.state.jobId) {
57
- this.getJob();
58
- this.getComments();
59
- this.getAssignees();
60
- this.getExternalSync();
61
- }
62
- }
63
-
64
- getJob = async () => {
65
- try {
66
- const res = await maintenanceActions.getJob(
67
- this.props.auth.site,
68
- this.state.jobId,
69
- );
70
- this.setState({ updating: false });
71
- res.data.location = res.data.site;
72
- this.setJob(res.data);
73
- } catch (error) {
74
- console.error("getJob", error);
75
- }
76
- };
77
-
78
- getAssignees = async () => {
79
- try {
80
- const res = await maintenanceActions.getAssignees(this.props.auth.site);
81
- this.setState({ assignees: res.data.Users });
82
- } catch (error) {
83
- console.error("getAssignees", error);
84
- }
85
- };
86
-
87
- getExternalSync = async () => {
88
- try {
89
- this.setState({ loadingExternalSync: true });
90
- const res = await maintenanceActions.getExternalSync(this.state.jobId);
91
- this.setState({ externalSync: res.data, loadingExternalSync: false });
92
- } catch (error) {
93
- // 404 is expected if no sync - don't show error
94
- if (error && error.response && error.response.status !== 404) {
95
- console.error("getExternalSync", error);
96
- }
97
- this.setState({ loadingExternalSync: false });
98
- }
99
- };
100
-
101
- onRetrySync = async () => {
102
- const { job } = this.state;
103
- if (!job || this.state.retryingSync) return;
104
-
105
- this.setState({ retryingSync: true, retrySyncError: null });
106
-
107
- try {
108
- await maintenanceActions.retrySync(job.id);
109
- // Refresh job data to get updated history
110
- await this.getJob();
111
- this.setState({ retryingSync: false, retrySyncInitiated: true });
112
- } catch (error) {
113
- console.error("onRetrySync", error);
114
- const errorMessage =
115
- (error &&
116
- error.response &&
117
- error.response.data &&
118
- error.response.data.error) ||
119
- "Failed to retry sync. Please try again.";
120
- this.setState({ retryingSync: false, retrySyncError: errorMessage });
121
- }
122
- };
123
-
124
- getStatusType = (status) => {
125
- const { statusTypes } = this.props;
126
- let statusType = statusTypes.find((s) => s.text === status);
127
- if (!statusType) {
128
- const defaultStatus = statusTypes.find(
129
- (s) => s.category === STATUS_NOT_ACTIONED,
130
- );
131
- statusType = { ...defaultStatus, text: status };
132
- }
133
- return statusType;
134
- };
135
-
136
- setJob = (job) => {
137
- if (_.isEmpty(job.lastActivity)) {
138
- job.lastActivity = "-- --";
139
- job.noActivity = true;
140
- }
141
- if (_.isEmpty(job.status)) {
142
- job.status = "Unassigned";
143
- job.notStatus = true;
144
- }
145
- if (_.isEmpty(job.audience)) {
146
- job.audience = [{ displayName: "Unassigned", isEmpty: true }];
147
- }
148
- let needToMarkSeen = false;
149
- if (!job.seen) {
150
- job.seen = true;
151
- needToMarkSeen = true;
152
- }
153
- this.setState({ job }, () => {
154
- if (needToMarkSeen) this.markSeen();
155
- });
156
- this.props.jobsLoaded([job]);
157
- };
158
-
159
- editJob = () => {};
160
-
161
- isReadyToSaveNote = () => {
162
- const { noteAttachments, noteInput, noteImages } = this.state;
163
- if (_.some(noteAttachments, (n) => n.Uploading)) return false;
164
- return (
165
- !_.isEmpty(noteInput) ||
166
- !_.isEmpty(noteAttachments) ||
167
- !_.isEmpty(noteImages)
168
- );
169
- };
170
-
171
- onOpenAddNote = () => {
172
- this.setState({ addNoteOpen: true, editingNote: null });
173
- };
174
-
175
- onCloseAddNote = () => {
176
- const newState = { addNoteOpen: false, editingNote: null };
177
- if (!!this.state.editingNote) {
178
- newState.noteInput = "";
179
- newState.noteAttachments = [];
180
- newState.noteImages = [];
181
- }
182
- this.setState(newState);
183
- };
184
-
185
- onOpenNoteMenu = (index) => {
186
- if (this.state.noteMenuOpen === index) {
187
- this.setState({ noteMenuOpen: null });
188
- } else {
189
- this.setState({ noteMenuOpen: index });
190
- }
191
- };
192
-
193
- onHandlePDFFileChange = (event) => {
194
- const file = event.target.files[0];
195
- if (!file || this.state.uploadingNoteAttachment) return;
196
- const noteAttachments = [...this.state.noteAttachments];
197
- const newAttachment = {
198
- Uploading: true,
199
- Title: file.name,
200
- };
201
- noteAttachments.push(newAttachment);
202
- this.setState({
203
- noteAttachments,
204
- });
205
- Apis.fileActions
206
- .uploadMediaAsync(file, file.name)
207
- .then((fileRes) => {
208
- newAttachment.Source = fileRes;
209
- newAttachment.Uploading = false;
210
- this.setState({
211
- noteAttachments: [...this.state.noteAttachments],
212
- });
213
- })
214
- .catch((uploadErrorRes) => {
215
- console.log(uploadErrorRes);
216
- newAttachment.Uploading = false;
217
- this.setState({
218
- noteAttachments: [...this.state.noteAttachments],
219
- });
220
- });
221
- event.target.value = "";
222
- };
223
-
224
- onRemoveAttachment = (a) => {
225
- const index = this.state.noteAttachments.indexOf(a);
226
- if (index > -1) {
227
- const newAttachments = [...this.state.noteAttachments];
228
- newAttachments.splice(index, 1);
229
- this.setState({
230
- noteAttachments: newAttachments,
231
- });
232
- }
233
- };
234
-
235
- onShowSelectAssignee = () => {
236
- this.setState({
237
- showingAssigneeSelector: true,
238
- });
239
- };
240
-
241
- onCloseSelectAssignee = () => {
242
- this.setState({
243
- showingAssigneeSelector: false,
244
- });
245
- };
246
-
247
- onSelectAssignee = (user) => {
248
- this.setState({
249
- selectedAssignee: user,
250
- });
251
- };
252
-
253
- onConfirmAssignee = async () => {
254
- this.setState({
255
- confirmingAssignee: true,
256
- });
257
- try {
258
- if (this.state.selectedAssignee) {
259
- await this.onAssignUser(this.state.selectedAssignee.id);
260
- }
261
- this.onCloseSelectAssignee();
262
- } catch (error) {
263
- console.error("onConfirmAssignee", error);
264
- }
265
- this.setState({
266
- confirmingAssignee: false,
267
- });
268
- };
269
-
270
- // Method to handle user assignment
271
- onAssignUser = async (userId) => {
272
- try {
273
- const res = await maintenanceActions.assignJob(this.state.jobId, userId);
274
- this.getJob();
275
- } catch (err) {
276
- console.error("onAssignUser", err);
277
- }
278
- };
279
-
280
- onConfirmAddNote = async () => {
281
- if (!this.isReadyToSaveNote()) return;
282
-
283
- try {
284
- this.setState({ submittingNote: true });
285
- const res = await (this.state.editingNote
286
- ? maintenanceActions.editNote(
287
- this.state.jobId,
288
- this.state.editingNote,
289
- this.state.noteInput,
290
- this.state.noteAttachments.map((a) => {
291
- return {
292
- Title: a.Title,
293
- Source: a.Source,
294
- };
295
- }),
296
- this.state.noteImages,
297
- )
298
- : maintenanceActions.addNote(
299
- this.state.jobId,
300
- this.state.noteInput,
301
- this.state.noteAttachments.map((a) => {
302
- return {
303
- Title: a.Title,
304
- Source: a.Source,
305
- };
306
- }),
307
- this.state.noteImages,
308
- ));
309
- this.setState(
310
- {
311
- job: res.data.job,
312
- submittingNote: false,
313
- addNoteOpen: false,
314
- noteInput: "",
315
- noteAttachments: [],
316
- noteImages: [],
317
- editingNote: null,
318
- },
319
- () => {
320
- this.props.jobsLoaded([this.state.job]);
321
- },
322
- );
323
- } catch (err) {
324
- console.error("onConfirmAddNote", err);
325
- }
326
- };
327
-
328
- onDeleteNote = (n) => {
329
- if (!window.confirm(values.textAreYouSureYouWantToDeleteNote)) {
330
- this.setState({ noteMenuOpen: null });
331
- return;
332
- }
333
-
334
- maintenanceActions.deleteNote(this.state.jobId, n.Id);
335
- const newNotes = _.filter(this.state.job.Notes, (note) => note.Id !== n.Id);
336
- const newJob = { ...this.state.job };
337
- newJob.Notes = newNotes;
338
- this.setState({ job: newJob, noteMenuOpen: null });
339
- };
340
-
341
- onOpenEditNote = (n) => {
342
- this.setState(
343
- {
344
- noteAttachments: n.Attachments || [],
345
- noteImages: n.Images || [],
346
- noteInput: n.Note || "",
347
- addNoteOpen: true,
348
- editingNote: n.Id,
349
- noteMenuOpen: null,
350
- },
351
- this.checkSetImage,
352
- );
353
- };
354
-
355
- markSeen = () => {
356
- const { job } = this.state;
357
- const { auth } = this.props;
358
- // Must have maintenance permission and not the requester
359
- if (
360
- !Session.validateAccess(
361
- auth.site,
362
- values.permissionMaintenanceTracking,
363
- auth,
364
- )
365
- )
366
- return;
367
- if (auth.user.Id === job.userID) return;
368
-
369
- this.setState({ updating: true }, async () => {
370
- try {
371
- const update = {
372
- id: job.id,
373
- seen: true,
374
- status: job.status || "Unassigned",
375
- };
376
-
377
- await maintenanceActions.editJob(update, auth.site);
378
- } catch (error) {
379
- this.setState({ updating: false });
380
- console.log("markSeen error", error);
381
- alert("Something went wrong with the request. Please try again.");
382
- }
383
- });
384
- };
385
-
386
- getComments = () => {
387
- reactionActions
388
- .getComments(this.state.jobId, values.commentKey, 0)
389
- .then((res) => {
390
- this.setState({ comments: res.data });
391
- });
392
- };
393
-
394
- checkSetImage() {
395
- if (this.imageInput && !_.isEmpty(this.state.noteImages)) {
396
- this.imageInput.setValue(this.state.noteImages);
397
- } else {
398
- setTimeout(this.checkSetImage, 100);
399
- }
400
- }
401
-
402
- onAddComment = async () => {
403
- const { commentInput, jobId, job, comments } = this.state;
404
- try {
405
- this.setState({ commentInput: "" });
406
- const res = await reactionActions.addComment(
407
- jobId,
408
- values.commentKey,
409
- job.title,
410
- job.site,
411
- commentInput,
412
- );
413
- this.setState({ comments: [...comments, res.data] });
414
- } catch (error) {
415
- console.error("onAddComment", error);
416
- }
417
- };
418
-
419
- onHandleChange = (event) => {
420
- var stateChange = {};
421
- stateChange[event.target.getAttribute("id")] = event.target.value;
422
- this.setState(stateChange);
423
- };
424
-
425
- onTogglePriorityChanger = () => {
426
- this.setState({ priorityChangerOpen: !this.state.priorityChangerOpen });
427
- };
428
-
429
- onSelectPriority = async (priority) => {
430
- this.setState({
431
- job: { ...this.state.job, priority },
432
- priorityChangerOpen: false,
433
- });
434
-
435
- try {
436
- const res = await maintenanceActions.editJobPriority(
437
- this.state.job.id,
438
- priority,
439
- );
440
- const { job } = res.data;
441
- this.props.jobsLoaded([job]);
442
- this.setState({ job });
443
- } catch (error) {
444
- console.error("onSelectPriority", error);
445
- }
446
- };
447
-
448
- onToggleStatusChanger = () => {
449
- this.setState({ statusChangerOpen: !this.state.statusChangerOpen });
450
- };
451
-
452
- onSelectStatus = async (status) => {
453
- this.setState({
454
- job: {
455
- ...this.state.job,
456
- status: status,
457
- },
458
- statusChangerOpen: false,
459
- });
460
-
461
- try {
462
- const res = await maintenanceActions.editJobStatus(
463
- this.state.job.id,
464
- status,
465
- );
466
- const { job } = res.data;
467
- this.props.jobsLoaded([job]);
468
- this.setState({ job });
469
- } catch (error) {
470
- console.error("onSelectStatus", error);
471
- }
472
- };
473
-
474
- renderPriorityChanger() {
475
- if (!this.state.priorityChangerOpen) return null;
476
- return (
477
- <div className="statusChanger statusChanger-priority">
478
- {jobPriorityOptions.map((p) => {
479
- return (
480
- <div
481
- key={p.name}
482
- className="statusLabel"
483
- onClick={() => this.onSelectPriority(p.name)}
484
- style={{ backgroundColor: p.color }}
485
- >
486
- <span className="statusLabel_text">{p.name}</span>
487
- </div>
488
- );
489
- })}
490
- </div>
491
- );
492
- }
493
-
494
- renderPriorityLabel() {
495
- const { auth } = this.props;
496
- if (
497
- !Session.validateAccess(
498
- auth.site,
499
- values.permissionMaintenanceTracking,
500
- auth,
501
- )
502
- )
503
- return null;
504
-
505
- const { job } = this.state;
506
- if (!job) return null;
507
-
508
- const selectedPriority = getJobPriority(job.priority);
509
- return (
510
- <div
511
- className="statusLabel marginTop-5 pointer"
512
- onClick={this.onTogglePriorityChanger}
513
- style={{ backgroundColor: selectedPriority.color }}
514
- >
515
- <span className="statusLabel_text">
516
- {job.priority || selectedPriority.name}
517
- </span>
518
- {this.renderPriorityChanger()}
519
- </div>
520
- );
521
- }
522
-
523
- renderStatusLabel() {
524
- if (!this.state.job.status) return null;
525
- const statusType = this.getStatusType(this.state.job.status);
526
- const { auth } = this.props;
527
- if (
528
- Session.validateAccess(
529
- auth.site,
530
- values.permissionMaintenanceTracking,
531
- auth,
532
- )
533
- ) {
534
- return (
535
- <div
536
- className="statusLabel pointer"
537
- onClick={this.onToggleStatusChanger}
538
- style={{ backgroundColor: statusType.color }}
539
- >
540
- <span className="statusLabel_text">{statusType.text}</span>
541
- {this.renderStatusChanger()}
542
- </div>
543
- );
544
- }
545
- return (
546
- <div
547
- className="statusLabel"
548
- style={{ backgroundColor: statusType.color }}
549
- >
550
- <span className="statusLabel_text">{statusType.text}</span>
551
- </div>
552
- );
553
- }
554
-
555
- renderNotesButton() {
556
- const { auth } = this.props;
557
- if (
558
- !Session.validateAccess(
559
- auth.site,
560
- values.permissionMaintenanceTracking,
561
- auth,
562
- )
563
- )
564
- return null;
565
- return (
566
- <div
567
- className="statusLabel pointer"
568
- onClick={this.onOpenAddNote}
569
- style={{
570
- backgroundColor: Config.env.colourBrandingMain,
571
- marginLeft: 8,
572
- }}
573
- >
574
- <span className="statusLabel_text">Add Note</span>
575
- </div>
576
- );
577
- }
578
-
579
- renderAssignButton() {
580
- const { auth } = this.props;
581
- if (
582
- !Session.validateAccess(
583
- auth.site,
584
- values.permissionMaintenanceTracking,
585
- auth,
586
- )
587
- )
588
- return null;
589
- return (
590
- <div
591
- className="statusLabel pointer"
592
- onClick={this.onShowSelectAssignee}
593
- style={{
594
- backgroundColor: Config.env.colourBrandingMain,
595
- marginLeft: 8,
596
- }}
597
- >
598
- <span className="statusLabel_text">Assign</span>
599
- </div>
600
- );
601
- }
602
-
603
- renderStatusChanger() {
604
- if (!this.state.statusChangerOpen) return null;
605
- const { statusTypes } = this.props;
606
-
607
- return (
608
- <div className="statusChanger statusChanger-maintenance">
609
- {statusTypes.map((status) => {
610
- return (
611
- <div
612
- key={status.text}
613
- className="statusLabel"
614
- onClick={() => this.onSelectStatus(status.text)}
615
- style={{ backgroundColor: status.color }}
616
- >
617
- <span className="statusLabel_text">{status.text}</span>
618
- </div>
619
- );
620
- })}
621
- </div>
622
- );
623
- }
624
-
625
- renderComment(c) {
626
- return <Components.Comment key={c.Id} comment={c} />;
627
- return (
628
- <div key={c.Id} className="comment">
629
- <p className="comment_text">{Helper.toParagraphed(c.Comment)}</p>
630
- <div className="comment_bottom">
631
- <Components.ProfilePic
632
- className="comment_profilePic"
633
- size={25}
634
- image={c.User.profilePic}
635
- />
636
- <p className="comment_name">{c.User.displayName}</p>
637
- <p className="comment_time">
638
- {moment.utc(c.Timestamp).local().format("D MMM YYYY • h:mma")}
639
- </p>
640
- </div>
641
- </div>
642
- );
643
- }
644
-
645
- renderCommentSection() {
646
- if (this.state.loadingComments) return null;
647
-
648
- return (
649
- <div className="padding-60 paddingLeft-20">
650
- <div className="newTopBar paddingLeft-40">
651
- <Components.Text type="formTitleSmall" className="marginBottom-16">
652
- Comments
653
- </Components.Text>
654
- <div className="commentSection">
655
- {this.state.comments.map((c) => this.renderComment(c))}
656
- </div>
657
- <div className="commentReply">
658
- <div
659
- className={`commentReply_button${!_.isEmpty(this.state.commentInput) ? " commentReply_button-active" : ""}`}
660
- onClick={this.onAddComment}
661
- >
662
- <FontAwesome className="commentReply_icon" name="paper-plane-o" />
663
- </div>
664
- <Textarea
665
- id="commentInput"
666
- placeholder="Reply here..."
667
- type="text"
668
- className="commentReply_input"
669
- value={this.state.commentInput}
670
- onChange={(e) => this.onHandleChange(e)}
671
- />
672
- </div>
673
- </div>
674
- </div>
675
- );
676
- }
677
-
678
- renderImageGrid(images, size = undefined) {
679
- const imagesToUse = images && images.length > 0 ? images : [];
680
- return (
681
- <div className="imageGrid">
682
- {imagesToUse.map((image, i) => {
683
- return (
684
- <a href={image} target="_blank" rel="noopener noreferrer" key={i}>
685
- <div
686
- className="imageGrid_image"
687
- style={{
688
- backgroundImage: `url('${Helper.get1400(image)}')`,
689
- width: size,
690
- height: size,
691
- }}
692
- ></div>
693
- </a>
694
- );
695
- })}
696
- </div>
697
- );
698
- }
699
-
700
- renderDocumentGrid(documents) {
701
- const documentsToUse = documents && documents.length > 0 ? documents : [];
702
- return (
703
- <div className="documentGrid">
704
- {documentsToUse.map((doc, index) => (
705
- <Components.Attachment
706
- key={index}
707
- uploading={doc.uploading}
708
- source={doc.url}
709
- title={doc.name}
710
- />
711
- ))}
712
- </div>
713
- );
714
- }
715
-
716
- renderImages() {
717
- if (_.isEmpty(this.state.job.image) && _.isEmpty(this.state.job.images))
718
- return null;
719
-
720
- const imagesToUse = _.isEmpty(this.state.job.image)
721
- ? this.state.job.images
722
- : [this.state.job.image];
723
- return (
724
- <div className="padding-60 paddingVertical-40 bottomDivideBorder">
725
- <Components.Text type="formTitleSmall" className="marginBottom-16">
726
- Images
727
- </Components.Text>
728
- {this.renderImageGrid(imagesToUse)}
729
- </div>
730
- );
731
- }
732
-
733
- renderCustomFields() {
734
- const { job } = this.state;
735
- const { customFields } = job;
736
-
737
- const labelClass = "fieldLabel";
738
- const answerClass = "fontRegular fontSize-16 text-dark marginTop-5";
739
-
740
- const renderAnswer = (field) => {
741
- switch (field.type) {
742
- case "date":
743
- return (
744
- <div className={answerClass}>
745
- {field.answer
746
- ? moment(field.answer, "YYYY-MM-DD").format("DD-MMM-YYYY")
747
- : ""}
748
- </div>
749
- );
750
- case "time":
751
- return (
752
- <div className={answerClass}>
753
- {field.answer
754
- ? moment(field.answer, "HH:mm").format("h:mm a")
755
- : ""}
756
- </div>
757
- );
758
- case "yn":
759
- return (
760
- <div className={answerClass}>{field.answer ? "Yes" : "No"}</div>
761
- );
762
- case "checkbox":
763
- return (
764
- <div className={answerClass}>
765
- {field.answer && Array.isArray(field.answer)
766
- ? field.answer.join(", ")
767
- : ""}
768
- </div>
769
- );
770
- case "image":
771
- return this.renderImageGrid(field.answer);
772
- case "document":
773
- return this.renderDocumentGrid(field.answer);
774
- default:
775
- return <div className={answerClass}>{field.answer}</div>;
776
- }
777
- };
778
-
779
- return (
780
- <div className="padding-60 paddingVertical-40 bottomDivideBorder">
781
- {customFields.map((field, index) => {
782
- if (["staticTitle", "staticText"].includes(field.type)) return null;
783
- if (
784
- _.isNil(field.answer) ||
785
- field.answer === "" ||
786
- field.answer.length === 0
787
- )
788
- return null;
789
- return (
790
- <div key={index} className="marginTop-16">
791
- <div className={labelClass}>{field.label}</div>
792
- {renderAnswer(field)}
793
- </div>
794
- );
795
- })}
796
- </div>
797
- );
798
- }
799
-
800
- renderInner() {
801
- if (this.state.job == null) return null;
802
- const { customFields } = this.state.job;
803
- const hasCustomFields = customFields && customFields.length > 0;
804
-
805
- return (
806
- <div style={{ paddingBottom: 40 }}>
807
- <div className="padding-60 paddingVertical-40 bottomDivideBorder relative">
808
- <Components.Text type="formTitleLarge" className="marginBottom-8">
809
- {this.state.job.title || values.textSingularName}
810
- </Components.Text>
811
- <Components.Text type="formTitleMedium" className="marginBottom-24">
812
- {values.textEntityName} #{this.state.job.jobId}
813
- </Components.Text>
814
- <div className="marginTop-16">
815
- <div className={"fieldLabel"}>Submission date</div>
816
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
817
- {moment
818
- .utc(this.state.job.createdTime)
819
- .local()
820
- .format("D MMM YY")}
821
- </div>
822
- </div>
823
- <div className="marginTop-16">
824
- <div className={"fieldLabel"}>Type</div>
825
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
826
- {this.state.job.type}
827
- </div>
828
- </div>
829
- <div className="marginTop-16">
830
- <div className={"fieldLabel"}>Address</div>
831
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
832
- {this.state.job.room}
833
- </div>
834
- </div>
835
- {hasCustomFields ? null : (
836
- <div className="marginTop-16">
837
- <div className={"fieldLabel"}>
838
- Description {this.state.job.image ? "- (image supplied)" : ""}
839
- </div>
840
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
841
- {this.state.job.description}
842
- </div>
843
- </div>
844
- )}
845
- </div>
846
- <div className="padding-60 paddingVertical-40 bottomDivideBorder">
847
- <Components.Text type="formTitleSmall" className="marginBottom-16">
848
- Contact Details
849
- </Components.Text>
850
- <div className="marginTop-16">
851
- <div className={"fieldLabel"}>Name</div>
852
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
853
- {this.state.job.userName}
854
- </div>
855
- </div>
856
- <div className="marginTop-16">
857
- <div className={"fieldLabel"}>Contact number</div>
858
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
859
- {_.isEmpty(this.state.job.phone)
860
- ? "No phone provided"
861
- : this.state.job.phone}
862
- </div>
863
- </div>
864
- {hasCustomFields ? null : (
865
- <div>
866
- <div className="marginTop-16">
867
- <div className={"fieldLabel"}>Should person be home?</div>
868
- <div
869
- className={"fontRegular fontSize-16 text-dark marginTop-5"}
870
- >
871
- {this.state.job.isHome ? "Yes" : "No"}
872
- </div>
873
- </div>
874
- {this.state.job.isHome && this.state.job.homeText && (
875
- <div className="marginTop-16">
876
- <div className={"fieldLabel"}>When</div>
877
- <div
878
- className={"fontRegular fontSize-16 text-dark marginTop-5"}
879
- >
880
- {this.state.job.homeText}
881
- </div>
882
- </div>
883
- )}
884
- </div>
885
- )}
886
- </div>
887
- {hasCustomFields ? null : this.renderImages()}
888
- {hasCustomFields ? this.renderCustomFields() : null}
889
- {this.renderCommentSection()}
890
- </div>
891
- );
892
- }
893
-
894
- renderHistoryEntry(e, i) {
895
- const { job } = this.state;
896
- const entryToUse = e || {
897
- timestamp: job.createdTime,
898
- status: "Unassigned",
899
- user: {
900
- displayName: job.userName,
901
- id: job.userID,
902
- profilePic: job.userProfilePic,
903
- },
904
- };
905
- const statusType = this.getStatusType(entryToUse.status);
906
- return (
907
- <div className="ticketHistoryEntry" key={i}>
908
- <p className="ticketHistoryEntry_timestamp">
909
- {moment.utc(entryToUse.timestamp).local().format("D MMM YYYY h:mma")}
910
- </p>
911
- <div
912
- className="statusLabel statusLabel-large statusLabel-full"
913
- style={{ backgroundColor: statusType.color }}
914
- >
915
- <span className="statusLabel_text">
916
- {e
917
- ? `${entryToUse.user.displayName} marked as ${statusType.text}`
918
- : `${values.textEntityName} opened`}
919
- </span>
920
- </div>
921
- </div>
922
- );
923
- }
924
-
925
- renderNote(note, index) {
926
- return (
927
- <div className="ticketHistoryEntry" key={index}>
928
- <p className="ticketHistoryEntry_timestamp">
929
- {moment.utc(note.Timestamp).local().format("D MMM YYYY h:mma")}
930
- </p>
931
- <div
932
- className="statusLabel statusLabel-large statusLabel-full"
933
- style={{ backgroundColor: "#6e79c5" }}
934
- >
935
- <span className="statusLabel_text">Staff Notes</span>
936
- </div>
937
- <div className="maintenanceNote">
938
- <div className="maintenanceNote_top">
939
- {this.props.auth &&
940
- this.props.auth.user &&
941
- this.props.auth.user.Id === note.User.id && (
942
- <Components.SVGIcon
943
- colour={Colours.COLOUR_DUSK_LIGHT}
944
- icon="more15"
945
- className="maintenanceNote_moreIcon"
946
- onClick={() => this.onOpenNoteMenu(index)}
947
- />
948
- )}
949
- <p className="maintenanceNote_name">{note.User.displayName}</p>
950
- {this.state.noteMenuOpen === index && (
951
- <Components.MoreMenu
952
- options={[
953
- {
954
- key: "edit",
955
- text: "Edit",
956
- onPress: () => this.onOpenEditNote(note),
957
- },
958
- {
959
- key: "delete",
960
- text: "Delete",
961
- onPress: () => this.onDeleteNote(note),
962
- },
963
- ]}
964
- />
965
- )}
966
- </div>
967
- <p className="maintenanceNote_text">
968
- {Helper.toParagraphed(note.Note)}
969
- </p>
970
- {note.Attachments.map((a, i) => this.renderAttachment(a, i))}
971
- {note.Images && note.Images.length > 0
972
- ? this.renderImageGrid(note.Images, IMAGE_SIZE_NOTE)
973
- : null}
974
- </div>
975
- </div>
976
- );
977
- }
978
-
979
- renderAssignment() {
980
- const { job } = this.state;
981
- if (!job) return null;
982
-
983
- return (
984
- <div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
985
- <div className="newTopBar clearfix flex flex-reverse">
986
- {this.renderAssignButton()}
987
- <Components.Text type="formTitleSmall" className="flex-1">
988
- Assignment
989
- </Components.Text>
990
- </div>
991
- <div>
992
- <div className="marginTop-16">
993
- <div className={"fieldLabel"}>Assigned to</div>
994
- <div className={"fontRegular fontSize-16 text-dark marginTop-5"}>
995
- {job.Assignee ? (
996
- <Components.UserListing user={job.Assignee} />
997
- ) : (
998
- "Unassigned"
999
- )}
1000
- </div>
1001
- </div>
1002
- </div>
1003
- </div>
1004
- );
1005
- }
1006
-
1007
- renderAssignmentEntry(e, i) {
1008
- return (
1009
- <div className="ticketHistoryEntry" key={i}>
1010
- <p className="ticketHistoryEntry_timestamp">
1011
- {moment.utc(e.timestamp).local().format("D MMM YYYY h:mma")}
1012
- </p>
1013
- <div
1014
- className="statusLabel statusLabel-large statusLabel-full"
1015
- style={{ backgroundColor: Colours.COLOUR_DUSK }}
1016
- >
1017
- <span className="statusLabel_text">
1018
- {e.user.displayName} assigned the {values.textSingularName} to{" "}
1019
- {e.assignedUser ? e.assignedUser.displayName : "Unassigned"}
1020
- </span>
1021
- </div>
1022
- </div>
1023
- );
1024
- }
1025
-
1026
- renderExternalSyncEntry(e, i) {
1027
- const isSuccess = e.EntryType === "ExternalIDSet";
1028
- const backgroundColor = isSuccess
1029
- ? Colours.COLOUR_GREEN
1030
- : Colours.COLOUR_RED; // Green for success, red for failure
1031
-
1032
- return (
1033
- <div className="ticketHistoryEntry" key={i}>
1034
- <p className="ticketHistoryEntry_timestamp">
1035
- {moment.utc(e.timestamp).local().format("D MMM YYYY h:mma")}
1036
- </p>
1037
- <div
1038
- className="statusLabel statusLabel-large statusLabel-full"
1039
- style={{ backgroundColor }}
1040
- >
1041
- <span className="statusLabel_text">
1042
- {isSuccess
1043
- ? `Synced to ${e.systemType || "external system"}${e.externalId ? ` (ID: ${e.externalId})` : ""}`
1044
- : `Failed to sync to ${e.systemType || "external system"}${e.error ? `: ${e.error}` : ""}`}
1045
- </span>
1046
- </div>
1047
- </div>
1048
- );
1049
- }
1050
-
1051
- renderPriority() {
1052
- const { auth } = this.props;
1053
- if (
1054
- !Session.validateAccess(
1055
- auth.site,
1056
- values.permissionMaintenanceTracking,
1057
- auth,
1058
- )
1059
- )
1060
- return null;
1061
-
1062
- return (
1063
- <div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
1064
- <div className="newTopBar clearfix flex flex-reverse">
1065
- {this.renderPriorityLabel()}
1066
- <Components.Text type="formTitleSmall" className="flex-1">
1067
- Priority
1068
- </Components.Text>
1069
- </div>
1070
- </div>
1071
- );
1072
- }
1073
-
1074
- hasSyncFailureWithoutSuccess() {
1075
- const { job } = this.state;
1076
- if (!job || !job.history) return false;
1077
-
1078
- const history = job.history || [];
1079
- const hasSuccess = history.some(
1080
- (entry) => entry.EntryType === "ExternalIDSet",
1081
- );
1082
- const hasFailure = history.some(
1083
- (entry) => entry.EntryType === "ExternalIDSetFailed",
1084
- );
1085
-
1086
- return hasFailure && !hasSuccess;
1087
- }
1088
-
1089
- renderRetrySyncButton() {
1090
- const { auth } = this.props;
1091
- const { retryingSync, retrySyncInitiated } = this.state;
1092
-
1093
- // Only show for users with maintenance tracking permission
1094
- if (
1095
- !Session.validateAccess(
1096
- auth.site,
1097
- values.permissionMaintenanceTracking,
1098
- auth,
1099
- )
1100
- )
1101
- return null;
1102
-
1103
- // Only show if there's a failure without success and retry hasn't been initiated
1104
- if (!this.hasSyncFailureWithoutSuccess() || retrySyncInitiated) return null;
1105
-
1106
- // Show spinner while retrying
1107
- if (retryingSync) {
1108
- return (
1109
- <FontAwesome
1110
- style={{
1111
- fontSize: 20,
1112
- color: Colours.COLOUR_DUSK_LIGHT,
1113
- marginLeft: 8,
1114
- }}
1115
- name="spinner fa-pulse fa-fw"
1116
- />
1117
- );
1118
- }
1119
-
1120
- return (
1121
- <div
1122
- className="statusLabel pointer"
1123
- onClick={this.onRetrySync}
1124
- style={{ backgroundColor: Colours.COLOUR_RED, marginLeft: 8 }}
1125
- >
1126
- <span className="statusLabel_text">Retry Sync</span>
1127
- </div>
1128
- );
1129
- }
1130
-
1131
- renderExternalSyncStatus() {
1132
- const { retrySyncError, retrySyncInitiated } = this.state;
1133
-
1134
- // Show error message if retry failed
1135
- if (retrySyncError) {
1136
- return (
1137
- <Components.Text type="body">
1138
- <FontAwesome
1139
- className="userStatusIcon"
1140
- name="times-circle"
1141
- style={{ color: Colours.COLOUR_RED }}
1142
- />{" "}
1143
- {retrySyncError}
1144
- </Components.Text>
1145
- );
1146
- }
1147
-
1148
- // Show success message if retry was initiated
1149
- if (retrySyncInitiated) {
1150
- return (
1151
- <Components.Text type="body">
1152
- <FontAwesome
1153
- className="userStatusIcon"
1154
- name="check-circle"
1155
- style={{ color: Colours.COLOUR_GREEN }}
1156
- />{" "}
1157
- Sync retry initiated. Check back shortly for results.
1158
- </Components.Text>
1159
- );
1160
- }
1161
-
1162
- // Show failure message with instruction
1163
- if (this.hasSyncFailureWithoutSuccess()) {
1164
- return (
1165
- <Components.Text type="body">
1166
- <FontAwesome
1167
- className="userStatusIcon"
1168
- name="times-circle"
1169
- style={{ color: Colours.COLOUR_RED }}
1170
- />{" "}
1171
- External sync failed. Use the retry button to attempt again.
1172
- </Components.Text>
1173
- );
1174
- }
1175
-
1176
- return null;
1177
- }
1178
-
1179
- renderExternalSync() {
1180
- const { externalSync, loadingExternalSync } = this.state;
1181
-
1182
- // Check if we should show this section at all
1183
- const hasExternalSyncData = externalSync && !loadingExternalSync;
1184
- const hasSyncFailure = this.hasSyncFailureWithoutSuccess();
1185
-
1186
- // Show section if we have sync data OR if there's a failure that can be retried
1187
- if (!hasExternalSyncData && !hasSyncFailure) return null;
1188
-
1189
- return (
1190
- <div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
1191
- <div className="newTopBar clearfix flex flex-reverse">
1192
- {this.renderRetrySyncButton()}
1193
- <Components.Text type="formTitleSmall" className="flex-1">
1194
- External Sync
1195
- </Components.Text>
1196
- </div>
1197
- <div className="marginTop-16">
1198
- {hasExternalSyncData ? (
1199
- <>
1200
- {externalSync.systemType && (
1201
- <Components.Text type="body" className="marginBottom-8">
1202
- <strong>System:</strong> {externalSync.systemType}
1203
- </Components.Text>
1204
- )}
1205
- {externalSync.externalId && (
1206
- <Components.Text type="body" className="marginBottom-8">
1207
- <strong>External ID:</strong> {externalSync.externalId}
1208
- </Components.Text>
1209
- )}
1210
- {externalSync.syncedAt && (
1211
- <Components.Text type="body" className="marginBottom-8">
1212
- <strong>Synced:</strong>{" "}
1213
- {moment
1214
- .utc(externalSync.syncedAt)
1215
- .local()
1216
- .format("D MMM YYYY h:mma")}
1217
- </Components.Text>
1218
- )}
1219
- </>
1220
- ) : (
1221
- this.renderExternalSyncStatus()
1222
- )}
1223
- </div>
1224
- </div>
1225
- );
1226
- }
1227
-
1228
- renderOverview() {
1229
- const { job } = this.state;
1230
- if (!job || !job.history) return null;
1231
-
1232
- const source = _.sortBy(
1233
- [
1234
- ...job.history.map((e) => {
1235
- return { ...e, EntryType: e.EntryType || "status" };
1236
- }),
1237
- ...(job.Notes || []).map((e) => {
1238
- return { ...e, timestamp: e.Timestamp, EntryType: "note" };
1239
- }),
1240
- ],
1241
- "timestamp",
1242
- );
1243
-
1244
- return (
1245
- <div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
1246
- <div className="newTopBar clearfix flex flex-reverse">
1247
- {this.renderNotesButton()}
1248
- {this.renderStatusLabel()}
1249
- <Components.Text type="formTitleSmall" className="flex-1">
1250
- Status History
1251
- </Components.Text>
1252
- </div>
1253
- {this.renderHistoryEntry(null, -1)}
1254
- {_.map(source, (e, i) => {
1255
- switch (e.EntryType) {
1256
- case "status":
1257
- return this.renderHistoryEntry(e, i);
1258
- case "note":
1259
- return this.renderNote(e, i);
1260
- case "assignment":
1261
- return this.renderAssignmentEntry(e, i);
1262
- case "ExternalIDSet":
1263
- case "ExternalIDSetFailed":
1264
- return this.renderExternalSyncEntry(e, i);
1265
- default:
1266
- break;
1267
- }
1268
- })}
1269
- </div>
1270
- );
1271
- }
1272
-
1273
- renderButtons() {
1274
- return (
1275
- <div>
1276
- <Components.Button
1277
- inline
1278
- buttonType="tertiary"
1279
- onClick={() => {
1280
- window.history.back();
1281
- }}
1282
- isActive
1283
- style={{ marginRight: 16 }}
1284
- >
1285
- Back
1286
- </Components.Button>
1287
- {Session.validateAccess(
1288
- this.props.auth.site,
1289
- values.permissionMaintenanceTracking,
1290
- this.props.auth,
1291
- ) &&
1292
- !_.isEmpty(this.state.job) && (
1293
- <Link to={`${values.routeAddRequest}/${this.state.jobId}`}>
1294
- <Components.Button
1295
- inline
1296
- style={{ marginRight: 25 }}
1297
- buttonType="outlined"
1298
- isActive
1299
- onClick={this.editJob}
1300
- >
1301
- Edit Details
1302
- </Components.Button>
1303
- </Link>
1304
- )}
1305
- </div>
1306
- );
1307
- }
1308
-
1309
- renderAttachment(attachment, index, onRemove) {
1310
- if (!attachment) return null;
1311
-
1312
- return (
1313
- <Components.Attachment
1314
- key={index}
1315
- uploading={attachment.Uploading}
1316
- source={attachment.Source}
1317
- title={attachment.Title}
1318
- onRemove={onRemove ? () => onRemove(attachment) : undefined}
1319
- />
1320
- );
1321
- }
1322
-
1323
- renderAddNotePopup() {
1324
- if (!this.state.addNoteOpen) return null;
1325
-
1326
- if (this.state.submittingNote) {
1327
- return (
1328
- <Components.Popup title="Saving Note" maxWidth={600} hasPadding>
1329
- <div className="flex flex-center-row">
1330
- <FontAwesome className="spinner" name="spinner fa-pulse fa-fw" />
1331
- </div>
1332
- </Components.Popup>
1333
- );
1334
- }
1335
-
1336
- return (
1337
- <Components.Popup
1338
- title={`${this.state.editingNote ? "Edit" : "Add"} Note`}
1339
- onClose={this.onCloseAddNote}
1340
- maxWidth={600}
1341
- hasPadding
1342
- buttons={[
1343
- {
1344
- type: "primary",
1345
- onClick: this.onConfirmAddNote,
1346
- isActive: this.isReadyToSaveNote(),
1347
- text: "Save",
1348
- },
1349
- {
1350
- type: "tertiary",
1351
- onClick: this.onCloseAddNote,
1352
- isActive: true,
1353
- text: "Cancel",
1354
- },
1355
- ]}
1356
- >
1357
- <Components.GenericInput
1358
- id="noteInput"
1359
- type="textarea"
1360
- componentClass="textarea"
1361
- value={this.state.noteInput}
1362
- placeholder="Enter note"
1363
- onChange={(e) => this.onHandleChange(e)}
1364
- inputStyle={{
1365
- width: 400,
1366
- }}
1367
- />
1368
- <Components.Text type="h5">Attachments</Components.Text>
1369
- {this.state.noteAttachments.map((a, i) =>
1370
- this.renderAttachment(a, i, this.onRemoveAttachment),
1371
- )}
1372
- <input
1373
- ref={(input) => (this.attachmentInput = input)}
1374
- id="attachmentInput"
1375
- type="file"
1376
- className="fileInput"
1377
- onChange={(e) => this.onHandlePDFFileChange(e)}
1378
- accept="application/pdf"
1379
- />
1380
- <div
1381
- className="iconTextButton marginBottom-16"
1382
- onClick={() => {
1383
- this.attachmentInput.click();
1384
- }}
1385
- >
1386
- <FontAwesome className="iconTextButton_icon" name="paperclip" />
1387
- <p className="iconTextButton_text">Add Attachment</p>
1388
- </div>
1389
- <Components.ImageInput
1390
- ref={(ref) => {
1391
- this.imageInput = ref;
1392
- }}
1393
- multiple
1394
- refreshCallback={(images) => {
1395
- this.setState({ noteImages: images });
1396
- }}
1397
- />
1398
- </Components.Popup>
1399
- );
1400
- }
1401
-
1402
- renderUsers() {
1403
- let content = null;
1404
- if (this.state.confirmingAssignee) {
1405
- content = (
1406
- <div className="flex flex-center-row">
1407
- <FontAwesome className="spinner" name="spinner fa-pulse fa-fw" />
1408
- </div>
1409
- );
1410
- } else if (this.state.selectedAssignee) {
1411
- content = (
1412
- <div>
1413
- <Components.UserListing
1414
- key={this.state.selectedAssignee.id}
1415
- user={this.state.selectedAssignee}
1416
- rightContent={
1417
- <Components.SVGIcon
1418
- className="removeIcon"
1419
- icon="close"
1420
- onClick={() => {
1421
- this.onSelectAssignee();
1422
- }}
1423
- colour={Colours.COLOUR_DUSK}
1424
- />
1425
- }
1426
- />
1427
- </div>
1428
- );
1429
- } else {
1430
- content = (
1431
- <div>
1432
- <Components.GenericInput
1433
- id="userSearch"
1434
- type="text"
1435
- // label="Search"
1436
- placeholder="Search name"
1437
- value={this.state.userSearch}
1438
- onChange={(e) => this.onHandleChange(e)}
1439
- alwaysShowLabel
1440
- />
1441
- {_.sortBy(this.state.assignees, (u) => u.displayName.toUpperCase())
1442
- .filter((u) => {
1443
- if (_.isEmpty(this.state.userSearch)) return true;
1444
- return (
1445
- u.displayName
1446
- .toUpperCase()
1447
- .indexOf(this.state.userSearch.toUpperCase()) > -1
1448
- );
1449
- })
1450
- .map((user) => {
1451
- return (
1452
- <Components.UserListing
1453
- key={user.id}
1454
- user={user}
1455
- onClick={() => {
1456
- this.onSelectAssignee(user);
1457
- }}
1458
- />
1459
- );
1460
- })}
1461
- </div>
1462
- );
1463
- }
1464
- return (
1465
- <div className="genericInputContainer">
1466
- <Components.Text type="formLabel">Select User</Components.Text>
1467
- {content}
1468
- </div>
1469
- );
1470
- }
1471
-
1472
- renderUserSelectionPopup() {
1473
- if (!this.state.showingAssigneeSelector) return null;
1474
- return (
1475
- <Components.Popup
1476
- title="Assign Job"
1477
- onClose={this.onCloseSelectAssignee}
1478
- maxWidth={600}
1479
- hasPadding
1480
- buttons={[
1481
- {
1482
- type: "primary",
1483
- onClick: this.onConfirmAssignee,
1484
- isActive: !!this.state.selectedAssignee,
1485
- text: "Confirm",
1486
- },
1487
- {
1488
- type: "tertiary",
1489
- onClick: this.onCloseSelectAssignee,
1490
- isActive: true,
1491
- text: "Cancel",
1492
- },
1493
- ]}
1494
- >
1495
- {this.renderUsers()}
1496
- </Components.Popup>
1497
- );
1498
- }
1499
-
1500
- render() {
1501
- return (
1502
- <Components.OverlayPage>
1503
- {this.renderAddNotePopup()}
1504
- {this.renderUserSelectionPopup()}
1505
- <Components.OverlayPageContents>
1506
- <Components.OverlayPageSection className="pageSectionWrapper--fixedPopupSize">
1507
- {this.renderInner()}
1508
- </Components.OverlayPageSection>
1509
- <Components.OverlayPageSection className="pageSectionWrapper--newPopupSide pageSectionWrapper--newPopupSide-fixedWidth">
1510
- {this.renderAssignment()}
1511
- {this.renderPriority()}
1512
- {this.renderExternalSync()}
1513
- {this.renderOverview()}
1514
- </Components.OverlayPageSection>
1515
- </Components.OverlayPageContents>
1516
- <Components.OverlayPageBottomButtons>
1517
- {this.renderButtons()}
1518
- </Components.OverlayPageBottomButtons>
1519
- </Components.OverlayPage>
1520
- );
1521
- }
1522
- }
1523
-
1524
- const mapStateToProps = (state) => {
1525
- const { auth } = state;
1526
- return { auth, statusTypes: state[values.reducerKey].jobstatuses };
1527
- };
1528
-
1529
- export default connect(mapStateToProps, { jobsLoaded, jobStatusesUpdate })(
1530
- withRouter(Job),
1531
- );