@plusscommunities/pluss-maintenance-app-forms 7.0.20-auth.0 → 7.0.21

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 (97) hide show
  1. package/dist/module/actions/JobActions.js +4 -4
  2. package/dist/module/actions/JobActions.js.map +1 -1
  3. package/dist/module/actions/index.js +1 -1
  4. package/dist/module/actions/index.js.map +1 -1
  5. package/dist/module/actions/types.js +1 -1
  6. package/dist/module/actions/types.js.map +1 -1
  7. package/dist/module/apis/index.js +3 -3
  8. package/dist/module/apis/index.js.map +1 -1
  9. package/dist/module/apis/maintenanceActions.js +42 -34
  10. package/dist/module/apis/maintenanceActions.js.map +1 -1
  11. package/dist/module/apis/userActions.js +5 -5
  12. package/dist/module/apis/userActions.js.map +1 -1
  13. package/dist/module/components/FilterPopupMenu.js +49 -49
  14. package/dist/module/components/FilterPopupMenu.js.map +1 -1
  15. package/dist/module/components/MaintenanceList.js +38 -38
  16. package/dist/module/components/MaintenanceList.js.map +1 -1
  17. package/dist/module/components/MaintenanceListItem.js +62 -62
  18. package/dist/module/components/MaintenanceListItem.js.map +1 -1
  19. package/dist/module/components/MaintenanceWidgetItem.js +27 -27
  20. package/dist/module/components/MaintenanceWidgetItem.js.map +1 -1
  21. package/dist/module/components/PrioritySelectorPopup.js +15 -15
  22. package/dist/module/components/PrioritySelectorPopup.js.map +1 -1
  23. package/dist/module/components/StatusSelectorPopup.js +16 -16
  24. package/dist/module/components/StatusSelectorPopup.js.map +1 -1
  25. package/dist/module/components/WidgetLarge.js +2 -2
  26. package/dist/module/components/WidgetLarge.js.map +1 -1
  27. package/dist/module/components/WidgetSmall.js +19 -19
  28. package/dist/module/components/WidgetSmall.js.map +1 -1
  29. package/dist/module/core.config.js +1 -1
  30. package/dist/module/core.config.js.map +1 -1
  31. package/dist/module/feature.config.js +17 -17
  32. package/dist/module/feature.config.js.map +1 -1
  33. package/dist/module/helper.js +10 -10
  34. package/dist/module/helper.js.map +1 -1
  35. package/dist/module/index.js +11 -11
  36. package/dist/module/index.js.map +1 -1
  37. package/dist/module/reducers/JobsReducer.js +13 -13
  38. package/dist/module/reducers/JobsReducer.js.map +1 -1
  39. package/dist/module/screens/JobTypePicker.js +17 -17
  40. package/dist/module/screens/JobTypePicker.js.map +1 -1
  41. package/dist/module/screens/MaintenancePage.js +10 -10
  42. package/dist/module/screens/MaintenancePage.js.map +1 -1
  43. package/dist/module/screens/MaintenanceUserPicker.js +129 -22
  44. package/dist/module/screens/MaintenanceUserPicker.js.map +1 -1
  45. package/dist/module/screens/RequestDetail.js +252 -137
  46. package/dist/module/screens/RequestDetail.js.map +1 -1
  47. package/dist/module/screens/RequestNotes.js +59 -59
  48. package/dist/module/screens/RequestNotes.js.map +1 -1
  49. package/dist/module/screens/ServiceRequest.js +198 -186
  50. package/dist/module/screens/ServiceRequest.js.map +1 -1
  51. package/dist/module/values.config.a.js +31 -30
  52. package/dist/module/values.config.a.js.map +1 -1
  53. package/dist/module/values.config.default.js +35 -34
  54. package/dist/module/values.config.default.js.map +1 -1
  55. package/dist/module/values.config.enquiry.js +35 -34
  56. package/dist/module/values.config.enquiry.js.map +1 -1
  57. package/dist/module/values.config.feedback.js +35 -34
  58. package/dist/module/values.config.feedback.js.map +1 -1
  59. package/dist/module/values.config.food.js +35 -34
  60. package/dist/module/values.config.food.js.map +1 -1
  61. package/dist/module/values.config.forms.js +34 -34
  62. package/dist/module/values.config.forms.js.map +1 -1
  63. package/dist/module/values.config.js +34 -34
  64. package/dist/module/values.config.js.map +1 -1
  65. package/package.json +52 -56
  66. package/src/actions/JobActions.js +67 -60
  67. package/src/actions/index.js +1 -1
  68. package/src/actions/types.js +1 -2
  69. package/src/apis/index.js +3 -3
  70. package/src/apis/maintenanceActions.js +189 -172
  71. package/src/apis/userActions.js +17 -17
  72. package/src/components/FilterPopupMenu.js +313 -256
  73. package/src/components/MaintenanceList.js +396 -317
  74. package/src/components/MaintenanceListItem.js +347 -288
  75. package/src/components/MaintenanceWidgetItem.js +145 -124
  76. package/src/components/PrioritySelectorPopup.js +81 -68
  77. package/src/components/StatusSelectorPopup.js +81 -70
  78. package/src/components/WidgetLarge.js +5 -5
  79. package/src/components/WidgetSmall.js +153 -133
  80. package/src/core.config.js +27 -3
  81. package/src/feature.config.js +62 -62
  82. package/src/helper.js +58 -53
  83. package/src/index.js +22 -22
  84. package/src/reducers/JobsReducer.js +85 -66
  85. package/src/screens/JobTypePicker.js +115 -92
  86. package/src/screens/MaintenancePage.js +89 -80
  87. package/src/screens/MaintenanceUserPicker.js +262 -99
  88. package/src/screens/RequestDetail.js +1344 -1021
  89. package/src/screens/RequestNotes.js +946 -805
  90. package/src/screens/ServiceRequest.js +1773 -1544
  91. package/src/values.config.a.js +34 -33
  92. package/src/values.config.default.js +40 -39
  93. package/src/values.config.enquiry.js +40 -39
  94. package/src/values.config.feedback.js +40 -39
  95. package/src/values.config.food.js +40 -39
  96. package/src/values.config.forms.js +39 -39
  97. package/src/values.config.js +39 -39
@@ -1,817 +1,958 @@
1
- import React, { Component } from 'react';
1
+ import React, { Component } from "react";
2
2
  import {
3
- ScrollView,
4
- View,
5
- Text,
6
- TouchableOpacity,
7
- Modal,
8
- KeyboardAvoidingView,
9
- Platform,
10
- FlatList,
11
- Dimensions,
12
- ImageBackground,
13
- Keyboard,
14
- } from 'react-native';
15
- import { connect } from 'react-redux';
16
- import { Icon } from '@rneui/themed';
17
- import _ from 'lodash';
18
- import moment from 'moment';
19
- import { jobAdded } from '../actions';
20
- import { maintenanceActions } from '../apis';
21
- import { getBottomSpace } from 'react-native-iphone-x-helper';
22
- import { Services } from '../feature.config';
23
- import { Components, Colours, Helper } from '../core.config';
24
- import { values } from '../values.config';
25
-
26
- const SCREEN_WIDTH = Dimensions.get('window').width;
3
+ ScrollView,
4
+ View,
5
+ Text,
6
+ TouchableOpacity,
7
+ Modal,
8
+ KeyboardAvoidingView,
9
+ Platform,
10
+ FlatList,
11
+ Dimensions,
12
+ ImageBackground,
13
+ Keyboard,
14
+ } from "react-native";
15
+ import { connect } from "react-redux";
16
+ import { Icon } from "@rneui/themed";
17
+ import _ from "lodash";
18
+ import moment from "moment";
19
+ import { jobAdded } from "../actions";
20
+ import { maintenanceActions } from "../apis";
21
+ import { getBottomSpace } from "react-native-iphone-x-helper";
22
+ import { Services } from "../feature.config";
23
+ import { Components, Colours, Helper } from "../core.config";
24
+ import { values } from "../values.config";
25
+
26
+ const SCREEN_WIDTH = Dimensions.get("window").width;
27
27
  const PHOTO_SIZE = (SCREEN_WIDTH - 64) / 3;
28
28
 
29
29
  class RequestNotes extends Component {
30
- constructor(props) {
31
- super(props);
32
-
33
- this.state = {
34
- job: {},
35
- selectedPDF: null,
36
- noteAttachments: [],
37
- noteInput: '',
38
- addNoteOpen: false,
39
- noteImages: [{ add: true }],
40
- uploadingImage: false,
41
- galleryOpen: false,
42
- galleryImages: [],
43
- galleryIndex: 0,
44
- };
45
- this.checkThumb = null;
46
- }
47
-
48
- componentDidMount() {
49
- this.updateJobState();
50
- }
51
-
52
- componentDidUpdate(prevProps) {
53
- if (prevProps.jobs !== this.props.jobs) {
54
- this.updateJobState();
55
- }
56
- }
57
-
58
- componentWillUnmount() {
59
- clearInterval(this.checkThumb);
60
- }
61
-
62
- updateJobState() {
63
- const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || this.props.job;
64
- this.setState({ job });
65
- }
66
-
67
- onPressBack = () => {
68
- Services.navigation.goBack();
69
- };
70
-
71
- onOpenAttachment = a => {
72
- this.setState({
73
- selectedPDF: a,
74
- });
75
- };
76
-
77
- onCloseAttachment = () => {
78
- this.setState({
79
- selectedPDF: null,
80
- });
81
- };
82
-
83
- onPressDeleteNote = n => {
84
- this.setState({
85
- noteToDelete: n,
86
- });
87
- };
88
-
89
- onPressConfirmDelete = () => {
90
- maintenanceActions.deleteNote(this.props.job.id, this.state.noteToDelete.Id);
91
- const newNotes = _.filter(this.state.job.Notes, note => {
92
- return note.Id !== this.state.noteToDelete.Id;
93
- });
94
- const newJob = { ...this.props.job };
95
- newJob.Notes = newNotes;
96
- this.props.jobAdded(newJob);
97
- this.props.onChange && this.props.onChange();
98
- this.setState({
99
- job: newJob,
100
- noteToDelete: null,
101
- });
102
- };
103
-
104
- onPressCancelDelete = () => {
105
- this.setState({ noteToDelete: null });
106
- };
107
-
108
- isReadyToSaveNote = () => {
109
- const uploadingAttachments = _.some(this.state.noteAttachments, n => n.Uploading);
110
- const uploadingImages = _.some(this.state.noteImages, n => n.uploading);
111
- if (uploadingAttachments || uploadingImages) return false;
112
- return (
113
- !_.isEmpty(this.state.noteInput) ||
114
- !_.isEmpty(this.state.noteAttachments) ||
115
- !_.isEmpty(this.state.noteImages.filter(img => !img.add))
116
- );
117
- };
118
-
119
- openAddNote = () => {
120
- this.setState({
121
- addNoteOpen: true,
122
- editingNote: null,
123
- });
124
- };
125
-
126
- closeAddNote = () => {
127
- const newState = {
128
- addNoteOpen: false,
129
- editingNote: null,
130
- };
131
- if (!!this.state.editingNote) {
132
- newState.noteInput = '';
133
- newState.noteAttachments = [];
134
- newState.noteImages = [];
135
- }
136
- this.setState(newState);
137
- };
138
-
139
- openEditNote = n => {
140
- const noteImages = [...(n.Images || []).map(i => ({ url: i, thumbNailUrl: Helper.getThumb300(i) })), { add: true }];
141
- this.setState({
142
- noteAttachments: n.Attachments || [],
143
- noteImages,
144
- noteInput: n.Note || '',
145
- addNoteOpen: true,
146
- editingNote: n.Id,
147
- noteMenuOpen: null,
148
- });
149
- };
150
-
151
- confirmAddNote = async () => {
152
- if (!this.isReadyToSaveNote()) {
153
- return;
154
- }
155
-
156
- try {
157
- this.setState({ submittingNote: true });
158
- const attachments = this.state.noteAttachments
159
- .filter(a => !a.add)
160
- .map(a => {
161
- return {
162
- Title: a.Title,
163
- Source: a.Source,
164
- };
165
- });
166
- const images = this.state.noteImages.filter(img => !img.add).map(img => img.url);
167
- const res = await (this.state.editingNote
168
- ? maintenanceActions.editNote(this.props.job.id, this.state.editingNote, this.state.noteInput, attachments, images)
169
- : maintenanceActions.addNote(this.props.job.id, this.state.noteInput, attachments, images));
170
- this.setState(
171
- {
172
- job: res.data.job,
173
- submittingNote: false,
174
- addNoteOpen: false,
175
- noteInput: '',
176
- noteAttachments: [],
177
- noteImages: [{ add: true }],
178
- uploadingImage: false,
179
- editingNote: null,
180
- },
181
- () => {
182
- this.props.jobAdded(res.data.job);
183
- this.props.onChange && this.props.onChange();
184
- },
185
- );
186
- } catch (err) {
187
- console.log('error');
188
- console.log(err);
189
- this.setState({
190
- submittingNote: false,
191
- uploadingImage: false,
192
- });
193
- }
194
- };
195
-
196
- setImages = (noteImages, uploadingImage = false, callback = null) => {
197
- this.setState({ noteImages, uploadingImage }, callback);
198
- };
199
-
200
- getImages = () => {
201
- const { noteImages } = this.state;
202
- const imagesList = _.cloneDeep(noteImages);
203
- if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
204
- return [{ add: true }];
205
- }
206
- return imagesList;
207
- };
208
-
209
- getImageUrls = () => {
210
- const imagesList = this.getImages();
211
- return _.filter(imagesList, img => {
212
- return !img.uploading && !img.add;
213
- }).map(img => {
214
- return img.url;
215
- });
216
- };
217
-
218
- waitForThumbnails = () => {
219
- if (this.checkThumb) return;
220
-
221
- this.checkThumb = setInterval(async () => {
222
- const imagesList = this.getImages();
223
- const imagesUpdate = [];
224
- await Promise.all(
225
- imagesList.map(image => {
226
- return new Promise(async resolve => {
227
- const newImage = { ...image };
228
- imagesUpdate.push(newImage);
229
- if (newImage.url && !newImage.thumbNailExists) {
230
- newImage.uploading = false;
231
- newImage.allowRetry = false;
232
- newImage.thumbNailExists = await Helper.imageExists(newImage.thumbNailUrl);
233
- resolve(newImage.thumbNailExists);
234
- }
235
- resolve(true);
236
- });
237
- }),
238
- );
239
- const thumbnailsExist = imagesUpdate.every(image => !image.url || image.thumbNailExists);
240
- if (thumbnailsExist) {
241
- clearInterval(this.checkThumb);
242
- this.checkThumb = null;
243
- this.setImages(imagesUpdate);
244
- }
245
- }, 2000);
246
- };
247
-
248
- removeImage = index => {
249
- const imagesUpdate = this.getImages();
250
- imagesUpdate.splice(index, 1);
251
-
252
- this.setImages(imagesUpdate);
253
- };
254
-
255
- showUploadMenu = () => {
256
- Keyboard.dismiss();
257
- const { uploadingImage, submittingNote } = this.state;
258
- if (uploadingImage || submittingNote) return;
259
- this.imageUploader.showUploadMenu();
260
- };
261
-
262
- toggleFullscreenVideo = url => {
263
- if (typeof url !== 'string') url = '';
264
- this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
265
- };
266
-
267
- openGallery(images, index) {
268
- this.setState({ galleryOpen: true, galleryImages: images, galleryIndex: index });
269
- }
270
-
271
- closeGallery() {
272
- this.setState({ galleryOpen: false, galleryImages: [], galleryIndex: 0 });
273
- }
274
-
275
- onUploadStarted = (uploadUri, imageUri) => {
276
- const imagesUpdate = this.getImages();
277
- imagesUpdate.splice(imagesUpdate.length - 1, 0, {
278
- uploading: true,
279
- uploadProgress: '0%',
280
- uploadUri,
281
- imageUri,
282
- allowRetry: true,
283
- });
284
-
285
- this.setImages(imagesUpdate, true);
286
- };
287
-
288
- onUploadProgress = progress => {
289
- const imagesUpdate = this.getImages();
290
- imagesUpdate.map(img => {
291
- if (img.uploadUri === progress.uri) {
292
- img.uploadProgress = progress.percentage;
293
- img.uploading = true;
294
- img.allowRetry = true;
295
- }
296
- });
297
-
298
- this.setImages(imagesUpdate, true);
299
- };
300
-
301
- onUploadSuccess = async (uri, uploadUri) => {
302
- const imagesUpdate = this.getImages();
303
- imagesUpdate.map(img => {
304
- if (img.uploadUri === uploadUri && img.uploading) {
305
- img.url = uri.replace('/general/', '/general1400/');
306
- img.thumbNailExists = false;
307
- img.thumbNailUrl = Helper.getThumb300(img.url);
308
- img.allowRetry = true;
309
- }
310
- });
311
-
312
- this.setImages(imagesUpdate, false, () => this.waitForThumbnails());
313
- };
314
-
315
- onUploadFailed = uploadUri => {
316
- const imagesUpdate = this.getImages();
317
- imagesUpdate.map(img => {
318
- if (img.uploadUri === uploadUri) {
319
- img.uploading = true; // Requried for retry
320
- img.uploadProgress = '';
321
- img.allowRetry = true;
322
- }
323
- });
324
-
325
- this.setImages(imagesUpdate);
326
- };
327
-
328
- onLibrarySelected = uri => {
329
- const imagesUpdate = this.getImages();
330
- imagesUpdate.splice(imagesUpdate.length - 1, 0, {
331
- uploading: false,
332
- allowRetry: false,
333
- url: Helper.get1400(uri),
334
- thumbNailExists: true,
335
- thumbNailUrl: Helper.getThumb300(uri),
336
- });
337
-
338
- this.setImages(imagesUpdate);
339
- };
340
-
341
- renderAttachment(a, i) {
342
- return (
343
- <Components.Attachment
344
- onPress={() => {
345
- this.onOpenAttachment(a);
346
- }}
347
- key={i}
348
- title={a.Title}
349
- />
350
- );
351
- }
352
-
353
- renderPlayableImageUrl(images, index, containerStyle) {
354
- const url = images[index];
355
- const thumbUrl = Helper.getThumb300(url);
356
-
357
- if (Helper.isVideo(url)) {
358
- return (
359
- <ImageBackground style={[{ flex: 1 }, containerStyle]} source={{ uri: thumbUrl }}>
360
- <View style={styles.imagePlayContainer}>
361
- <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, url)}>
362
- <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
363
- </TouchableOpacity>
364
- </View>
365
- </ImageBackground>
366
- );
367
- }
368
-
369
- const imageUrl = Helper.get1400(url);
370
- return (
371
- <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, images, index || 0)}>
372
- <ImageBackground resizeMode="cover" style={styles.imageContainer} source={{ uri: imageUrl }} />
373
- </TouchableOpacity>
374
- );
375
- }
376
-
377
- renderImages(note) {
378
- if (!note) return null;
379
- if (!_.isNil(note.Images) && !_.isEmpty(note.Images)) {
380
- return (
381
- <ScrollView horizontal style={styles.sideBySideImages}>
382
- {note.Images.map((_p, index) => this.renderPlayableImageUrl(note.Images, index, styles.sideBySideImageContainer))}
383
- </ScrollView>
384
- );
385
- }
386
- return null;
387
- }
388
-
389
- renderImagePopup() {
390
- const { galleryOpen, galleryImages, galleryIndex } = this.state;
391
- console.log('renderimagepopup', { galleryOpen, galleryImages, galleryIndex });
392
- return (
393
- <Components.ImagePopup visible={galleryOpen} images={galleryImages} index={galleryIndex} onClose={this.closeGallery.bind(this)} />
394
- );
395
- }
396
-
397
- renderNote(n) {
398
- return (
399
- <View style={styles.noteContainer} key={n.Id}>
400
- <View style={styles.noteContainerTop}>
401
- <Components.ProfilePic Diameter={30} ProfilePic={n.User.profilePic} style={styles.profilePic} />
402
- <View style={styles.noteContainerTopRight}>
403
- {this.props.user.uid === n.User.id && (
404
- <TouchableOpacity
405
- onPress={() => {
406
- this.onPressDeleteNote(n);
407
- }}
408
- >
409
- <View style={[styles.noteContainerButtonContainer, { backgroundColor: this.props.colourBrandingMain }]}>
410
- <Icon name="trash" type="font-awesome" iconStyle={styles.noteContainerButtonIcon} />
411
- </View>
412
- </TouchableOpacity>
413
- )}
414
- {this.props.user.uid === n.User.id && (
415
- <TouchableOpacity
416
- onPress={() => {
417
- this.openEditNote(n);
418
- }}
419
- >
420
- <View style={[styles.noteContainerButtonContainer, { backgroundColor: this.props.colourBrandingMain }]}>
421
- <Icon name="pencil" type="font-awesome" iconStyle={styles.noteContainerButtonIcon} />
422
- </View>
423
- </TouchableOpacity>
424
- )}
425
- <View style={styles.noteContainerTopFill}>
426
- <Text style={styles.noteContainerName}>{n.User.displayName}</Text>
427
- </View>
428
- </View>
429
- </View>
430
- <Text style={styles.noteContainerText}>{n.Note}</Text>
431
- {(n.Attachments || []).map((a, i) => this.renderAttachment(a, i))}
432
- {this.renderImages(n)}
433
- <Text style={styles.noteTimestamp}>
434
- {moment
435
- .utc(n.Timestamp)
436
- .local()
437
- .format(Helper.TIMESTAMP_FORMAT)}
438
- </Text>
439
- </View>
440
- );
441
- }
442
-
443
- renderNotes() {
444
- return (this.state.job.Notes || []).map((n, i) => {
445
- return this.renderNote(n, i);
446
- });
447
- }
448
-
449
- renderPDF() {
450
- if (_.isEmpty(this.state.selectedPDF)) {
451
- return null;
452
- }
453
- return (
454
- <Components.PDFPopup
455
- source={this.state.selectedPDF.Source}
456
- onClose={this.onCloseAttachment}
457
- title={this.state.selectedPDF.Title}
458
- pdfCount={1}
459
- />
460
- );
461
- }
462
-
463
- renderNoteDeleteConfirm() {
464
- return (
465
- <Components.ConfirmPopup
466
- visible={!!this.state.noteToDelete}
467
- onConfirm={this.onPressConfirmDelete}
468
- onCancel={this.onPressCancelDelete}
469
- onClose={this.onPressCancelDelete}
470
- text="Are you sure you want to delete this note?"
471
- />
472
- );
473
- }
474
-
475
- renderFooterContent() {
476
- if (this.state.submittingNote) {
477
- return <Components.Spinner />;
478
- }
479
- return (
480
- <Components.InlineButton color={this.props.colourBrandingMain} onPress={this.confirmAddNote} fillTouchable large>
481
- Save
482
- </Components.InlineButton>
483
- );
484
- }
485
-
486
- renderUploadMenu() {
487
- return (
488
- <Components.ImageUploader
489
- ref={ref => (this.imageUploader = ref)}
490
- onUploadStarted={this.onUploadStarted}
491
- onUploadSuccess={this.onUploadSuccess}
492
- onUploadFailed={this.onUploadFailed}
493
- onUploadProgress={this.onUploadProgress}
494
- onLibrarySelected={this.onLibrarySelected}
495
- size={{ width: 1400 }}
496
- quality={0.8}
497
- fileName={'serviceNoteImage'}
498
- popupTitle={'Upload Image'}
499
- userId={this.props.user.uid}
500
- allowsEditing={false}
501
- multiple
502
- hideLibrary
503
- />
504
- );
505
- }
506
-
507
- renderImage(item, index) {
508
- const isVideoUrl = Helper.isVideo(item.url);
509
- const imagesList = this.getImages();
510
-
511
- if (item.add) {
512
- return (
513
- <TouchableOpacity activeOpacity={0.8} onPress={() => this.showUploadMenu()}>
514
- <View
515
- style={[
516
- styles.imageContainer,
517
- imagesList.length > 1 && styles.imageContainerNotEmpty,
518
- index % 3 === 0 && { marginLeft: 0 },
519
- index > 2 && { marginTop: 8 },
520
- ]}
521
- >
522
- <View style={styles.imageCircle}>
523
- <Icon name="camera" type="font-awesome" iconStyle={styles.addImageIcon} />
524
- </View>
525
- </View>
526
- </TouchableOpacity>
527
- );
528
- }
529
- return (
530
- <View
531
- style={[
532
- styles.imageContainer,
533
- imagesList.length > 1 && styles.imageContainerNotEmpty,
534
- index % 3 === 0 && { marginLeft: 0 },
535
- index > 2 && { marginTop: 8 },
536
- ]}
537
- >
538
- {item.uploading ? (
539
- <Components.ImageUploadProgress uploader={this.imageUploader} image={item} color={this.props.colourBrandingMain} />
540
- ) : (
541
- <ImageBackground
542
- style={styles.imageBackground}
543
- source={Helper.getImageSource(item.thumbNailExists ? item.thumbNailUrl : item.url || item)}
544
- >
545
- {isVideoUrl && (
546
- <View style={styles.imagePlayContainer}>
547
- <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, item.url)}>
548
- <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
549
- </TouchableOpacity>
550
- </View>
551
- )}
552
- <TouchableOpacity style={styles.removeImage} onPress={() => this.removeImage(index)}>
553
- <Icon name="remove" type="font-awesome" iconStyle={styles.imageControlIcon} style={styles.removeImage} />
554
- </TouchableOpacity>
555
- </ImageBackground>
556
- )}
557
- </View>
558
- );
559
- }
560
-
561
- renderImageList() {
562
- const imagesList = this.getImages();
563
-
564
- return (
565
- <View style={styles.imageListContainer}>
566
- <FlatList
567
- keyboardShouldPersistTaps="always"
568
- enableEmptySections
569
- data={imagesList}
570
- renderItem={({ item, index }) => this.renderImage(item, index)}
571
- keyExtractor={(item, index) => index}
572
- numColumns={3}
573
- scrollEnabled={false}
574
- />
575
- </View>
576
- );
577
- }
578
-
579
- renderVideoPlayerPopup() {
580
- const { showFullscreenVideo, currentVideoUrl } = this.state;
581
- if (!currentVideoUrl) return;
582
-
583
- return (
584
- <Components.VideoPopup
585
- uri={currentVideoUrl}
586
- visible={showFullscreenVideo}
587
- showFullscreenButton={false}
588
- onClose={this.toggleFullscreenVideo}
589
- />
590
- );
591
- }
592
-
593
- renderAdd() {
594
- return (
595
- <Modal animationType="slide" visible={this.state.addNoteOpen}>
596
- <KeyboardAvoidingView style={styles.container} behavior={'padding'}>
597
- {this.renderUploadMenu()}
598
- <Components.Header leftText="Cancel" onPressLeft={this.closeAddNote} text="" />
599
- <ScrollView style={styles.scrollContainer} contentContainerStyle={styles.innerContainer}>
600
- <Components.TextStyle type="pageHeading">Add Staff Note</Components.TextStyle>
601
- <Components.GenericInputSection
602
- label="Content"
603
- placeholder="Insert your notes here..."
604
- value={this.state.noteInput}
605
- onChangeText={noteInput => this.setState({ noteInput })}
606
- isValid={() => {
607
- return !_.isEmpty(this.state.noteInput);
608
- }}
609
- autoCapitalize="sentences"
610
- minHeight={135}
611
- autoCorrect
612
- multiline
613
- autoGrow
614
- required
615
- squaredCorners
616
- sectionStyle={styles.inputSection}
617
- />
618
- <Components.GenericInputSection label="Attachments" sectionStyle={styles.inputSection}>
619
- {this.renderImageList()}
620
- {this.state.editingNote ? (
621
- (this.state.noteAttachments || []).map((a, i) => this.renderAttachment(a, i))
622
- ) : (
623
- <Components.TextStyle type="body" style={styles.attachmentInfo}>
624
- PDFs can only be attached to notes from the Community Manager.
625
- </Components.TextStyle>
626
- )}
627
- </Components.GenericInputSection>
628
- </ScrollView>
629
- <View style={styles.popupFooter}>{this.renderFooterContent()}</View>
630
- {this.renderVideoPlayerPopup()}
631
- </KeyboardAvoidingView>
632
- </Modal>
633
- );
634
- }
635
-
636
- render() {
637
- return (
638
- <View style={styles.container}>
639
- {this.renderPDF()}
640
- {this.renderNoteDeleteConfirm()}
641
- {this.renderAdd()}
642
- <Components.Header leftIcon="angle-left" onPressLeft={this.onPressBack} text="Staff Notes" />
643
- <ScrollView style={styles.scrollContainer} contentContainerStyle={styles.innerContainer}>
644
- {this.renderNotes()}
645
- </ScrollView>
646
- <Components.AddButton onPress={this.openAddNote} />
647
- {this.renderImagePopup()}
648
- </View>
649
- );
650
- }
30
+ constructor(props) {
31
+ super(props);
32
+
33
+ this.state = {
34
+ job: {},
35
+ selectedPDF: null,
36
+ noteAttachments: [],
37
+ noteInput: "",
38
+ addNoteOpen: false,
39
+ noteImages: [{ add: true }],
40
+ uploadingImage: false,
41
+ galleryOpen: false,
42
+ galleryImages: [],
43
+ galleryIndex: 0,
44
+ };
45
+ this.checkThumb = null;
46
+ }
47
+
48
+ componentDidMount() {
49
+ this.updateJobState();
50
+ }
51
+
52
+ componentDidUpdate(prevProps) {
53
+ if (prevProps.jobs !== this.props.jobs) {
54
+ this.updateJobState();
55
+ }
56
+ }
57
+
58
+ componentWillUnmount() {
59
+ clearInterval(this.checkThumb);
60
+ }
61
+
62
+ updateJobState() {
63
+ const job =
64
+ _.find(this.props.jobs, (j) => j.id === this.props.job.id) ||
65
+ this.props.job;
66
+ this.setState({ job });
67
+ }
68
+
69
+ onPressBack = () => {
70
+ Services.navigation.goBack();
71
+ };
72
+
73
+ onOpenAttachment = (a) => {
74
+ this.setState({
75
+ selectedPDF: a,
76
+ });
77
+ };
78
+
79
+ onCloseAttachment = () => {
80
+ this.setState({
81
+ selectedPDF: null,
82
+ });
83
+ };
84
+
85
+ onPressDeleteNote = (n) => {
86
+ this.setState({
87
+ noteToDelete: n,
88
+ });
89
+ };
90
+
91
+ onPressConfirmDelete = () => {
92
+ maintenanceActions.deleteNote(
93
+ this.props.job.id,
94
+ this.state.noteToDelete.Id,
95
+ );
96
+ const newNotes = _.filter(this.state.job.Notes, (note) => {
97
+ return note.Id !== this.state.noteToDelete.Id;
98
+ });
99
+ const newJob = { ...this.props.job };
100
+ newJob.Notes = newNotes;
101
+ this.props.jobAdded(newJob);
102
+ this.props.onChange && this.props.onChange();
103
+ this.setState({
104
+ job: newJob,
105
+ noteToDelete: null,
106
+ });
107
+ };
108
+
109
+ onPressCancelDelete = () => {
110
+ this.setState({ noteToDelete: null });
111
+ };
112
+
113
+ isReadyToSaveNote = () => {
114
+ const uploadingAttachments = _.some(
115
+ this.state.noteAttachments,
116
+ (n) => n.Uploading,
117
+ );
118
+ const uploadingImages = _.some(this.state.noteImages, (n) => n.uploading);
119
+ if (uploadingAttachments || uploadingImages) return false;
120
+ return (
121
+ !_.isEmpty(this.state.noteInput) ||
122
+ !_.isEmpty(this.state.noteAttachments) ||
123
+ !_.isEmpty(this.state.noteImages.filter((img) => !img.add))
124
+ );
125
+ };
126
+
127
+ openAddNote = () => {
128
+ this.setState({
129
+ addNoteOpen: true,
130
+ editingNote: null,
131
+ });
132
+ };
133
+
134
+ closeAddNote = () => {
135
+ const newState = {
136
+ addNoteOpen: false,
137
+ editingNote: null,
138
+ };
139
+ if (!!this.state.editingNote) {
140
+ newState.noteInput = "";
141
+ newState.noteAttachments = [];
142
+ newState.noteImages = [];
143
+ }
144
+ this.setState(newState);
145
+ };
146
+
147
+ openEditNote = (n) => {
148
+ const noteImages = [
149
+ ...(n.Images || []).map((i) => ({
150
+ url: i,
151
+ thumbNailUrl: Helper.getThumb300(i),
152
+ })),
153
+ { add: true },
154
+ ];
155
+ this.setState({
156
+ noteAttachments: n.Attachments || [],
157
+ noteImages,
158
+ noteInput: n.Note || "",
159
+ addNoteOpen: true,
160
+ editingNote: n.Id,
161
+ noteMenuOpen: null,
162
+ });
163
+ };
164
+
165
+ confirmAddNote = async () => {
166
+ if (!this.isReadyToSaveNote()) {
167
+ return;
168
+ }
169
+
170
+ try {
171
+ this.setState({ submittingNote: true });
172
+ const attachments = this.state.noteAttachments
173
+ .filter((a) => !a.add)
174
+ .map((a) => {
175
+ return {
176
+ Title: a.Title,
177
+ Source: a.Source,
178
+ };
179
+ });
180
+ const images = this.state.noteImages
181
+ .filter((img) => !img.add)
182
+ .map((img) => img.url);
183
+ const res = await (this.state.editingNote
184
+ ? maintenanceActions.editNote(
185
+ this.props.job.id,
186
+ this.state.editingNote,
187
+ this.state.noteInput,
188
+ attachments,
189
+ images,
190
+ )
191
+ : maintenanceActions.addNote(
192
+ this.props.job.id,
193
+ this.state.noteInput,
194
+ attachments,
195
+ images,
196
+ ));
197
+ this.setState(
198
+ {
199
+ job: res.data.job,
200
+ submittingNote: false,
201
+ addNoteOpen: false,
202
+ noteInput: "",
203
+ noteAttachments: [],
204
+ noteImages: [{ add: true }],
205
+ uploadingImage: false,
206
+ editingNote: null,
207
+ },
208
+ () => {
209
+ this.props.jobAdded(res.data.job);
210
+ this.props.onChange && this.props.onChange();
211
+ },
212
+ );
213
+ } catch (err) {
214
+ console.log("error");
215
+ console.log(err);
216
+ this.setState({
217
+ submittingNote: false,
218
+ uploadingImage: false,
219
+ });
220
+ }
221
+ };
222
+
223
+ setImages = (noteImages, uploadingImage = false, callback = null) => {
224
+ this.setState({ noteImages, uploadingImage }, callback);
225
+ };
226
+
227
+ getImages = () => {
228
+ const { noteImages } = this.state;
229
+ const imagesList = _.cloneDeep(noteImages);
230
+ if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
231
+ return [{ add: true }];
232
+ }
233
+ return imagesList;
234
+ };
235
+
236
+ getImageUrls = () => {
237
+ const imagesList = this.getImages();
238
+ return _.filter(imagesList, (img) => {
239
+ return !img.uploading && !img.add;
240
+ }).map((img) => {
241
+ return img.url;
242
+ });
243
+ };
244
+
245
+ waitForThumbnails = () => {
246
+ if (this.checkThumb) return;
247
+
248
+ this.checkThumb = setInterval(async () => {
249
+ const imagesList = this.getImages();
250
+ const imagesUpdate = [];
251
+ await Promise.all(
252
+ imagesList.map((image) => {
253
+ return new Promise(async (resolve) => {
254
+ const newImage = { ...image };
255
+ imagesUpdate.push(newImage);
256
+ if (newImage.url && !newImage.thumbNailExists) {
257
+ newImage.uploading = false;
258
+ newImage.allowRetry = false;
259
+ newImage.thumbNailExists = await Helper.imageExists(
260
+ newImage.thumbNailUrl,
261
+ );
262
+ resolve(newImage.thumbNailExists);
263
+ }
264
+ resolve(true);
265
+ });
266
+ }),
267
+ );
268
+ const thumbnailsExist = imagesUpdate.every(
269
+ (image) => !image.url || image.thumbNailExists,
270
+ );
271
+ if (thumbnailsExist) {
272
+ clearInterval(this.checkThumb);
273
+ this.checkThumb = null;
274
+ this.setImages(imagesUpdate);
275
+ }
276
+ }, 2000);
277
+ };
278
+
279
+ removeImage = (index) => {
280
+ const imagesUpdate = this.getImages();
281
+ imagesUpdate.splice(index, 1);
282
+
283
+ this.setImages(imagesUpdate);
284
+ };
285
+
286
+ showUploadMenu = () => {
287
+ Keyboard.dismiss();
288
+ const { uploadingImage, submittingNote } = this.state;
289
+ if (uploadingImage || submittingNote) return;
290
+ this.imageUploader.showUploadMenu();
291
+ };
292
+
293
+ toggleFullscreenVideo = (url) => {
294
+ if (typeof url !== "string") url = "";
295
+ this.setState({
296
+ showFullscreenVideo: url.length > 0,
297
+ currentVideoUrl: url,
298
+ });
299
+ };
300
+
301
+ openGallery(images, index) {
302
+ this.setState({
303
+ galleryOpen: true,
304
+ galleryImages: images,
305
+ galleryIndex: index,
306
+ });
307
+ }
308
+
309
+ closeGallery() {
310
+ this.setState({ galleryOpen: false, galleryImages: [], galleryIndex: 0 });
311
+ }
312
+
313
+ onUploadStarted = (uploadUri, imageUri) => {
314
+ const imagesUpdate = this.getImages();
315
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
316
+ uploading: true,
317
+ uploadProgress: "0%",
318
+ uploadUri,
319
+ imageUri,
320
+ allowRetry: true,
321
+ });
322
+
323
+ this.setImages(imagesUpdate, true);
324
+ };
325
+
326
+ onUploadProgress = (progress) => {
327
+ const imagesUpdate = this.getImages();
328
+ imagesUpdate.map((img) => {
329
+ if (img.uploadUri === progress.uri) {
330
+ img.uploadProgress = progress.percentage;
331
+ img.uploading = true;
332
+ img.allowRetry = true;
333
+ }
334
+ });
335
+
336
+ this.setImages(imagesUpdate, true);
337
+ };
338
+
339
+ onUploadSuccess = async (uri, uploadUri) => {
340
+ const imagesUpdate = this.getImages();
341
+ imagesUpdate.map((img) => {
342
+ if (img.uploadUri === uploadUri && img.uploading) {
343
+ img.url = uri.replace("/general/", "/general1400/");
344
+ img.thumbNailExists = false;
345
+ img.thumbNailUrl = Helper.getThumb300(img.url);
346
+ img.allowRetry = true;
347
+ }
348
+ });
349
+
350
+ this.setImages(imagesUpdate, false, () => this.waitForThumbnails());
351
+ };
352
+
353
+ onUploadFailed = (uploadUri) => {
354
+ const imagesUpdate = this.getImages();
355
+ imagesUpdate.map((img) => {
356
+ if (img.uploadUri === uploadUri) {
357
+ img.uploading = true; // Requried for retry
358
+ img.uploadProgress = "";
359
+ img.allowRetry = true;
360
+ }
361
+ });
362
+
363
+ this.setImages(imagesUpdate);
364
+ };
365
+
366
+ onLibrarySelected = (uri) => {
367
+ const imagesUpdate = this.getImages();
368
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
369
+ uploading: false,
370
+ allowRetry: false,
371
+ url: Helper.get1400(uri),
372
+ thumbNailExists: true,
373
+ thumbNailUrl: Helper.getThumb300(uri),
374
+ });
375
+
376
+ this.setImages(imagesUpdate);
377
+ };
378
+
379
+ renderAttachment(a, i) {
380
+ return (
381
+ <Components.Attachment
382
+ onPress={() => {
383
+ this.onOpenAttachment(a);
384
+ }}
385
+ key={i}
386
+ title={a.Title}
387
+ />
388
+ );
389
+ }
390
+
391
+ renderPlayableImageUrl(images, index, containerStyle) {
392
+ const url = images[index];
393
+ const thumbUrl = Helper.getThumb300(url);
394
+
395
+ if (Helper.isVideo(url)) {
396
+ return (
397
+ <ImageBackground
398
+ style={[{ flex: 1 }, containerStyle]}
399
+ source={{ uri: thumbUrl }}
400
+ >
401
+ <View style={styles.imagePlayContainer}>
402
+ <TouchableOpacity
403
+ onPress={this.toggleFullscreenVideo.bind(this, url)}
404
+ >
405
+ <Icon
406
+ name="play"
407
+ type="font-awesome"
408
+ iconStyle={styles.imageControlIcon}
409
+ />
410
+ </TouchableOpacity>
411
+ </View>
412
+ </ImageBackground>
413
+ );
414
+ }
415
+
416
+ const imageUrl = Helper.get1400(url);
417
+ return (
418
+ <TouchableOpacity
419
+ style={containerStyle}
420
+ onPress={this.openGallery.bind(this, images, index || 0)}
421
+ >
422
+ <ImageBackground
423
+ resizeMode="cover"
424
+ style={styles.imageContainer}
425
+ source={{ uri: imageUrl }}
426
+ />
427
+ </TouchableOpacity>
428
+ );
429
+ }
430
+
431
+ renderImages(note) {
432
+ if (!note) return null;
433
+ if (!_.isNil(note.Images) && !_.isEmpty(note.Images)) {
434
+ return (
435
+ <ScrollView horizontal style={styles.sideBySideImages}>
436
+ {note.Images.map((_p, index) =>
437
+ this.renderPlayableImageUrl(
438
+ note.Images,
439
+ index,
440
+ styles.sideBySideImageContainer,
441
+ ),
442
+ )}
443
+ </ScrollView>
444
+ );
445
+ }
446
+ return null;
447
+ }
448
+
449
+ renderImagePopup() {
450
+ const { galleryOpen, galleryImages, galleryIndex } = this.state;
451
+ console.log("renderimagepopup", {
452
+ galleryOpen,
453
+ galleryImages,
454
+ galleryIndex,
455
+ });
456
+ return (
457
+ <Components.ImagePopup
458
+ visible={galleryOpen}
459
+ images={galleryImages}
460
+ index={galleryIndex}
461
+ onClose={this.closeGallery.bind(this)}
462
+ />
463
+ );
464
+ }
465
+
466
+ renderNote(n) {
467
+ return (
468
+ <View style={styles.noteContainer} key={n.Id}>
469
+ <View style={styles.noteContainerTop}>
470
+ <Components.ProfilePic
471
+ Diameter={30}
472
+ ProfilePic={n.User.profilePic}
473
+ style={styles.profilePic}
474
+ />
475
+ <View style={styles.noteContainerTopRight}>
476
+ {this.props.user.uid === n.User.id && (
477
+ <TouchableOpacity
478
+ onPress={() => {
479
+ this.onPressDeleteNote(n);
480
+ }}
481
+ >
482
+ <View
483
+ style={[
484
+ styles.noteContainerButtonContainer,
485
+ { backgroundColor: this.props.colourBrandingMain },
486
+ ]}
487
+ >
488
+ <Icon
489
+ name="trash"
490
+ type="font-awesome"
491
+ iconStyle={styles.noteContainerButtonIcon}
492
+ />
493
+ </View>
494
+ </TouchableOpacity>
495
+ )}
496
+ {this.props.user.uid === n.User.id && (
497
+ <TouchableOpacity
498
+ onPress={() => {
499
+ this.openEditNote(n);
500
+ }}
501
+ >
502
+ <View
503
+ style={[
504
+ styles.noteContainerButtonContainer,
505
+ { backgroundColor: this.props.colourBrandingMain },
506
+ ]}
507
+ >
508
+ <Icon
509
+ name="pencil"
510
+ type="font-awesome"
511
+ iconStyle={styles.noteContainerButtonIcon}
512
+ />
513
+ </View>
514
+ </TouchableOpacity>
515
+ )}
516
+ <View style={styles.noteContainerTopFill}>
517
+ <Text style={styles.noteContainerName}>{n.User.displayName}</Text>
518
+ </View>
519
+ </View>
520
+ </View>
521
+ <Text style={styles.noteContainerText}>{n.Note}</Text>
522
+ {(n.Attachments || []).map((a, i) => this.renderAttachment(a, i))}
523
+ {this.renderImages(n)}
524
+ <Text style={styles.noteTimestamp}>
525
+ {moment.utc(n.Timestamp).local().format(Helper.TIMESTAMP_FORMAT)}
526
+ </Text>
527
+ </View>
528
+ );
529
+ }
530
+
531
+ renderNotes() {
532
+ return (this.state.job.Notes || []).map((n, i) => {
533
+ return this.renderNote(n, i);
534
+ });
535
+ }
536
+
537
+ renderPDF() {
538
+ if (_.isEmpty(this.state.selectedPDF)) {
539
+ return null;
540
+ }
541
+ return (
542
+ <Components.PDFPopup
543
+ source={this.state.selectedPDF.Source}
544
+ onClose={this.onCloseAttachment}
545
+ title={this.state.selectedPDF.Title}
546
+ pdfCount={1}
547
+ />
548
+ );
549
+ }
550
+
551
+ renderNoteDeleteConfirm() {
552
+ return (
553
+ <Components.ConfirmPopup
554
+ visible={!!this.state.noteToDelete}
555
+ onConfirm={this.onPressConfirmDelete}
556
+ onCancel={this.onPressCancelDelete}
557
+ onClose={this.onPressCancelDelete}
558
+ text="Are you sure you want to delete this note?"
559
+ />
560
+ );
561
+ }
562
+
563
+ renderFooterContent() {
564
+ if (this.state.submittingNote) {
565
+ return <Components.Spinner />;
566
+ }
567
+ return (
568
+ <Components.InlineButton
569
+ color={this.props.colourBrandingMain}
570
+ onPress={this.confirmAddNote}
571
+ fillTouchable
572
+ large
573
+ >
574
+ Save
575
+ </Components.InlineButton>
576
+ );
577
+ }
578
+
579
+ renderUploadMenu() {
580
+ return (
581
+ <Components.ImageUploader
582
+ ref={(ref) => (this.imageUploader = ref)}
583
+ onUploadStarted={this.onUploadStarted}
584
+ onUploadSuccess={this.onUploadSuccess}
585
+ onUploadFailed={this.onUploadFailed}
586
+ onUploadProgress={this.onUploadProgress}
587
+ onLibrarySelected={this.onLibrarySelected}
588
+ size={{ width: 1400 }}
589
+ quality={0.8}
590
+ fileName={"serviceNoteImage"}
591
+ popupTitle={"Upload Image"}
592
+ userId={this.props.user.uid}
593
+ allowsEditing={false}
594
+ multiple
595
+ hideLibrary
596
+ />
597
+ );
598
+ }
599
+
600
+ renderImage(item, index) {
601
+ const isVideoUrl = Helper.isVideo(item.url);
602
+ const imagesList = this.getImages();
603
+
604
+ if (item.add) {
605
+ return (
606
+ <TouchableOpacity
607
+ activeOpacity={0.8}
608
+ onPress={() => this.showUploadMenu()}
609
+ >
610
+ <View
611
+ style={[
612
+ styles.imageContainer,
613
+ imagesList.length > 1 && styles.imageContainerNotEmpty,
614
+ index % 3 === 0 && { marginLeft: 0 },
615
+ index > 2 && { marginTop: 8 },
616
+ ]}
617
+ >
618
+ <View style={styles.imageCircle}>
619
+ <Icon
620
+ name="camera"
621
+ type="font-awesome"
622
+ iconStyle={styles.addImageIcon}
623
+ />
624
+ </View>
625
+ </View>
626
+ </TouchableOpacity>
627
+ );
628
+ }
629
+ return (
630
+ <View
631
+ style={[
632
+ styles.imageContainer,
633
+ imagesList.length > 1 && styles.imageContainerNotEmpty,
634
+ index % 3 === 0 && { marginLeft: 0 },
635
+ index > 2 && { marginTop: 8 },
636
+ ]}
637
+ >
638
+ {item.uploading ? (
639
+ <Components.ImageUploadProgress
640
+ uploader={this.imageUploader}
641
+ image={item}
642
+ color={this.props.colourBrandingMain}
643
+ />
644
+ ) : (
645
+ <ImageBackground
646
+ style={styles.imageBackground}
647
+ source={Helper.getImageSource(
648
+ item.thumbNailExists ? item.thumbNailUrl : item.url || item,
649
+ )}
650
+ >
651
+ {isVideoUrl && (
652
+ <View style={styles.imagePlayContainer}>
653
+ <TouchableOpacity
654
+ onPress={this.toggleFullscreenVideo.bind(this, item.url)}
655
+ >
656
+ <Icon
657
+ name="play"
658
+ type="font-awesome"
659
+ iconStyle={styles.imageControlIcon}
660
+ />
661
+ </TouchableOpacity>
662
+ </View>
663
+ )}
664
+ <TouchableOpacity
665
+ style={styles.removeImage}
666
+ onPress={() => this.removeImage(index)}
667
+ >
668
+ <Icon
669
+ name="remove"
670
+ type="font-awesome"
671
+ iconStyle={styles.imageControlIcon}
672
+ style={styles.removeImage}
673
+ />
674
+ </TouchableOpacity>
675
+ </ImageBackground>
676
+ )}
677
+ </View>
678
+ );
679
+ }
680
+
681
+ renderImageList() {
682
+ const imagesList = this.getImages();
683
+
684
+ return (
685
+ <View style={styles.imageListContainer}>
686
+ <FlatList
687
+ keyboardShouldPersistTaps="always"
688
+ enableEmptySections
689
+ data={imagesList}
690
+ renderItem={({ item, index }) => this.renderImage(item, index)}
691
+ keyExtractor={(item, index) => index}
692
+ numColumns={3}
693
+ scrollEnabled={false}
694
+ />
695
+ </View>
696
+ );
697
+ }
698
+
699
+ renderVideoPlayerPopup() {
700
+ const { showFullscreenVideo, currentVideoUrl } = this.state;
701
+ if (!currentVideoUrl) return;
702
+
703
+ return (
704
+ <Components.VideoPopup
705
+ uri={currentVideoUrl}
706
+ visible={showFullscreenVideo}
707
+ showFullscreenButton={false}
708
+ onClose={this.toggleFullscreenVideo}
709
+ />
710
+ );
711
+ }
712
+
713
+ renderAdd() {
714
+ return (
715
+ <Modal animationType="slide" visible={this.state.addNoteOpen}>
716
+ <KeyboardAvoidingView style={styles.container} behavior={"padding"}>
717
+ {this.renderUploadMenu()}
718
+ <Components.Header
719
+ leftText="Cancel"
720
+ onPressLeft={this.closeAddNote}
721
+ text=""
722
+ />
723
+ <ScrollView
724
+ style={styles.scrollContainer}
725
+ contentContainerStyle={styles.innerContainer}
726
+ >
727
+ <Components.TextStyle type="pageHeading">
728
+ Add Staff Note
729
+ </Components.TextStyle>
730
+ <Components.GenericInputSection
731
+ label="Content"
732
+ placeholder="Insert your notes here..."
733
+ value={this.state.noteInput}
734
+ onChangeText={(noteInput) => this.setState({ noteInput })}
735
+ isValid={() => {
736
+ return !_.isEmpty(this.state.noteInput);
737
+ }}
738
+ autoCapitalize="sentences"
739
+ minHeight={135}
740
+ autoCorrect
741
+ multiline
742
+ autoGrow
743
+ required
744
+ squaredCorners
745
+ sectionStyle={styles.inputSection}
746
+ />
747
+ <Components.GenericInputSection
748
+ label="Attachments"
749
+ sectionStyle={styles.inputSection}
750
+ >
751
+ {this.renderImageList()}
752
+ {this.state.editingNote ? (
753
+ (this.state.noteAttachments || []).map((a, i) =>
754
+ this.renderAttachment(a, i),
755
+ )
756
+ ) : (
757
+ <Components.TextStyle type="body" style={styles.attachmentInfo}>
758
+ PDFs can only be attached to notes from the Community Manager.
759
+ </Components.TextStyle>
760
+ )}
761
+ </Components.GenericInputSection>
762
+ </ScrollView>
763
+ <View style={styles.popupFooter}>{this.renderFooterContent()}</View>
764
+ {this.renderVideoPlayerPopup()}
765
+ </KeyboardAvoidingView>
766
+ </Modal>
767
+ );
768
+ }
769
+
770
+ render() {
771
+ return (
772
+ <View style={styles.container}>
773
+ {this.renderPDF()}
774
+ {this.renderNoteDeleteConfirm()}
775
+ {this.renderAdd()}
776
+ <Components.Header
777
+ leftIcon="angle-left"
778
+ onPressLeft={this.onPressBack}
779
+ text="Staff Notes"
780
+ />
781
+ <ScrollView
782
+ style={styles.scrollContainer}
783
+ contentContainerStyle={styles.innerContainer}
784
+ >
785
+ {this.renderNotes()}
786
+ </ScrollView>
787
+ <Components.AddButton onPress={this.openAddNote} />
788
+ {this.renderImagePopup()}
789
+ </View>
790
+ );
791
+ }
651
792
  }
652
793
 
653
794
  const styles = {
654
- container: {
655
- flex: 1,
656
- position: 'relative',
657
- backgroundColor: '#fff',
658
- },
659
- scrollContainer: {
660
- flex: 1,
661
- },
662
- innerContainer: {
663
- padding: 16,
664
- },
665
- popupFooter: {
666
- paddingHorizontal: 16,
667
- paddingBottom: getBottomSpace() + 16,
668
- },
669
- noteContainer: {
670
- ...Helper.getShadowStyle(),
671
- padding: 8,
672
- marginBottom: 16,
673
- },
674
- noteContainerTop: {
675
- flexDirection: 'row',
676
- alignItems: 'center',
677
- },
678
- profilePic: {
679
- marginRight: 8,
680
- },
681
- noteContainerTopFill: {
682
- flex: 1,
683
- },
684
- noteContainerName: {
685
- fontFamily: 'sf-semibold',
686
- fontSize: 13,
687
- color: Colours.TEXT_DARKEST,
688
- },
689
- noteContainerText: {
690
- marginTop: 8,
691
- fontFamily: 'sf-regular',
692
- fontSize: 13,
693
- color: Colours.TEXT_DARKEST,
694
- },
695
- noteTimestamp: {
696
- marginTop: 8,
697
- fontFamily: 'sf-semibold',
698
- fontSize: 11,
699
- color: Colours.TEXT_LIGHT,
700
- },
701
- noteContainerTopRight: {
702
- flex: 1,
703
- flexDirection: 'row-reverse',
704
- alignItems: 'center',
705
- },
706
- noteContainerButtonContainer: {
707
- width: 24,
708
- height: 24,
709
- borderRadius: 12,
710
- alignItems: 'center',
711
- justifyContent: 'center',
712
- marginLeft: 8,
713
- },
714
- noteContainerButtonIcon: {
715
- fontSize: 13,
716
- color: '#fff',
717
- },
718
- inputSection: {
719
- marginTop: 24,
720
- },
721
- sectionHeading: {
722
- fontFamily: 'qs-bold',
723
- fontSize: 24,
724
- color: Colours.TEXT_DARKEST,
725
- },
726
- attachmentInfo: {
727
- marginTop: 8,
728
- },
729
- imageListContainer: {
730
- marginVertical: 8,
731
- },
732
- imageContainer: {
733
- width: PHOTO_SIZE,
734
- height: PHOTO_SIZE,
735
- borderStyle: 'dashed',
736
- justifyContent: 'center',
737
- alignItems: 'center',
738
- borderWidth: 1,
739
- borderColor: Colours.LINEGREY,
740
- backgroundColor: Colours.BOXGREY,
741
- borderRadius: 4,
742
- marginLeft: 8,
743
- },
744
- imageContainerNotEmpty: {
745
- borderWidth: 1,
746
- borderColor: Colours.LINEGREY,
747
- backgroundColor: '#fff',
748
- borderStyle: 'dashed',
749
- },
750
- imageBackground: {
751
- flex: 1,
752
- height: '100%',
753
- width: '100%',
754
- borderRadius: 4,
755
- },
756
- imageCircle: {
757
- width: 90,
758
- height: 90,
759
- borderRadius: 45,
760
- backgroundColor: Colours.PINKISH_GREY,
761
- justifyContent: 'center',
762
- },
763
- addImageIcon: {
764
- color: '#fff',
765
- fontSize: 32,
766
- },
767
- imagePlayContainer: {
768
- position: 'absolute',
769
- top: 0,
770
- left: 0,
771
- right: 0,
772
- bottom: 0,
773
- alignItems: 'center',
774
- justifyContent: 'center',
775
- },
776
- imageControlIcon: {
777
- color: '#fff',
778
- fontSize: 20,
779
- textShadowColor: 'rgba(0,0,0,0.3)',
780
- textShadowOffset: { width: 2, height: 2 },
781
- },
782
- removeImage: {
783
- position: 'absolute',
784
- top: 0,
785
- right: 0,
786
- padding: 4,
787
- width: 40,
788
- height: 40,
789
- alignItems: 'center',
790
- justifyContent: 'center',
791
- },
792
- sideBySideImages: {
793
- flex: 1,
794
- flexDirection: 'row',
795
- marginTop: 12,
796
- marginHorizontal: -8,
797
- },
798
- sideBySideImageContainer: {
799
- backgroundColor: '#fff',
800
- width: 60,
801
- height: 50,
802
- borderRadius: 2,
803
- overflow: 'hidden',
804
- marginRight: 2,
805
- },
795
+ container: {
796
+ flex: 1,
797
+ position: "relative",
798
+ backgroundColor: "#fff",
799
+ },
800
+ scrollContainer: {
801
+ flex: 1,
802
+ },
803
+ innerContainer: {
804
+ padding: 16,
805
+ },
806
+ popupFooter: {
807
+ paddingHorizontal: 16,
808
+ paddingBottom: getBottomSpace() + 16,
809
+ },
810
+ noteContainer: {
811
+ ...Helper.getShadowStyle(),
812
+ padding: 8,
813
+ marginBottom: 16,
814
+ },
815
+ noteContainerTop: {
816
+ flexDirection: "row",
817
+ alignItems: "center",
818
+ },
819
+ profilePic: {
820
+ marginRight: 8,
821
+ },
822
+ noteContainerTopFill: {
823
+ flex: 1,
824
+ },
825
+ noteContainerName: {
826
+ fontFamily: "sf-semibold",
827
+ fontSize: 13,
828
+ color: Colours.TEXT_DARKEST,
829
+ },
830
+ noteContainerText: {
831
+ marginTop: 8,
832
+ fontFamily: "sf-regular",
833
+ fontSize: 13,
834
+ color: Colours.TEXT_DARKEST,
835
+ },
836
+ noteTimestamp: {
837
+ marginTop: 8,
838
+ fontFamily: "sf-semibold",
839
+ fontSize: 11,
840
+ color: Colours.TEXT_LIGHT,
841
+ },
842
+ noteContainerTopRight: {
843
+ flex: 1,
844
+ flexDirection: "row-reverse",
845
+ alignItems: "center",
846
+ },
847
+ noteContainerButtonContainer: {
848
+ width: 24,
849
+ height: 24,
850
+ borderRadius: 12,
851
+ alignItems: "center",
852
+ justifyContent: "center",
853
+ marginLeft: 8,
854
+ },
855
+ noteContainerButtonIcon: {
856
+ fontSize: 13,
857
+ color: "#fff",
858
+ },
859
+ inputSection: {
860
+ marginTop: 24,
861
+ },
862
+ sectionHeading: {
863
+ fontFamily: "qs-bold",
864
+ fontSize: 24,
865
+ color: Colours.TEXT_DARKEST,
866
+ },
867
+ attachmentInfo: {
868
+ marginTop: 8,
869
+ },
870
+ imageListContainer: {
871
+ marginVertical: 8,
872
+ },
873
+ imageContainer: {
874
+ width: PHOTO_SIZE,
875
+ height: PHOTO_SIZE,
876
+ borderStyle: "dashed",
877
+ justifyContent: "center",
878
+ alignItems: "center",
879
+ borderWidth: 1,
880
+ borderColor: Colours.LINEGREY,
881
+ backgroundColor: Colours.BOXGREY,
882
+ borderRadius: 4,
883
+ marginLeft: 8,
884
+ },
885
+ imageContainerNotEmpty: {
886
+ borderWidth: 1,
887
+ borderColor: Colours.LINEGREY,
888
+ backgroundColor: "#fff",
889
+ borderStyle: "dashed",
890
+ },
891
+ imageBackground: {
892
+ flex: 1,
893
+ height: "100%",
894
+ width: "100%",
895
+ borderRadius: 4,
896
+ },
897
+ imageCircle: {
898
+ width: 90,
899
+ height: 90,
900
+ borderRadius: 45,
901
+ backgroundColor: Colours.PINKISH_GREY,
902
+ justifyContent: "center",
903
+ },
904
+ addImageIcon: {
905
+ color: "#fff",
906
+ fontSize: 32,
907
+ },
908
+ imagePlayContainer: {
909
+ position: "absolute",
910
+ top: 0,
911
+ left: 0,
912
+ right: 0,
913
+ bottom: 0,
914
+ alignItems: "center",
915
+ justifyContent: "center",
916
+ },
917
+ imageControlIcon: {
918
+ color: "#fff",
919
+ fontSize: 20,
920
+ textShadowColor: "rgba(0,0,0,0.3)",
921
+ textShadowOffset: { width: 2, height: 2 },
922
+ },
923
+ removeImage: {
924
+ position: "absolute",
925
+ top: 0,
926
+ right: 0,
927
+ padding: 4,
928
+ width: 40,
929
+ height: 40,
930
+ alignItems: "center",
931
+ justifyContent: "center",
932
+ },
933
+ sideBySideImages: {
934
+ flex: 1,
935
+ flexDirection: "row",
936
+ marginTop: 12,
937
+ marginHorizontal: -8,
938
+ },
939
+ sideBySideImageContainer: {
940
+ backgroundColor: "#fff",
941
+ width: 60,
942
+ height: 50,
943
+ borderRadius: 2,
944
+ overflow: "hidden",
945
+ marginRight: 2,
946
+ },
806
947
  };
807
948
 
808
- const mapStateToProps = state => {
809
- return {
810
- user: state.user,
811
- colourBrandingMain: Colours.getMainBrandingColourFromState(state),
812
- colourBrandingLight: Colours.getLightBrandingColourFromState(state),
813
- jobs: state[values.reducerKey].jobs,
814
- };
949
+ const mapStateToProps = (state) => {
950
+ return {
951
+ user: state.user,
952
+ colourBrandingMain: Colours.getMainBrandingColourFromState(state),
953
+ colourBrandingLight: Colours.getLightBrandingColourFromState(state),
954
+ jobs: state[values.reducerKey].jobs,
955
+ };
815
956
  };
816
957
 
817
958
  export default connect(mapStateToProps, { jobAdded })(RequestNotes);