@plusscommunities/pluss-maintenance-web 1.1.16 → 1.1.18-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/dist/index.cjs.js +4847 -14535
- package/dist/index.esm.js +4823 -14523
- package/dist/index.umd.js +4850 -14525
- package/package.json +7 -2
- package/src/actions/types.js +6 -4
- package/src/apis/maintenanceActions.js +34 -35
- package/src/components/ActivityText.js +12 -11
- package/src/components/AnalyticsHub.js +21 -12
- package/src/components/JobList.js +66 -24
- package/src/components/JobTypes.js +10 -162
- package/src/components/PreviewGrid.js +20 -7
- package/src/feature.config.js +41 -33
- package/src/index.js +15 -2
- package/src/screens/AddJob.js +482 -121
- package/src/screens/AddJobType.js +841 -0
- package/src/screens/Job.js +98 -40
- package/src/screens/RequestsHub.js +16 -8
- 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 +57 -0
- package/src/values.config.js +57 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import { Table } from 'react-bootstrap';
|
|
3
|
+
import { withRouter } from 'react-router';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import { connect } from 'react-redux';
|
|
6
|
+
import FontAwesome from 'react-fontawesome';
|
|
7
|
+
import { PlussCore } from '../feature.config';
|
|
8
|
+
import { jobTypesUpdate } from '../actions';
|
|
9
|
+
import { maintenanceActions } from '../apis';
|
|
10
|
+
import { values } from '../values.config';
|
|
11
|
+
|
|
12
|
+
const { Components, Session, Helper } = PlussCore;
|
|
13
|
+
const DEFAULT_FIELD = { type: 'text', label: '', mandatory: false, isTitle: false, values: [''] };
|
|
14
|
+
|
|
15
|
+
class AddJobType extends Component {
|
|
16
|
+
constructor(props) {
|
|
17
|
+
super(props);
|
|
18
|
+
|
|
19
|
+
this.fieldTypes = [
|
|
20
|
+
{
|
|
21
|
+
Title: 'Text Input',
|
|
22
|
+
Key: 'text',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
Title: 'Email Input',
|
|
26
|
+
Key: 'email',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
Title: 'Phone Input',
|
|
30
|
+
Key: 'phone',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
Title: 'Date Input',
|
|
34
|
+
Key: 'date',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
Title: 'Time Input',
|
|
38
|
+
Key: 'time',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
Title: 'Image Input',
|
|
42
|
+
Key: 'image',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
Title: 'Yes/No Question',
|
|
46
|
+
Key: 'yn',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
Title: 'Multiple Choice',
|
|
50
|
+
Key: 'multichoice',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
Title: 'Checkboxes',
|
|
54
|
+
Key: 'checkbox',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
Title: 'Title Text',
|
|
58
|
+
Key: 'staticTitle',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
Title: 'Paragraph Text',
|
|
62
|
+
Key: 'staticText',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
this.state = {
|
|
67
|
+
loading: false,
|
|
68
|
+
jobTypeId: Helper.safeReadParams(this.props, 'jobTypeId') ? this.props.match.params.jobTypeId : null,
|
|
69
|
+
jobTypeName: '',
|
|
70
|
+
jobTypeEmail: '',
|
|
71
|
+
jobTypeDescription: '',
|
|
72
|
+
hasCustomFields: values.forceCustomFields ? true : false,
|
|
73
|
+
customFields: [_.cloneDeep(DEFAULT_FIELD)],
|
|
74
|
+
jobTypeLevel: 1,
|
|
75
|
+
warnings: ['this is a test warning'],
|
|
76
|
+
showWarnings: false,
|
|
77
|
+
submitting: false,
|
|
78
|
+
success: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
UNSAFE_componentWillMount() {
|
|
83
|
+
Session.checkLoggedIn(this, this.props.auth);
|
|
84
|
+
if (this.state.jobTypeId) this.getJobType();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getJobType = async () => {
|
|
88
|
+
try {
|
|
89
|
+
const res = await maintenanceActions.getJobType(this.props.auth.site, this.state.jobTypeId);
|
|
90
|
+
const { typeName, email, description, level, hasCustomFields, customFields } = res.data;
|
|
91
|
+
this.setState({
|
|
92
|
+
jobTypeName: typeName,
|
|
93
|
+
jobTypeEmail: email,
|
|
94
|
+
jobTypeDescription: description,
|
|
95
|
+
jobTypeLevel: level,
|
|
96
|
+
hasCustomFields,
|
|
97
|
+
customFields,
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('getJobType', error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
getFieldTypeTitle = (type) => {
|
|
105
|
+
const fieldType = this.fieldTypes.find((f) => f.Key === type);
|
|
106
|
+
return fieldType ? fieldType.Title : '';
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
validateEmail = () => {
|
|
110
|
+
const { jobTypeEmail } = this.state;
|
|
111
|
+
return !_.isEmpty(jobTypeEmail) && Helper.isEmail(jobTypeEmail);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
isJobTypeValid = () => {
|
|
115
|
+
const { jobTypeName, jobTypeDescription } = this.state;
|
|
116
|
+
|
|
117
|
+
if (_.isEmpty(jobTypeName)) return false;
|
|
118
|
+
if (!this.validateEmail()) return false;
|
|
119
|
+
if (_.isEmpty(jobTypeDescription)) return false;
|
|
120
|
+
if (!this.validateCustomFields()) return false;
|
|
121
|
+
|
|
122
|
+
return true;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
validateCustomFields() {
|
|
126
|
+
const { submitting, hasCustomFields, customFields } = this.state;
|
|
127
|
+
if (submitting) return false;
|
|
128
|
+
|
|
129
|
+
// Validate custom fields
|
|
130
|
+
const warnings = [];
|
|
131
|
+
if (hasCustomFields) {
|
|
132
|
+
let missingLabel = false;
|
|
133
|
+
let hasMandatoryField = false;
|
|
134
|
+
let titleFieldCount = 0;
|
|
135
|
+
customFields.forEach((field) => {
|
|
136
|
+
const { type, label, values, mandatory, isTitle } = field;
|
|
137
|
+
if (['staticTitle', 'staticText'].includes(type)) return;
|
|
138
|
+
|
|
139
|
+
if (_.isEmpty(label)) {
|
|
140
|
+
missingLabel = true;
|
|
141
|
+
} else if (
|
|
142
|
+
_.isEmpty(type) ||
|
|
143
|
+
((type === 'multichoice' || type === 'checkbox') && (!values || values.length < 2 || !values.every((value) => !_.isEmpty(value))))
|
|
144
|
+
) {
|
|
145
|
+
warnings.push(`'${label}' is incomplete`);
|
|
146
|
+
}
|
|
147
|
+
if (mandatory) hasMandatoryField = true;
|
|
148
|
+
if (isTitle) titleFieldCount += 1;
|
|
149
|
+
});
|
|
150
|
+
if (missingLabel) {
|
|
151
|
+
warnings.push('All inputs must have a label');
|
|
152
|
+
}
|
|
153
|
+
if (!hasMandatoryField) {
|
|
154
|
+
warnings.push('There must be at least one required input');
|
|
155
|
+
}
|
|
156
|
+
if (titleFieldCount === 0) {
|
|
157
|
+
warnings.push('One of the required inputs must be selected as title for the request');
|
|
158
|
+
} else if (titleFieldCount > 1) {
|
|
159
|
+
warnings.push('Only one required input can be selected as title for the request');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.setState({ warnings });
|
|
164
|
+
return warnings.length === 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onHandleChange = (event) => {
|
|
168
|
+
var stateChange = {};
|
|
169
|
+
stateChange[event.target.getAttribute('id')] = event.target.value;
|
|
170
|
+
this.setState(stateChange);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
onFieldTypeChanged = (fieldIndex, key) => {
|
|
174
|
+
const customFields = [...this.state.customFields];
|
|
175
|
+
if (customFields[fieldIndex].type === key) return;
|
|
176
|
+
|
|
177
|
+
customFields[fieldIndex] = _.cloneDeep(DEFAULT_FIELD);
|
|
178
|
+
customFields[fieldIndex].type = key;
|
|
179
|
+
|
|
180
|
+
this.setState({ customFields });
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
onFieldLabelChanged = (fieldIndex, event) => {
|
|
184
|
+
const customFields = [...this.state.customFields];
|
|
185
|
+
customFields[fieldIndex].label = event.target.value;
|
|
186
|
+
|
|
187
|
+
this.setState({ customFields });
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
onFieldPlaceHolderChanged = (fieldIndex, event) => {
|
|
191
|
+
const customFields = [...this.state.customFields];
|
|
192
|
+
customFields[fieldIndex].placeHolder = event.target.value;
|
|
193
|
+
|
|
194
|
+
this.setState({ customFields });
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
onFieldMandatoryChanged = (fieldIndex) => {
|
|
198
|
+
const customFields = [...this.state.customFields];
|
|
199
|
+
customFields[fieldIndex].mandatory = !customFields[fieldIndex].mandatory;
|
|
200
|
+
if (!customFields[fieldIndex].mandatory) {
|
|
201
|
+
// If a field is not mandatory, it cannot be a title field
|
|
202
|
+
customFields[fieldIndex].isTitle = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.setState({ customFields });
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
onFieldIsTitleChanged = (fieldIndex) => {
|
|
209
|
+
const customFields = [...this.state.customFields];
|
|
210
|
+
customFields[fieldIndex].isTitle = !customFields[fieldIndex].isTitle;
|
|
211
|
+
if (customFields[fieldIndex].isTitle) {
|
|
212
|
+
// If a field is title field, force mandatory
|
|
213
|
+
customFields[fieldIndex].mandatory = true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.setState({ customFields });
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
onFieldOptionChanged = (fieldIndex, optionIndex, event) => {
|
|
220
|
+
const customFields = [...this.state.customFields];
|
|
221
|
+
const prevValue = customFields[fieldIndex].values[optionIndex];
|
|
222
|
+
customFields[fieldIndex].values[optionIndex] = event.target.value;
|
|
223
|
+
// Change corresponding validation if exists
|
|
224
|
+
const validations = customFields[fieldIndex].validation;
|
|
225
|
+
let validation = validations ? validations.find((val) => val.value === prevValue) : null;
|
|
226
|
+
if (validation) validation.value = event.target.value;
|
|
227
|
+
|
|
228
|
+
this.setState({ customFields });
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
onAddNewOption = (fieldIndex) => {
|
|
232
|
+
const customFields = [...this.state.customFields];
|
|
233
|
+
customFields[fieldIndex].values.push('');
|
|
234
|
+
this.setState({ customFields });
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
onRemoveOption = (fieldIndex, optionIndex) => {
|
|
238
|
+
const customFields = [...this.state.customFields];
|
|
239
|
+
customFields[fieldIndex].values.splice(optionIndex, 1);
|
|
240
|
+
this.setState({ customFields });
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
onRemoveField(fieldIndex) {
|
|
244
|
+
const customFields = [...this.state.customFields];
|
|
245
|
+
if (customFields.length < 2) return;
|
|
246
|
+
|
|
247
|
+
customFields.splice(fieldIndex, 1);
|
|
248
|
+
this.setState({ customFields });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
onAddNewField = () => {
|
|
252
|
+
const customFields = [...this.state.customFields];
|
|
253
|
+
customFields.push(_.cloneDeep(DEFAULT_FIELD));
|
|
254
|
+
this.setState({ customFields });
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
onMoveFieldPrev = (fieldIndex) => {
|
|
258
|
+
if (fieldIndex === 0) return;
|
|
259
|
+
const customFields = [...this.state.customFields];
|
|
260
|
+
const item = customFields.splice(fieldIndex, 1)[0];
|
|
261
|
+
customFields.splice(fieldIndex - 1, 0, item);
|
|
262
|
+
|
|
263
|
+
this.setState({ customFields });
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
onMoveFieldNext = (fieldIndex) => {
|
|
267
|
+
const customFields = [...this.state.customFields];
|
|
268
|
+
if (fieldIndex > customFields.length - 1) return;
|
|
269
|
+
const item = customFields.splice(fieldIndex, 1)[0];
|
|
270
|
+
customFields.splice(fieldIndex + 1, 0, item);
|
|
271
|
+
|
|
272
|
+
this.setState({ customFields });
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
onBack = () => {
|
|
276
|
+
window.history.back();
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
onSave = () => {
|
|
280
|
+
const { site } = this.props.auth;
|
|
281
|
+
const { submitting, jobTypeId, jobTypeName, jobTypeEmail, jobTypeDescription, jobTypeLevel, hasCustomFields, customFields } =
|
|
282
|
+
this.state;
|
|
283
|
+
|
|
284
|
+
if (submitting) return;
|
|
285
|
+
if (!this.isJobTypeValid()) {
|
|
286
|
+
this.setState({ showWarnings: true });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.setState({ submitting: true }, async () => {
|
|
291
|
+
try {
|
|
292
|
+
if (jobTypeId) {
|
|
293
|
+
await maintenanceActions.editJobType(
|
|
294
|
+
site,
|
|
295
|
+
jobTypeId,
|
|
296
|
+
jobTypeName,
|
|
297
|
+
jobTypeEmail,
|
|
298
|
+
jobTypeDescription,
|
|
299
|
+
jobTypeLevel,
|
|
300
|
+
hasCustomFields,
|
|
301
|
+
customFields,
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
await maintenanceActions.addJobType(
|
|
305
|
+
site,
|
|
306
|
+
jobTypeName,
|
|
307
|
+
jobTypeEmail,
|
|
308
|
+
jobTypeDescription,
|
|
309
|
+
jobTypeLevel,
|
|
310
|
+
hasCustomFields,
|
|
311
|
+
customFields,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
this.props.jobTypesUpdate(site);
|
|
315
|
+
this.setState({ submitting: false, success: true });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('onSave', error);
|
|
318
|
+
this.setState({ submitting: false });
|
|
319
|
+
alert('Something went wrong with the request. Please try again.');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
renderBaseForm() {
|
|
325
|
+
const { success, jobTypeId, showWarnings, jobTypeName, jobTypeEmail, jobTypeDescription, hasCustomFields } = this.state;
|
|
326
|
+
if (success) return null;
|
|
327
|
+
|
|
328
|
+
// TODO: Request Type should be configurable
|
|
329
|
+
return (
|
|
330
|
+
<div className="padding-60 paddingVertical-40 bottomDivideBorder">
|
|
331
|
+
<Components.Text type="formTitleLarge" className="marginBottom-24">
|
|
332
|
+
{!jobTypeId ? 'New' : 'Edit'} Request Type
|
|
333
|
+
</Components.Text>
|
|
334
|
+
<Components.GenericInput
|
|
335
|
+
id="jobTypeName"
|
|
336
|
+
type="text"
|
|
337
|
+
label="Request type title"
|
|
338
|
+
placeholder="Request type title"
|
|
339
|
+
value={jobTypeName}
|
|
340
|
+
onChange={this.onHandleChange}
|
|
341
|
+
isRequired
|
|
342
|
+
isValid={() => !_.isEmpty(jobTypeName)}
|
|
343
|
+
showError={() => showWarnings && _.isEmpty(jobTypeName)}
|
|
344
|
+
alwaysShowLabel
|
|
345
|
+
/>
|
|
346
|
+
<Components.GenericInput
|
|
347
|
+
id="jobTypeEmail"
|
|
348
|
+
type="text"
|
|
349
|
+
label="Email"
|
|
350
|
+
placeholder="Request email"
|
|
351
|
+
help="This is the email address that'll receive service requests of this type"
|
|
352
|
+
value={jobTypeEmail}
|
|
353
|
+
onChange={this.onHandleChange}
|
|
354
|
+
isRequired
|
|
355
|
+
isValid={this.validateEmail}
|
|
356
|
+
showError={() => showWarnings && !this.validateEmail()}
|
|
357
|
+
alwaysShowLabel
|
|
358
|
+
/>
|
|
359
|
+
<Components.GenericInput
|
|
360
|
+
id="jobTypeDescription"
|
|
361
|
+
type="text"
|
|
362
|
+
label="Description"
|
|
363
|
+
placeholder="Add a description. "
|
|
364
|
+
help="This description will be visible to the people to help them select the correct request type."
|
|
365
|
+
value={jobTypeDescription}
|
|
366
|
+
onChange={this.onHandleChange}
|
|
367
|
+
isRequired
|
|
368
|
+
isValid={() => !_.isEmpty(jobTypeDescription)}
|
|
369
|
+
showError={() => showWarnings && _.isEmpty(jobTypeDescription)}
|
|
370
|
+
alwaysShowLabel
|
|
371
|
+
/>
|
|
372
|
+
{values.forceCustomFields ? null : (
|
|
373
|
+
<Components.RadioButton
|
|
374
|
+
label="Do you want to create a custom form for this request type?"
|
|
375
|
+
isActive={hasCustomFields}
|
|
376
|
+
options={[
|
|
377
|
+
{
|
|
378
|
+
Label: 'Yes',
|
|
379
|
+
Value: true,
|
|
380
|
+
onChange: () => this.setState({ hasCustomFields: true }),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
Label: 'No',
|
|
384
|
+
Value: false,
|
|
385
|
+
onChange: () => this.setState({ hasCustomFields: false }),
|
|
386
|
+
},
|
|
387
|
+
]}
|
|
388
|
+
/>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
renderOptionalCheckBox(field, fieldIndex) {
|
|
395
|
+
return (
|
|
396
|
+
<Components.CheckBox
|
|
397
|
+
id={`fieldOptional${fieldIndex}`}
|
|
398
|
+
label="Set this field as optional"
|
|
399
|
+
isActive={!field.mandatory}
|
|
400
|
+
onChange={(e) => this.onFieldMandatoryChanged(fieldIndex, e)}
|
|
401
|
+
/>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
renderTitleCheckBox(field, fieldIndex) {
|
|
406
|
+
return (
|
|
407
|
+
<Components.CheckBox
|
|
408
|
+
id={`fieldTitle${fieldIndex}`}
|
|
409
|
+
label="Use this field as the title"
|
|
410
|
+
isActive={field.isTitle}
|
|
411
|
+
onChange={(e) => this.onFieldIsTitleChanged(fieldIndex, e)}
|
|
412
|
+
/>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
renderFieldText(field, fieldIndex) {
|
|
417
|
+
return (
|
|
418
|
+
<div className="fieldInner">
|
|
419
|
+
<Components.GenericInput
|
|
420
|
+
id={`fieldLabel${fieldIndex}`}
|
|
421
|
+
className={'textInput'}
|
|
422
|
+
placeholder={'Type your label here (required)'}
|
|
423
|
+
value={field.label}
|
|
424
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
425
|
+
/>
|
|
426
|
+
<Components.GenericInput
|
|
427
|
+
id={`fieldPlaceHolder${fieldIndex}`}
|
|
428
|
+
className={'placeHolderInput'}
|
|
429
|
+
placeholder={'Insert placeholder (optional)'}
|
|
430
|
+
value={field.placeHolder}
|
|
431
|
+
onChange={(e) => this.onFieldPlaceHolderChanged(fieldIndex, e)}
|
|
432
|
+
/>
|
|
433
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
434
|
+
{this.renderTitleCheckBox(field, fieldIndex)}
|
|
435
|
+
</div>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
renderFieldDate(field, fieldIndex) {
|
|
440
|
+
return (
|
|
441
|
+
<div className="fieldInner">
|
|
442
|
+
<Components.GenericInput
|
|
443
|
+
id={`fieldLabel${fieldIndex}`}
|
|
444
|
+
className={'textInput'}
|
|
445
|
+
placeholder={'Type your label here (required)'}
|
|
446
|
+
value={field.label}
|
|
447
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
448
|
+
/>
|
|
449
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
450
|
+
{this.renderTitleCheckBox(field, fieldIndex)}
|
|
451
|
+
</div>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
renderFieldTime(field, fieldIndex) {
|
|
456
|
+
return (
|
|
457
|
+
<div className="fieldInner">
|
|
458
|
+
<Components.GenericInput
|
|
459
|
+
id={`fieldLabel${fieldIndex}`}
|
|
460
|
+
className={'textInput'}
|
|
461
|
+
placeholder={'Type your label here (required)'}
|
|
462
|
+
value={field.label}
|
|
463
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
464
|
+
/>
|
|
465
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
466
|
+
{this.renderTitleCheckBox(field, fieldIndex)}
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
renderFieldImage(field, fieldIndex) {
|
|
472
|
+
return (
|
|
473
|
+
<div className="fieldInner">
|
|
474
|
+
<Components.GenericInput
|
|
475
|
+
id={`fieldLabel${fieldIndex}`}
|
|
476
|
+
className={'textInput'}
|
|
477
|
+
placeholder={'Type your label here (required)'}
|
|
478
|
+
value={field.label}
|
|
479
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
480
|
+
/>
|
|
481
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
renderFieldYesNo(field, fieldIndex) {
|
|
487
|
+
return (
|
|
488
|
+
<div className="fieldInner">
|
|
489
|
+
<Components.GenericInput
|
|
490
|
+
id={`fieldLabel${fieldIndex}`}
|
|
491
|
+
className={'textInput'}
|
|
492
|
+
placeholder={'Type your label here (required)'}
|
|
493
|
+
value={field.label}
|
|
494
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
495
|
+
/>
|
|
496
|
+
<Table className="optionsContainer">
|
|
497
|
+
<thead className="headings">
|
|
498
|
+
<tr>
|
|
499
|
+
<th className="icon" />
|
|
500
|
+
<th className="options">Options</th>
|
|
501
|
+
</tr>
|
|
502
|
+
</thead>
|
|
503
|
+
<tbody>
|
|
504
|
+
{['Yes', 'No'].map((value, optionIndex) => {
|
|
505
|
+
return (
|
|
506
|
+
<tr key={optionIndex} className="option">
|
|
507
|
+
<td>
|
|
508
|
+
<Components.RadioButton single disabled isActive={false} />
|
|
509
|
+
</td>
|
|
510
|
+
<td>
|
|
511
|
+
<Components.GenericInput id={`fieldOption${optionIndex}`} value={value} disabled />
|
|
512
|
+
</td>
|
|
513
|
+
</tr>
|
|
514
|
+
);
|
|
515
|
+
})}
|
|
516
|
+
</tbody>
|
|
517
|
+
</Table>
|
|
518
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
renderFieldMultiple(field, fieldIndex) {
|
|
524
|
+
const { customFields } = this.state;
|
|
525
|
+
return (
|
|
526
|
+
<div className="fieldInner">
|
|
527
|
+
<Components.GenericInput
|
|
528
|
+
id={`fieldLabel${fieldIndex}`}
|
|
529
|
+
className={'textInput'}
|
|
530
|
+
placeholder={'Type your label here (required)'}
|
|
531
|
+
value={field.label}
|
|
532
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
533
|
+
/>
|
|
534
|
+
<Table className="optionsContainer">
|
|
535
|
+
<thead className="headings">
|
|
536
|
+
<tr>
|
|
537
|
+
<th className="icon" />
|
|
538
|
+
<th className="options-wide">Options</th>
|
|
539
|
+
<th className="remove" />
|
|
540
|
+
</tr>
|
|
541
|
+
</thead>
|
|
542
|
+
<tbody>
|
|
543
|
+
{field.values &&
|
|
544
|
+
field.values.map((value, optionIndex) => {
|
|
545
|
+
return (
|
|
546
|
+
<tr key={optionIndex} className="option">
|
|
547
|
+
<td>
|
|
548
|
+
<Components.RadioButton single disabled isActive={false} />
|
|
549
|
+
</td>
|
|
550
|
+
<td>
|
|
551
|
+
<Components.GenericInput
|
|
552
|
+
id={`fieldOption${optionIndex}`}
|
|
553
|
+
placeholder={'Enter option'}
|
|
554
|
+
value={value}
|
|
555
|
+
onChange={(e) => this.onFieldOptionChanged(fieldIndex, optionIndex, e)}
|
|
556
|
+
/>
|
|
557
|
+
</td>
|
|
558
|
+
<td>
|
|
559
|
+
{customFields[fieldIndex].values.length > 1 ? (
|
|
560
|
+
<div onClick={() => this.onRemoveOption(fieldIndex, optionIndex)}>
|
|
561
|
+
<FontAwesome name="minus-circle" className="cornerCancelButton_icon" />
|
|
562
|
+
</div>
|
|
563
|
+
) : null}
|
|
564
|
+
</td>
|
|
565
|
+
</tr>
|
|
566
|
+
);
|
|
567
|
+
})}
|
|
568
|
+
</tbody>
|
|
569
|
+
</Table>
|
|
570
|
+
<div className="clearfix addoption optionAdd marginBottom-10" onClick={() => this.onAddNewOption(fieldIndex)}>
|
|
571
|
+
<Components.P60Icon className="addoption_plus" icon="add-circle" />
|
|
572
|
+
<div className="fillSpace">
|
|
573
|
+
<p className="addoption_text">Add Another Option</p>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
577
|
+
{this.renderTitleCheckBox(field, fieldIndex)}
|
|
578
|
+
</div>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
renderFieldCheckbox(field, fieldIndex) {
|
|
583
|
+
const { customFields } = this.state;
|
|
584
|
+
return (
|
|
585
|
+
<div className="fieldInner">
|
|
586
|
+
<Components.GenericInput
|
|
587
|
+
id={`fieldLabel${fieldIndex}`}
|
|
588
|
+
className={'textInput'}
|
|
589
|
+
placeholder={'Type your label here (required)'}
|
|
590
|
+
value={field.label}
|
|
591
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
592
|
+
/>
|
|
593
|
+
<Table className="optionsContainer">
|
|
594
|
+
<thead className="headings">
|
|
595
|
+
<tr>
|
|
596
|
+
<th className="icon" />
|
|
597
|
+
<th>Options</th>
|
|
598
|
+
<th className="remove" />
|
|
599
|
+
</tr>
|
|
600
|
+
</thead>
|
|
601
|
+
<tbody>
|
|
602
|
+
{field.values &&
|
|
603
|
+
field.values.map((value, optionIndex) => {
|
|
604
|
+
return (
|
|
605
|
+
<tr key={optionIndex} className="option">
|
|
606
|
+
<td>
|
|
607
|
+
<Components.RadioButton single disabled isActive={false} isSquare />
|
|
608
|
+
</td>
|
|
609
|
+
<td>
|
|
610
|
+
<Components.GenericInput
|
|
611
|
+
id={`fieldOption${optionIndex}`}
|
|
612
|
+
placeholder={'Enter option'}
|
|
613
|
+
value={value}
|
|
614
|
+
onChange={(e) => this.onFieldOptionChanged(fieldIndex, optionIndex, e)}
|
|
615
|
+
/>
|
|
616
|
+
</td>
|
|
617
|
+
<td>
|
|
618
|
+
{customFields[fieldIndex].values.length > 1 ? (
|
|
619
|
+
<div onClick={() => this.onRemoveOption(fieldIndex, optionIndex)}>
|
|
620
|
+
<FontAwesome name="minus-circle" className="cornerCancelButton_icon" />
|
|
621
|
+
</div>
|
|
622
|
+
) : null}
|
|
623
|
+
</td>
|
|
624
|
+
</tr>
|
|
625
|
+
);
|
|
626
|
+
})}
|
|
627
|
+
</tbody>
|
|
628
|
+
</Table>
|
|
629
|
+
<div className="clearfix addoption optionAdd marginBottom-10" onClick={() => this.onAddNewOption(fieldIndex)}>
|
|
630
|
+
<Components.P60Icon className="addoption_plus" icon="add-circle" />
|
|
631
|
+
<div className="fillSpace">
|
|
632
|
+
<p className="addoption_text">Add Another Option</p>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
{this.renderOptionalCheckBox(field, fieldIndex)}
|
|
636
|
+
{this.renderTitleCheckBox(field, fieldIndex)}
|
|
637
|
+
</div>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
renderFieldStaticTitle(field, fieldIndex) {
|
|
642
|
+
return (
|
|
643
|
+
<div className="fieldInner">
|
|
644
|
+
<Components.GenericInput
|
|
645
|
+
id={`fieldStatic${fieldIndex}`}
|
|
646
|
+
placeholder="Insert title here (required)"
|
|
647
|
+
value={field.label}
|
|
648
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
649
|
+
large
|
|
650
|
+
/>
|
|
651
|
+
</div>
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
renderFieldStaticText(field, fieldIndex) {
|
|
656
|
+
return (
|
|
657
|
+
<div className="fieldInner">
|
|
658
|
+
<Components.GenericInput
|
|
659
|
+
id={`fieldStatic${fieldIndex}`}
|
|
660
|
+
placeholder="Insert your paragraph text here (required)"
|
|
661
|
+
value={field.label}
|
|
662
|
+
onChange={(e) => this.onFieldLabelChanged(fieldIndex, e)}
|
|
663
|
+
type="textarea"
|
|
664
|
+
/>
|
|
665
|
+
</div>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
renderField(field, fieldIndex) {
|
|
670
|
+
const { customFields } = this.state;
|
|
671
|
+
|
|
672
|
+
const renderFieldContent = () => {
|
|
673
|
+
switch (field.type) {
|
|
674
|
+
case 'text':
|
|
675
|
+
case 'email':
|
|
676
|
+
case 'phone':
|
|
677
|
+
return this.renderFieldText(field, fieldIndex);
|
|
678
|
+
case 'date':
|
|
679
|
+
return this.renderFieldDate(field, fieldIndex);
|
|
680
|
+
case 'time':
|
|
681
|
+
return this.renderFieldTime(field, fieldIndex);
|
|
682
|
+
case 'image':
|
|
683
|
+
return this.renderFieldImage(field, fieldIndex);
|
|
684
|
+
case 'yn':
|
|
685
|
+
return this.renderFieldYesNo(field, fieldIndex);
|
|
686
|
+
case 'multichoice':
|
|
687
|
+
return this.renderFieldMultiple(field, fieldIndex);
|
|
688
|
+
case 'checkbox':
|
|
689
|
+
return this.renderFieldCheckbox(field, fieldIndex);
|
|
690
|
+
case 'staticTitle':
|
|
691
|
+
return this.renderFieldStaticTitle(field, fieldIndex);
|
|
692
|
+
case 'staticText':
|
|
693
|
+
return this.renderFieldStaticText(field, fieldIndex);
|
|
694
|
+
default:
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
return (
|
|
700
|
+
<div className="fieldContainer" key={fieldIndex}>
|
|
701
|
+
<div className="fieldNumberContainer">
|
|
702
|
+
<p className="fieldNumber">{fieldIndex + 1}</p>
|
|
703
|
+
</div>
|
|
704
|
+
<div className="field">
|
|
705
|
+
<div className="fieldHeader">
|
|
706
|
+
<div className="group">
|
|
707
|
+
<div className="line" />
|
|
708
|
+
<div className="fieldType">{this.getFieldTypeTitle(field.type)}</div>
|
|
709
|
+
</div>
|
|
710
|
+
<div className="group">
|
|
711
|
+
<Components.DropdownInput
|
|
712
|
+
id={`fieldType${fieldIndex}`}
|
|
713
|
+
placeholder="Type"
|
|
714
|
+
value="Change Field Type"
|
|
715
|
+
options={this.fieldTypes}
|
|
716
|
+
onSelect={(key) => this.onFieldTypeChanged(fieldIndex, key)}
|
|
717
|
+
/>
|
|
718
|
+
{customFields.length > 1 && (
|
|
719
|
+
<div className="delete" onClick={() => this.onRemoveField(fieldIndex)}>
|
|
720
|
+
Delete
|
|
721
|
+
</div>
|
|
722
|
+
)}
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
{renderFieldContent()}
|
|
726
|
+
</div>
|
|
727
|
+
{customFields.length > 1 && (
|
|
728
|
+
<div className="switchField">
|
|
729
|
+
{fieldIndex > 0 ? (
|
|
730
|
+
<div className="circle" onClick={() => this.onMoveFieldPrev(fieldIndex)}>
|
|
731
|
+
<FontAwesome name="angle-up" className="icon" />
|
|
732
|
+
</div>
|
|
733
|
+
) : null}
|
|
734
|
+
{fieldIndex < customFields.length - 1 ? (
|
|
735
|
+
<div className="circle" onClick={() => this.onMoveFieldNext(fieldIndex)}>
|
|
736
|
+
<FontAwesome name="angle-down" className="icon" />
|
|
737
|
+
</div>
|
|
738
|
+
) : null}
|
|
739
|
+
</div>
|
|
740
|
+
)}
|
|
741
|
+
</div>
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
renderCustomForm() {
|
|
746
|
+
const { success, hasCustomFields, customFields } = this.state;
|
|
747
|
+
if (success || !hasCustomFields) return null;
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
<div className="padding-60 paddingTop-8 paddingLeft-20">
|
|
751
|
+
<div className="fields">
|
|
752
|
+
{customFields.map((field, fieldIndex) => this.renderField(field, fieldIndex))}
|
|
753
|
+
<div className="clearfix addoption addField" onClick={() => this.onAddNewField()}>
|
|
754
|
+
<Components.P60Icon className="addoption_plus" icon="add-circle" />
|
|
755
|
+
<div className="fillSpace">
|
|
756
|
+
<p className="addoption_text">Add New Field</p>
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
renderWarnings() {
|
|
765
|
+
const { showWarnings, warnings } = this.state;
|
|
766
|
+
if (!showWarnings || !warnings || warnings.length === 0) return null;
|
|
767
|
+
|
|
768
|
+
return (
|
|
769
|
+
<div className="padding-40 paddingBottom-8 text-help">
|
|
770
|
+
To save the form
|
|
771
|
+
<ul style={{ padding: 0, paddingLeft: 16 }}>
|
|
772
|
+
{warnings.map((warn, index) => (
|
|
773
|
+
<li key={index}>{warn}</li>
|
|
774
|
+
))}
|
|
775
|
+
</ul>
|
|
776
|
+
</div>
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
renderSuccess() {
|
|
781
|
+
const { success, jobTypeId } = this.state;
|
|
782
|
+
if (!success) return null;
|
|
783
|
+
|
|
784
|
+
// TODO: Replace name with configuration
|
|
785
|
+
return (
|
|
786
|
+
<Components.SuccessPopup
|
|
787
|
+
text={`Request Type has been ${!jobTypeId ? 'added' : 'edited'}`}
|
|
788
|
+
buttons={[
|
|
789
|
+
{
|
|
790
|
+
type: 'outlined',
|
|
791
|
+
onClick: this.onBack,
|
|
792
|
+
text: 'Go to home',
|
|
793
|
+
},
|
|
794
|
+
]}
|
|
795
|
+
/>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
renderSubmit() {
|
|
800
|
+
if (this.state.submitting) {
|
|
801
|
+
return <Components.Button buttonType="secondary">Saving...</Components.Button>;
|
|
802
|
+
}
|
|
803
|
+
return (
|
|
804
|
+
<div>
|
|
805
|
+
<Components.Button inline buttonType="tertiary" onClick={this.onBack} isActive style={{ marginRight: 8 }}>
|
|
806
|
+
Cancel
|
|
807
|
+
</Components.Button>
|
|
808
|
+
<Components.Button inline buttonType="primary" onClick={this.onSave} isActive>
|
|
809
|
+
Save
|
|
810
|
+
</Components.Button>
|
|
811
|
+
</div>
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
render() {
|
|
816
|
+
const { success } = this.state;
|
|
817
|
+
|
|
818
|
+
return (
|
|
819
|
+
<Components.OverlayPage>
|
|
820
|
+
<Components.OverlayPageContents noBottomButtons={success}>
|
|
821
|
+
<Components.OverlayPageSection className="pageSectionWrapper--newPopup">
|
|
822
|
+
<div className="addForm">
|
|
823
|
+
{this.renderBaseForm()}
|
|
824
|
+
{this.renderWarnings()}
|
|
825
|
+
{this.renderCustomForm()}
|
|
826
|
+
</div>
|
|
827
|
+
{this.renderSuccess()}
|
|
828
|
+
</Components.OverlayPageSection>
|
|
829
|
+
</Components.OverlayPageContents>
|
|
830
|
+
<Components.OverlayPageBottomButtons>{this.renderSubmit()}</Components.OverlayPageBottomButtons>
|
|
831
|
+
</Components.OverlayPage>
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const mapStateToProps = (state) => {
|
|
837
|
+
const { auth } = state;
|
|
838
|
+
return { auth, strings: (state.strings && state.strings.config) || {} };
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
export default connect(mapStateToProps, { jobTypesUpdate })(withRouter(AddJobType));
|