@plusscommunities/pluss-maintenance-web-forms 1.1.20-beta.0
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.
- package/.babelrc +3 -0
- package/dist/index.cjs.js +6405 -0
- package/dist/index.esm.js +6353 -0
- package/dist/index.umd.js +6388 -0
- package/package.json +56 -0
- package/rollup.config.js +59 -0
- package/src/actions/JobsActions.js +98 -0
- package/src/actions/index.js +1 -0
- package/src/actions/types.js +6 -0
- package/src/apis/index.js +9 -0
- package/src/apis/maintenanceActions.js +181 -0
- package/src/apis/reactionActions.js +46 -0
- package/src/components/ActivityText.js +57 -0
- package/src/components/AnalyticsHub.js +167 -0
- package/src/components/JobList.js +842 -0
- package/src/components/JobTypes.js +198 -0
- package/src/components/PreviewFull.js +33 -0
- package/src/components/PreviewGrid.js +29 -0
- package/src/components/PreviewWidget.js +35 -0
- package/src/components/ViewFull.js +25 -0
- package/src/components/ViewWidget.js +23 -0
- package/src/feature.config.js +112 -0
- package/src/images/forms/full.png +0 -0
- package/src/images/forms/fullNoTitle.png +0 -0
- package/src/images/forms/previewWidget.png +0 -0
- package/src/images/forms/widget.png +0 -0
- package/src/images/full.png +0 -0
- package/src/images/fullNoTitle.png +0 -0
- package/src/images/previewWidget.png +0 -0
- package/src/images/widget.png +0 -0
- package/src/index.js +29 -0
- package/src/maintenanceStatus.json +17 -0
- package/src/reducers/MaintenanceReducer.js +74 -0
- package/src/screens/AddJob.js +859 -0
- package/src/screens/AddJobType.js +841 -0
- package/src/screens/Job.js +971 -0
- package/src/screens/RequestsHub.js +221 -0
- package/src/values.config.a.js +57 -0
- package/src/values.config.b.js +57 -0
- package/src/values.config.c.js +57 -0
- package/src/values.config.d.js +57 -0
- package/src/values.config.default.js +68 -0
- package/src/values.config.forms.js +67 -0
- package/src/values.config.js +67 -0
|
@@ -0,0 +1,971 @@
|
|
|
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 } from '../actions';
|
|
10
|
+
import Config, { PlussCore } from '../feature.config';
|
|
11
|
+
import { maintenanceActions, reactionActions } from '../apis';
|
|
12
|
+
import StatusTypes from '../maintenanceStatus.json';
|
|
13
|
+
import { values } from '../values.config';
|
|
14
|
+
|
|
15
|
+
const { Apis, Helper, Session, Colours, Components } = PlussCore;
|
|
16
|
+
|
|
17
|
+
class Job extends Component {
|
|
18
|
+
constructor(props) {
|
|
19
|
+
super(props);
|
|
20
|
+
this.state = {
|
|
21
|
+
jobId: Helper.safeReadParams(props, 'jobId') ? props.match.params.jobId : null,
|
|
22
|
+
job: null,
|
|
23
|
+
showingSelector: false,
|
|
24
|
+
updating: false,
|
|
25
|
+
comments: [],
|
|
26
|
+
commentInput: '',
|
|
27
|
+
loadingComments: false,
|
|
28
|
+
statusChangerOpen: false,
|
|
29
|
+
addNoteOpen: false,
|
|
30
|
+
noteAttachments: [],
|
|
31
|
+
noteInput: '',
|
|
32
|
+
assignees: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
37
|
+
Session.checkLoggedIn(this, this.props.auth);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
componentDidMount() {
|
|
41
|
+
if (this.state.jobId) {
|
|
42
|
+
this.getJob();
|
|
43
|
+
this.getComments();
|
|
44
|
+
this.getAssignees();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getJob = async () => {
|
|
49
|
+
try {
|
|
50
|
+
const res = await maintenanceActions.getJob(this.props.auth.site, this.state.jobId);
|
|
51
|
+
this.setState({ updating: false });
|
|
52
|
+
res.data.location = res.data.site;
|
|
53
|
+
this.setJob(res.data);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('getJob', error);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
getAssignees = async () => {
|
|
60
|
+
try {
|
|
61
|
+
const res = await maintenanceActions.getAssignees(this.props.auth.site);
|
|
62
|
+
this.setState({ assignees: res.data.Users });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('getAssignees', error);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
getStatusType = (status) => {
|
|
69
|
+
let statusType = StatusTypes[status] || Object.values(StatusTypes).find((s) => s.text === status);
|
|
70
|
+
if (!statusType) statusType = { text: status, color: '#aaa' };
|
|
71
|
+
return statusType;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
setJob = (job) => {
|
|
75
|
+
if (_.isEmpty(job.lastActivity)) {
|
|
76
|
+
job.lastActivity = '-- --';
|
|
77
|
+
job.noActivity = true;
|
|
78
|
+
}
|
|
79
|
+
if (_.isEmpty(job.status)) {
|
|
80
|
+
job.status = 'Unassigned';
|
|
81
|
+
job.notStatus = true;
|
|
82
|
+
}
|
|
83
|
+
if (_.isEmpty(job.audience)) {
|
|
84
|
+
job.audience = [{ displayName: 'Unassigned', isEmpty: true }];
|
|
85
|
+
}
|
|
86
|
+
let needToMarkSeen = false;
|
|
87
|
+
if (!job.seen) {
|
|
88
|
+
job.seen = true;
|
|
89
|
+
needToMarkSeen = true;
|
|
90
|
+
}
|
|
91
|
+
this.setState({ job }, () => {
|
|
92
|
+
if (needToMarkSeen) this.markSeen();
|
|
93
|
+
});
|
|
94
|
+
this.props.jobsLoaded([job]);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
editJob = () => {};
|
|
98
|
+
|
|
99
|
+
isReadyToSaveNote = () => {
|
|
100
|
+
const { noteAttachments, noteInput } = this.state;
|
|
101
|
+
if (_.some(noteAttachments, (n) => n.Uploading)) return false;
|
|
102
|
+
return !_.isEmpty(noteInput) || !_.isEmpty(noteAttachments);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
onOpenAddNote = () => {
|
|
106
|
+
this.setState({ addNoteOpen: true, editingNote: null });
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
onCloseAddNote = () => {
|
|
110
|
+
const newState = { addNoteOpen: false, editingNote: null };
|
|
111
|
+
if (!!this.state.editingNote) {
|
|
112
|
+
newState.noteInput = '';
|
|
113
|
+
newState.noteAttachments = [];
|
|
114
|
+
}
|
|
115
|
+
this.setState(newState);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
onOpenNoteMenu = (index) => {
|
|
119
|
+
if (this.state.noteMenuOpen === index) {
|
|
120
|
+
this.setState({ noteMenuOpen: null });
|
|
121
|
+
} else {
|
|
122
|
+
this.setState({ noteMenuOpen: index });
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
onHandlePDFFileChange = (event) => {
|
|
127
|
+
const file = event.target.files[0];
|
|
128
|
+
if (!file || this.state.uploadingNoteAttachment) return;
|
|
129
|
+
const noteAttachments = [...this.state.noteAttachments];
|
|
130
|
+
const newAttachment = {
|
|
131
|
+
Uploading: true,
|
|
132
|
+
Title: file.name,
|
|
133
|
+
};
|
|
134
|
+
noteAttachments.push(newAttachment);
|
|
135
|
+
this.setState({
|
|
136
|
+
noteAttachments,
|
|
137
|
+
});
|
|
138
|
+
Apis.fileActions
|
|
139
|
+
.uploadMediaAsync(file, file.name)
|
|
140
|
+
.then((fileRes) => {
|
|
141
|
+
newAttachment.Source = fileRes;
|
|
142
|
+
newAttachment.Uploading = false;
|
|
143
|
+
this.setState({
|
|
144
|
+
noteAttachments: [...this.state.noteAttachments],
|
|
145
|
+
});
|
|
146
|
+
})
|
|
147
|
+
.catch((uploadErrorRes) => {
|
|
148
|
+
console.log(uploadErrorRes);
|
|
149
|
+
newAttachment.Uploading = false;
|
|
150
|
+
this.setState({
|
|
151
|
+
noteAttachments: [...this.state.noteAttachments],
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
event.target.value = '';
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
onRemoveAttachment = (a) => {
|
|
158
|
+
const index = this.state.noteAttachments.indexOf(a);
|
|
159
|
+
if (index > -1) {
|
|
160
|
+
const newAttachments = [...this.state.noteAttachments];
|
|
161
|
+
newAttachments.splice(index, 1);
|
|
162
|
+
this.setState({
|
|
163
|
+
noteAttachments: newAttachments,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
onShowSelectAssignee = () => {
|
|
169
|
+
this.setState({
|
|
170
|
+
showingAssigneeSelector: true,
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
onCloseSelectAssignee = () => {
|
|
175
|
+
this.setState({
|
|
176
|
+
showingAssigneeSelector: false,
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
onSelectAssignee = (user) => {
|
|
181
|
+
this.setState({
|
|
182
|
+
selectedAssignee: user,
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
onConfirmAssignee = async () => {
|
|
187
|
+
this.setState({
|
|
188
|
+
confirmingAssignee: true,
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
if (this.state.selectedAssignee) {
|
|
192
|
+
await this.onAssignUser(this.state.selectedAssignee.id);
|
|
193
|
+
}
|
|
194
|
+
this.onCloseSelectAssignee();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('onConfirmAssignee', error);
|
|
197
|
+
}
|
|
198
|
+
this.setState({
|
|
199
|
+
confirmingAssignee: false,
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Method to handle user assignment
|
|
204
|
+
onAssignUser = async (userId) => {
|
|
205
|
+
try {
|
|
206
|
+
const res = await maintenanceActions.assignJob(this.state.jobId, userId);
|
|
207
|
+
this.getJob();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error('onAssignUser', err);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
onConfirmAddNote = async () => {
|
|
214
|
+
if (!this.isReadyToSaveNote()) return;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
this.setState({ submittingNote: true });
|
|
218
|
+
const res = await (this.state.editingNote
|
|
219
|
+
? maintenanceActions.editNote(
|
|
220
|
+
this.state.jobId,
|
|
221
|
+
this.state.editingNote,
|
|
222
|
+
this.state.noteInput,
|
|
223
|
+
this.state.noteAttachments.map((a) => {
|
|
224
|
+
return {
|
|
225
|
+
Title: a.Title,
|
|
226
|
+
Source: a.Source,
|
|
227
|
+
};
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
: maintenanceActions.addNote(
|
|
231
|
+
this.state.jobId,
|
|
232
|
+
this.state.noteInput,
|
|
233
|
+
this.state.noteAttachments.map((a) => {
|
|
234
|
+
return {
|
|
235
|
+
Title: a.Title,
|
|
236
|
+
Source: a.Source,
|
|
237
|
+
};
|
|
238
|
+
}),
|
|
239
|
+
));
|
|
240
|
+
this.setState(
|
|
241
|
+
{
|
|
242
|
+
job: res.data.job,
|
|
243
|
+
submittingNote: false,
|
|
244
|
+
addNoteOpen: false,
|
|
245
|
+
noteInput: '',
|
|
246
|
+
noteAttachments: [],
|
|
247
|
+
editingNote: null,
|
|
248
|
+
},
|
|
249
|
+
() => {
|
|
250
|
+
this.props.jobsLoaded([this.state.job]);
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error('onConfirmAddNote', err);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
onDeleteNote = (n) => {
|
|
259
|
+
if (!window.confirm(values.textAreYouSureYouWantToDeleteNote)) {
|
|
260
|
+
this.setState({ noteMenuOpen: null });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
maintenanceActions.deleteNote(this.state.jobId, n.Id);
|
|
265
|
+
const newNotes = _.filter(this.state.job.Notes, (note) => note.Id !== n.Id);
|
|
266
|
+
const newJob = { ...this.state.job };
|
|
267
|
+
newJob.Notes = newNotes;
|
|
268
|
+
this.setState({ job: newJob, noteMenuOpen: null });
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
onOpenEditNote = (n) => {
|
|
272
|
+
this.setState({
|
|
273
|
+
noteAttachments: n.Attachments || [],
|
|
274
|
+
noteInput: n.Note || '',
|
|
275
|
+
addNoteOpen: true,
|
|
276
|
+
editingNote: n.Id,
|
|
277
|
+
noteMenuOpen: null,
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
markSeen = () => {
|
|
282
|
+
const { job } = this.state;
|
|
283
|
+
const { auth } = this.props;
|
|
284
|
+
// Must have maintenance permission and not the requester
|
|
285
|
+
if (!Session.validateAccess(auth.site, values.permissionMaintenanceTracking, auth)) return;
|
|
286
|
+
if (auth.user.Id === job.userID) return;
|
|
287
|
+
|
|
288
|
+
this.setState({ updating: true }, async () => {
|
|
289
|
+
try {
|
|
290
|
+
const update = {
|
|
291
|
+
id: job.id,
|
|
292
|
+
seen: true,
|
|
293
|
+
status: job.status || 'Unassigned',
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await maintenanceActions.editJob(update, auth.site);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
this.setState({ updating: false });
|
|
299
|
+
console.log('markSeen error', error);
|
|
300
|
+
alert('Something went wrong with the request. Please try again.');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
getComments = () => {
|
|
306
|
+
reactionActions.getComments(this.state.jobId, values.featureKey, 0).then((res) => {
|
|
307
|
+
this.setState({ comments: res.data });
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
onAddComment = async () => {
|
|
312
|
+
const { commentInput, jobId, job, comments } = this.state;
|
|
313
|
+
try {
|
|
314
|
+
this.setState({ commentInput: '' });
|
|
315
|
+
const res = await reactionActions.addComment(jobId, values.featureKey, job.title, job.site, commentInput);
|
|
316
|
+
this.setState({ comments: [...comments, res.data] });
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('onAddComment', error);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
onHandleChange = (event) => {
|
|
323
|
+
var stateChange = {};
|
|
324
|
+
stateChange[event.target.getAttribute('id')] = event.target.value;
|
|
325
|
+
this.setState(stateChange);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
onToggleStatusChanger = () => {
|
|
329
|
+
this.setState({ statusChangerOpen: !this.state.statusChangerOpen });
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
onSelectStatus = async (status) => {
|
|
333
|
+
this.setState({
|
|
334
|
+
job: {
|
|
335
|
+
...this.state.job,
|
|
336
|
+
status: status,
|
|
337
|
+
},
|
|
338
|
+
statusChangerOpen: false,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const res = await maintenanceActions.editJobStatus(this.state.job.id, status);
|
|
343
|
+
this.setState({ job: res.data.job }, () => {
|
|
344
|
+
this.props.jobsLoaded([this.state.job]);
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error('onSelectStatus', error);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
renderStatusLabel() {
|
|
352
|
+
if (!this.state.job.status) return null;
|
|
353
|
+
const statusType = this.getStatusType(this.state.job.status);
|
|
354
|
+
const { auth } = this.props;
|
|
355
|
+
if (Session.validateAccess(auth.site, values.permissionMaintenanceTracking, auth)) {
|
|
356
|
+
return (
|
|
357
|
+
<div className="statusLabel pointer" onClick={this.onToggleStatusChanger} style={{ backgroundColor: statusType.color }}>
|
|
358
|
+
<span className="statusLabel_text">{statusType.text}</span>
|
|
359
|
+
{this.renderStatusChanger()}
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return (
|
|
364
|
+
<div className="statusLabel" style={{ backgroundColor: statusType.color }}>
|
|
365
|
+
<span className="statusLabel_text">{statusType.text}</span>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
renderNotesButton() {
|
|
371
|
+
const { auth } = this.props;
|
|
372
|
+
if (!Session.validateAccess(auth.site, values.permissionMaintenanceTracking, auth)) return null;
|
|
373
|
+
return (
|
|
374
|
+
<div
|
|
375
|
+
className="statusLabel pointer"
|
|
376
|
+
onClick={this.onOpenAddNote}
|
|
377
|
+
style={{ backgroundColor: Config.env.colourBrandingMain, marginLeft: 8 }}
|
|
378
|
+
>
|
|
379
|
+
<span className="statusLabel_text">Add Note</span>
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
renderAssignButton() {
|
|
385
|
+
const { auth } = this.props;
|
|
386
|
+
if (!Session.validateAccess(auth.site, values.permissionMaintenanceTracking, auth)) return null;
|
|
387
|
+
return (
|
|
388
|
+
<div
|
|
389
|
+
className="statusLabel pointer"
|
|
390
|
+
onClick={this.onShowSelectAssignee}
|
|
391
|
+
style={{ backgroundColor: Config.env.colourBrandingMain, marginLeft: 8 }}
|
|
392
|
+
>
|
|
393
|
+
<span className="statusLabel_text">Assign</span>
|
|
394
|
+
</div>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
renderStatusChanger() {
|
|
399
|
+
if (!this.state.statusChangerOpen) return null;
|
|
400
|
+
return (
|
|
401
|
+
<div className="statusChanger statusChanger-maintenance">
|
|
402
|
+
{Object.keys(StatusTypes).map((statusKey) => {
|
|
403
|
+
return (
|
|
404
|
+
<div
|
|
405
|
+
key={statusKey}
|
|
406
|
+
className="statusLabel"
|
|
407
|
+
onClick={() => this.onSelectStatus(statusKey)}
|
|
408
|
+
style={{ backgroundColor: StatusTypes[statusKey].color }}
|
|
409
|
+
>
|
|
410
|
+
<span className="statusLabel_text">{StatusTypes[statusKey].text}</span>
|
|
411
|
+
</div>
|
|
412
|
+
);
|
|
413
|
+
})}
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
renderComment(c) {
|
|
419
|
+
return <Components.Comment key={c.Id} comment={c} />;
|
|
420
|
+
return (
|
|
421
|
+
<div key={c.Id} className="comment">
|
|
422
|
+
<p className="comment_text">{Helper.toParagraphed(c.Comment)}</p>
|
|
423
|
+
<div className="comment_bottom">
|
|
424
|
+
<Components.ProfilePic className="comment_profilePic" size={25} image={c.User.profilePic} />
|
|
425
|
+
<p className="comment_name">{c.User.displayName}</p>
|
|
426
|
+
<p className="comment_time">{moment.utc(c.Timestamp).local().format('D MMM YYYY • h:mma')}</p>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
renderCommentSection() {
|
|
433
|
+
if (this.state.loadingComments) return null;
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<div className="padding-60 paddingLeft-20">
|
|
437
|
+
<div className="newTopBar paddingLeft-40">
|
|
438
|
+
<Components.Text type="formTitleSmall" className="marginBottom-16">
|
|
439
|
+
Comments
|
|
440
|
+
</Components.Text>
|
|
441
|
+
<div className="commentSection">{this.state.comments.map((c) => this.renderComment(c))}</div>
|
|
442
|
+
<div className="commentReply">
|
|
443
|
+
<div
|
|
444
|
+
className={`commentReply_button${!_.isEmpty(this.state.commentInput) ? ' commentReply_button-active' : ''}`}
|
|
445
|
+
onClick={this.onAddComment}
|
|
446
|
+
>
|
|
447
|
+
<FontAwesome className="commentReply_icon" name="paper-plane-o" />
|
|
448
|
+
</div>
|
|
449
|
+
<Textarea
|
|
450
|
+
id="commentInput"
|
|
451
|
+
placeholder="Reply here..."
|
|
452
|
+
type="text"
|
|
453
|
+
className="commentReply_input"
|
|
454
|
+
value={this.state.commentInput}
|
|
455
|
+
onChange={(e) => this.onHandleChange(e)}
|
|
456
|
+
/>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
renderImageGrid(images) {
|
|
464
|
+
const imagesToUse = images && images.length > 0 ? images : [];
|
|
465
|
+
return (
|
|
466
|
+
<div className="imageGrid">
|
|
467
|
+
{imagesToUse.map((image, i) => {
|
|
468
|
+
return (
|
|
469
|
+
<a href={image} target="_blank" rel="noopener noreferrer" key={i}>
|
|
470
|
+
<div className="imageGrid_image" style={{ backgroundImage: `url('${Helper.get1400(image)}')` }}></div>
|
|
471
|
+
</a>
|
|
472
|
+
);
|
|
473
|
+
})}
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
renderImages() {
|
|
479
|
+
if (_.isEmpty(this.state.job.image) && _.isEmpty(this.state.job.images)) return null;
|
|
480
|
+
|
|
481
|
+
const imagesToUse = _.isEmpty(this.state.job.image) ? this.state.job.images : [this.state.job.image];
|
|
482
|
+
return (
|
|
483
|
+
<div className="padding-60 paddingVertical-40 bottomDivideBorder">
|
|
484
|
+
<Components.Text type="formTitleSmall" className="marginBottom-16">
|
|
485
|
+
Images
|
|
486
|
+
</Components.Text>
|
|
487
|
+
{this.renderImageGrid(imagesToUse)}
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
renderCustomFields() {
|
|
493
|
+
const { job } = this.state;
|
|
494
|
+
const { customFields } = job;
|
|
495
|
+
|
|
496
|
+
const labelClass = 'fieldLabel';
|
|
497
|
+
const answerClass = 'fontRegular fontSize-16 text-dark marginTop-5';
|
|
498
|
+
|
|
499
|
+
const renderAnswer = (field) => {
|
|
500
|
+
switch (field.type) {
|
|
501
|
+
case 'date':
|
|
502
|
+
return <div className={answerClass}>{field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD-MMM-YYYY') : ''}</div>;
|
|
503
|
+
case 'time':
|
|
504
|
+
return <div className={answerClass}>{field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : ''}</div>;
|
|
505
|
+
case 'yn':
|
|
506
|
+
return <div className={answerClass}>{field.answer ? 'Yes' : 'No'}</div>;
|
|
507
|
+
case 'checkbox':
|
|
508
|
+
return <div className={answerClass}>{field.answer && Array.isArray(field.answer) ? field.answer.join(', ') : ''}</div>;
|
|
509
|
+
case 'image':
|
|
510
|
+
return this.renderImageGrid(field.answer);
|
|
511
|
+
default:
|
|
512
|
+
return <div className={answerClass}>{field.answer}</div>;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
return (
|
|
517
|
+
<div className="padding-60 paddingVertical-40 bottomDivideBorder">
|
|
518
|
+
{customFields.map((field, index) => {
|
|
519
|
+
if (['staticTitle', 'staticText'].includes(field.type)) return null;
|
|
520
|
+
if (_.isNil(field.answer) || field.answer === '') return null;
|
|
521
|
+
return (
|
|
522
|
+
<div key={index} className="marginTop-16">
|
|
523
|
+
<div className={labelClass}>{field.label}</div>
|
|
524
|
+
{renderAnswer(field)}
|
|
525
|
+
</div>
|
|
526
|
+
);
|
|
527
|
+
})}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
renderInner() {
|
|
533
|
+
if (this.state.job == null) return null;
|
|
534
|
+
const { customFields } = this.state.job;
|
|
535
|
+
const hasCustomFields = customFields && customFields.length > 0;
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div style={{ paddingBottom: 40 }}>
|
|
539
|
+
<div className="padding-60 paddingVertical-40 bottomDivideBorder relative">
|
|
540
|
+
<Components.Text type="formTitleLarge" className="marginBottom-8">
|
|
541
|
+
{this.state.job.title || values.textSingularName}
|
|
542
|
+
</Components.Text>
|
|
543
|
+
<Components.Text type="formTitleMedium" className="marginBottom-24">
|
|
544
|
+
{values.textEntityName} #{this.state.job.jobId}
|
|
545
|
+
</Components.Text>
|
|
546
|
+
<div className="marginTop-16">
|
|
547
|
+
<div className={'fieldLabel'}>Submission date</div>
|
|
548
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>
|
|
549
|
+
{moment.utc(this.state.job.createdTime).local().format('D MMM YY')}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div className="marginTop-16">
|
|
553
|
+
<div className={'fieldLabel'}>Type</div>
|
|
554
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.type}</div>
|
|
555
|
+
</div>
|
|
556
|
+
<div className="marginTop-16">
|
|
557
|
+
<div className={'fieldLabel'}>Address</div>
|
|
558
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.room}</div>
|
|
559
|
+
</div>
|
|
560
|
+
{hasCustomFields ? null : (
|
|
561
|
+
<div className="marginTop-16">
|
|
562
|
+
<div className={'fieldLabel'}>Description {this.state.job.image ? '- (image supplied)' : ''}</div>
|
|
563
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.description}</div>
|
|
564
|
+
</div>
|
|
565
|
+
)}
|
|
566
|
+
</div>
|
|
567
|
+
<div className="padding-60 paddingVertical-40 bottomDivideBorder">
|
|
568
|
+
<Components.Text type="formTitleSmall" className="marginBottom-16">
|
|
569
|
+
Contact Details
|
|
570
|
+
</Components.Text>
|
|
571
|
+
<div className="marginTop-16">
|
|
572
|
+
<div className={'fieldLabel'}>Name</div>
|
|
573
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.userName}</div>
|
|
574
|
+
</div>
|
|
575
|
+
<div className="marginTop-16">
|
|
576
|
+
<div className={'fieldLabel'}>Contact number</div>
|
|
577
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>
|
|
578
|
+
{_.isEmpty(this.state.job.phone) ? 'No phone provided' : this.state.job.phone}
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
{hasCustomFields ? null : (
|
|
582
|
+
<div>
|
|
583
|
+
<div className="marginTop-16">
|
|
584
|
+
<div className={'fieldLabel'}>Should person be home?</div>
|
|
585
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.isHome ? 'Yes' : 'No'}</div>
|
|
586
|
+
</div>
|
|
587
|
+
{this.state.job.isHome && this.state.job.homeText && (
|
|
588
|
+
<div className="marginTop-16">
|
|
589
|
+
<div className={'fieldLabel'}>When</div>
|
|
590
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>{this.state.job.homeText}</div>
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
</div>
|
|
594
|
+
)}
|
|
595
|
+
</div>
|
|
596
|
+
{hasCustomFields ? null : this.renderImages()}
|
|
597
|
+
{hasCustomFields ? this.renderCustomFields() : null}
|
|
598
|
+
{this.renderCommentSection()}
|
|
599
|
+
</div>
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
renderHistoryEntry(e, i) {
|
|
604
|
+
const { job } = this.state;
|
|
605
|
+
const entryToUse = e || {
|
|
606
|
+
timestamp: job.createdTime,
|
|
607
|
+
status: 'Unassigned',
|
|
608
|
+
user: {
|
|
609
|
+
displayName: job.userName,
|
|
610
|
+
id: job.userID,
|
|
611
|
+
profilePic: job.userProfilePic,
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
const statusType = this.getStatusType(entryToUse.status);
|
|
615
|
+
return (
|
|
616
|
+
<div className="ticketHistoryEntry" key={i}>
|
|
617
|
+
<p className="ticketHistoryEntry_timestamp">{moment.utc(entryToUse.timestamp).local().format('D MMM YYYY h:mma')}</p>
|
|
618
|
+
<div className="statusLabel statusLabel-large statusLabel-full" style={{ backgroundColor: statusType.color }}>
|
|
619
|
+
<span className="statusLabel_text">{e ? `Marked as ${statusType.text}` : `${values.textEntityName} opened`}</span>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
renderNote(note, index) {
|
|
626
|
+
return (
|
|
627
|
+
<div className="ticketHistoryEntry" key={index}>
|
|
628
|
+
<p className="ticketHistoryEntry_timestamp">{moment.utc(note.Timestamp).local().format('D MMM YYYY h:mma')}</p>
|
|
629
|
+
<div className="statusLabel statusLabel-large statusLabel-full" style={{ backgroundColor: '#6e79c5' }}>
|
|
630
|
+
<span className="statusLabel_text">Staff Notes</span>
|
|
631
|
+
</div>
|
|
632
|
+
<div className="maintenanceNote">
|
|
633
|
+
<div className="maintenanceNote_top">
|
|
634
|
+
{this.props.auth && this.props.auth.user && this.props.auth.user.Id === note.User.id && (
|
|
635
|
+
<Components.SVGIcon
|
|
636
|
+
colour={Colours.COLOUR_DUSK_LIGHT}
|
|
637
|
+
icon="more15"
|
|
638
|
+
className="maintenanceNote_moreIcon"
|
|
639
|
+
onClick={() => this.onOpenNoteMenu(index)}
|
|
640
|
+
/>
|
|
641
|
+
)}
|
|
642
|
+
<p className="maintenanceNote_name">{note.User.displayName}</p>
|
|
643
|
+
{this.state.noteMenuOpen === index && (
|
|
644
|
+
<Components.MoreMenu
|
|
645
|
+
options={[
|
|
646
|
+
{
|
|
647
|
+
key: 'edit',
|
|
648
|
+
text: 'Edit',
|
|
649
|
+
onPress: () => this.onOpenEditNote(note),
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
key: 'delete',
|
|
653
|
+
text: 'Delete',
|
|
654
|
+
onPress: () => this.onDeleteNote(note),
|
|
655
|
+
},
|
|
656
|
+
]}
|
|
657
|
+
/>
|
|
658
|
+
)}
|
|
659
|
+
</div>
|
|
660
|
+
<p className="maintenanceNote_text">{Helper.toParagraphed(note.Note)}</p>
|
|
661
|
+
{note.Attachments.map((a, i) => this.renderAttachment(a, i))}
|
|
662
|
+
</div>
|
|
663
|
+
</div>
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
renderAssignment() {
|
|
668
|
+
const { job } = this.state;
|
|
669
|
+
if (!job) return null;
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
|
|
673
|
+
<div className="newTopBar clearfix flex flex-reverse">
|
|
674
|
+
{this.renderAssignButton()}
|
|
675
|
+
<Components.Text type="formTitleSmall" className="flex-1">
|
|
676
|
+
Assignment
|
|
677
|
+
</Components.Text>
|
|
678
|
+
</div>
|
|
679
|
+
<div>
|
|
680
|
+
<div className="marginTop-16">
|
|
681
|
+
<div className={'fieldLabel'}>Assigned to</div>
|
|
682
|
+
<div className={'fontRegular fontSize-16 text-dark marginTop-5'}>
|
|
683
|
+
{job.Assignee ? <Components.UserListing user={job.Assignee} /> : 'Unassigned'}
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
renderAssignmentEntry(e, i) {
|
|
692
|
+
return (
|
|
693
|
+
<div className="ticketHistoryEntry" key={i}>
|
|
694
|
+
<p className="ticketHistoryEntry_timestamp">{moment.utc(e.timestamp).local().format('D MMM YYYY h:mma')}</p>
|
|
695
|
+
<div className="statusLabel statusLabel-large statusLabel-full" style={{ backgroundColor: Colours.COLOUR_DUSK }}>
|
|
696
|
+
<span className="statusLabel_text">
|
|
697
|
+
{e.user.displayName} assigned the {values.textSingularName} to {e.assignedUser ? e.assignedUser.displayName : 'Unassigned'}
|
|
698
|
+
</span>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
renderOverview() {
|
|
705
|
+
const { job } = this.state;
|
|
706
|
+
if (!job || !job.history) return null;
|
|
707
|
+
|
|
708
|
+
const source = _.sortBy(
|
|
709
|
+
[
|
|
710
|
+
...job.history.map((e) => {
|
|
711
|
+
return { ...e, EntryType: e.EntryType || 'status' };
|
|
712
|
+
}),
|
|
713
|
+
...(job.Notes || []).map((e) => {
|
|
714
|
+
return { ...e, timestamp: e.Timestamp, EntryType: 'note' };
|
|
715
|
+
}),
|
|
716
|
+
],
|
|
717
|
+
'timestamp',
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
return (
|
|
721
|
+
<div className="padding-32 paddingVertical-40 bottomDivideBorder relative">
|
|
722
|
+
<div className="newTopBar clearfix flex flex-reverse">
|
|
723
|
+
{this.renderNotesButton()}
|
|
724
|
+
{this.renderStatusLabel()}
|
|
725
|
+
<Components.Text type="formTitleSmall" className="flex-1">
|
|
726
|
+
Status History
|
|
727
|
+
</Components.Text>
|
|
728
|
+
</div>
|
|
729
|
+
{this.renderHistoryEntry(null, -1)}
|
|
730
|
+
{_.map(source, (e, i) => {
|
|
731
|
+
switch (e.EntryType) {
|
|
732
|
+
case 'status':
|
|
733
|
+
return this.renderHistoryEntry(e, i);
|
|
734
|
+
case 'note':
|
|
735
|
+
return this.renderNote(e, i);
|
|
736
|
+
case 'assignment':
|
|
737
|
+
return this.renderAssignmentEntry(e, i);
|
|
738
|
+
default:
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
})}
|
|
742
|
+
</div>
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
renderButtons() {
|
|
747
|
+
return (
|
|
748
|
+
<div>
|
|
749
|
+
<Components.Button
|
|
750
|
+
inline
|
|
751
|
+
buttonType="tertiary"
|
|
752
|
+
onClick={() => {
|
|
753
|
+
window.history.back();
|
|
754
|
+
}}
|
|
755
|
+
isActive
|
|
756
|
+
style={{ marginRight: 16 }}
|
|
757
|
+
>
|
|
758
|
+
Back
|
|
759
|
+
</Components.Button>
|
|
760
|
+
{Session.validateAccess(this.props.auth.site, values.permissionMaintenanceTracking, this.props.auth) &&
|
|
761
|
+
!_.isEmpty(this.state.job) && (
|
|
762
|
+
<Link to={`${values.routeAddRequest}/${this.state.jobId}`}>
|
|
763
|
+
<Components.Button inline style={{ marginRight: 25 }} buttonType="outlined" isActive onClick={this.editJob}>
|
|
764
|
+
Edit Details
|
|
765
|
+
</Components.Button>
|
|
766
|
+
</Link>
|
|
767
|
+
)}
|
|
768
|
+
</div>
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
renderAttachment(attachment, index, onRemove) {
|
|
773
|
+
if (!attachment) return null;
|
|
774
|
+
|
|
775
|
+
return (
|
|
776
|
+
<Components.Attachment
|
|
777
|
+
key={index}
|
|
778
|
+
uploading={attachment.Uploading}
|
|
779
|
+
source={attachment.Source}
|
|
780
|
+
title={attachment.Title}
|
|
781
|
+
onRemove={onRemove ? () => onRemove(attachment) : undefined}
|
|
782
|
+
/>
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
renderAddNotePopup() {
|
|
787
|
+
if (!this.state.addNoteOpen) return null;
|
|
788
|
+
|
|
789
|
+
if (this.state.submittingNote) {
|
|
790
|
+
return (
|
|
791
|
+
<Components.Popup title="Saving Note" maxWidth={600} hasPadding>
|
|
792
|
+
<div className="flex flex-center-row">
|
|
793
|
+
<FontAwesome className="spinner" name="spinner fa-pulse fa-fw" />
|
|
794
|
+
</div>
|
|
795
|
+
</Components.Popup>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return (
|
|
800
|
+
<Components.Popup
|
|
801
|
+
title={`${this.state.editingNote ? 'Edit' : 'Add'} Note`}
|
|
802
|
+
onClose={this.onCloseAddNote}
|
|
803
|
+
maxWidth={600}
|
|
804
|
+
hasPadding
|
|
805
|
+
buttons={[
|
|
806
|
+
{
|
|
807
|
+
type: 'primary',
|
|
808
|
+
onClick: this.onConfirmAddNote,
|
|
809
|
+
isActive: this.isReadyToSaveNote(),
|
|
810
|
+
text: 'Save',
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
type: 'tertiary',
|
|
814
|
+
onClick: this.onCloseAddNote,
|
|
815
|
+
isActive: true,
|
|
816
|
+
text: 'Cancel',
|
|
817
|
+
},
|
|
818
|
+
]}
|
|
819
|
+
>
|
|
820
|
+
<Components.GenericInput
|
|
821
|
+
id="noteInput"
|
|
822
|
+
type="textarea"
|
|
823
|
+
componentClass="textarea"
|
|
824
|
+
value={this.state.noteInput}
|
|
825
|
+
placeholder="Enter note"
|
|
826
|
+
onChange={(e) => this.onHandleChange(e)}
|
|
827
|
+
inputStyle={{
|
|
828
|
+
width: 400,
|
|
829
|
+
}}
|
|
830
|
+
/>
|
|
831
|
+
<Components.Text type="h5">Attachments</Components.Text>
|
|
832
|
+
{this.state.noteAttachments.map((a, i) => this.renderAttachment(a, i, this.onRemoveAttachment))}
|
|
833
|
+
<input
|
|
834
|
+
ref={(input) => (this.attachmentInput = input)}
|
|
835
|
+
id="attachmentInput"
|
|
836
|
+
type="file"
|
|
837
|
+
className="fileInput"
|
|
838
|
+
onChange={(e) => this.onHandlePDFFileChange(e)}
|
|
839
|
+
accept="application/pdf"
|
|
840
|
+
/>
|
|
841
|
+
<div
|
|
842
|
+
className="iconTextButton"
|
|
843
|
+
onClick={() => {
|
|
844
|
+
this.attachmentInput.click();
|
|
845
|
+
}}
|
|
846
|
+
>
|
|
847
|
+
<FontAwesome className="iconTextButton_icon" name="paperclip" />
|
|
848
|
+
<p className="iconTextButton_text">Add Attachment</p>
|
|
849
|
+
</div>
|
|
850
|
+
</Components.Popup>
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
renderUsers() {
|
|
855
|
+
let content = null;
|
|
856
|
+
if (this.state.confirmingAssignee) {
|
|
857
|
+
content = (
|
|
858
|
+
<div className="flex flex-center-row">
|
|
859
|
+
<FontAwesome className="spinner" name="spinner fa-pulse fa-fw" />
|
|
860
|
+
</div>
|
|
861
|
+
);
|
|
862
|
+
} else if (this.state.selectedAssignee) {
|
|
863
|
+
content = (
|
|
864
|
+
<div>
|
|
865
|
+
<Components.UserListing
|
|
866
|
+
key={this.state.selectedAssignee.id}
|
|
867
|
+
user={this.state.selectedAssignee}
|
|
868
|
+
rightContent={
|
|
869
|
+
<Components.SVGIcon
|
|
870
|
+
className="removeIcon"
|
|
871
|
+
icon="close"
|
|
872
|
+
onClick={() => {
|
|
873
|
+
this.onSelectAssignee();
|
|
874
|
+
}}
|
|
875
|
+
colour={Colours.COLOUR_DUSK}
|
|
876
|
+
/>
|
|
877
|
+
}
|
|
878
|
+
/>
|
|
879
|
+
</div>
|
|
880
|
+
);
|
|
881
|
+
} else {
|
|
882
|
+
content = (
|
|
883
|
+
<div>
|
|
884
|
+
<Components.GenericInput
|
|
885
|
+
id="userSearch"
|
|
886
|
+
type="text"
|
|
887
|
+
// label="Search"
|
|
888
|
+
placeholder="Search name"
|
|
889
|
+
value={this.state.userSearch}
|
|
890
|
+
onChange={(e) => this.onHandleChange(e)}
|
|
891
|
+
alwaysShowLabel
|
|
892
|
+
/>
|
|
893
|
+
{_.sortBy(this.state.assignees, (u) => u.displayName.toUpperCase())
|
|
894
|
+
.filter((u) => {
|
|
895
|
+
if (_.isEmpty(this.state.userSearch)) return true;
|
|
896
|
+
return u.displayName.toUpperCase().indexOf(this.state.userSearch.toUpperCase()) > -1;
|
|
897
|
+
})
|
|
898
|
+
.map((user) => {
|
|
899
|
+
return (
|
|
900
|
+
<Components.UserListing
|
|
901
|
+
key={user.id}
|
|
902
|
+
user={user}
|
|
903
|
+
onClick={() => {
|
|
904
|
+
this.onSelectAssignee(user);
|
|
905
|
+
}}
|
|
906
|
+
/>
|
|
907
|
+
);
|
|
908
|
+
})}
|
|
909
|
+
</div>
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
return (
|
|
913
|
+
<div className="genericInputContainer">
|
|
914
|
+
<Components.Text type="formLabel">Select User</Components.Text>
|
|
915
|
+
{content}
|
|
916
|
+
</div>
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
renderUserSelectionPopup() {
|
|
921
|
+
if (!this.state.showingAssigneeSelector) return null;
|
|
922
|
+
return (
|
|
923
|
+
<Components.Popup
|
|
924
|
+
title="Assign Job"
|
|
925
|
+
onClose={this.onCloseSelectAssignee}
|
|
926
|
+
maxWidth={600}
|
|
927
|
+
hasPadding
|
|
928
|
+
buttons={[
|
|
929
|
+
{
|
|
930
|
+
type: 'primary',
|
|
931
|
+
onClick: this.onConfirmAssignee,
|
|
932
|
+
isActive: !!this.state.selectedAssignee,
|
|
933
|
+
text: 'Confirm',
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
type: 'tertiary',
|
|
937
|
+
onClick: this.onCloseSelectAssignee,
|
|
938
|
+
isActive: true,
|
|
939
|
+
text: 'Cancel',
|
|
940
|
+
},
|
|
941
|
+
]}
|
|
942
|
+
>
|
|
943
|
+
{this.renderUsers()}
|
|
944
|
+
</Components.Popup>
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
render() {
|
|
949
|
+
return (
|
|
950
|
+
<Components.OverlayPage>
|
|
951
|
+
{this.renderAddNotePopup()}
|
|
952
|
+
{this.renderUserSelectionPopup()}
|
|
953
|
+
<Components.OverlayPageContents>
|
|
954
|
+
<Components.OverlayPageSection className="pageSectionWrapper--fixedPopupSize">{this.renderInner()}</Components.OverlayPageSection>
|
|
955
|
+
<Components.OverlayPageSection className="pageSectionWrapper--newPopupSide pageSectionWrapper--newPopupSide-fixedWidth">
|
|
956
|
+
{this.renderAssignment()}
|
|
957
|
+
{this.renderOverview()}
|
|
958
|
+
</Components.OverlayPageSection>
|
|
959
|
+
</Components.OverlayPageContents>
|
|
960
|
+
<Components.OverlayPageBottomButtons>{this.renderButtons()}</Components.OverlayPageBottomButtons>
|
|
961
|
+
</Components.OverlayPage>
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const mapStateToProps = (state) => {
|
|
967
|
+
const { auth } = state;
|
|
968
|
+
return { auth };
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
export default connect(mapStateToProps, { jobsLoaded })(withRouter(Job));
|