@plusscommunities/pluss-maintenance-app 6.0.20 → 6.0.22-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.
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 +36 -36
  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 +145 -145
  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 +2 -2
  50. package/dist/module/screens/ServiceRequest.js.map +1 -1
  51. package/dist/module/values.config.a.js +30 -30
  52. package/dist/module/values.config.a.js.map +1 -1
  53. package/dist/module/values.config.default.js +34 -34
  54. package/dist/module/values.config.default.js.map +1 -1
  55. package/dist/module/values.config.enquiry.js +34 -34
  56. package/dist/module/values.config.enquiry.js.map +1 -1
  57. package/dist/module/values.config.feedback.js +34 -34
  58. package/dist/module/values.config.feedback.js.map +1 -1
  59. package/dist/module/values.config.food.js +34 -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 +51 -51
  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 -178
  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 +263 -100
  88. package/src/screens/RequestDetail.js +1348 -1125
  89. package/src/screens/RequestNotes.js +950 -806
  90. package/src/screens/ServiceRequest.js +68 -21
  91. package/src/values.config.a.js +33 -33
  92. package/src/values.config.default.js +39 -39
  93. package/src/values.config.enquiry.js +39 -39
  94. package/src/values.config.feedback.js +39 -39
  95. package/src/values.config.food.js +39 -39
  96. package/src/values.config.forms.js +39 -39
  97. package/src/values.config.js +39 -39
@@ -1,817 +1,961 @@
1
- import React, { Component } from 'react';
2
- import { ScrollView, View, Text, TouchableOpacity, Modal, KeyboardAvoidingView, Platform, FlatList, Dimensions, ImageBackground, Keyboard } from 'react-native';
3
- import { connect } from 'react-redux';
4
- import { Icon } from 'react-native-elements';
5
- import _ from 'lodash';
6
- import moment from 'moment';
7
- import { jobAdded } from '../actions';
8
- import { maintenanceActions } from '../apis';
9
- import { getBottomSpace } from 'react-native-iphone-x-helper';
10
- import { Services } from '../feature.config';
11
- import { Components, Colours, Helper } from '../core.config';
12
- import { values } from '../values.config';
13
-
14
- const SCREEN_WIDTH = Dimensions.get('window').width;
1
+ import React, { Component } from "react";
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 "react-native-elements";
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;
15
27
  const PHOTO_SIZE = (SCREEN_WIDTH - 64) / 3;
16
28
 
17
29
  class RequestNotes extends Component {
18
- constructor(props) {
19
- super(props);
20
-
21
- this.state = {
22
- job: {},
23
- selectedPDF: null,
24
- noteAttachments: [],
25
- noteInput: '',
26
- addNoteOpen: false,
27
- noteImages: [{ add: true }],
28
- uploadingImage: false,
29
- galleryOpen: false,
30
- galleryImages: [],
31
- galleryIndex: 0
32
- };
33
- this.checkThumb = null;
34
- }
35
-
36
- componentDidMount() {
37
- this.updateJobState();
38
- }
39
-
40
- componentDidUpdate(prevProps) {
41
- if (prevProps.jobs !== this.props.jobs) {
42
- this.updateJobState();
43
- }
44
- }
45
-
46
- componentWillUnmount() {
47
- clearInterval(this.checkThumb);
48
- }
49
-
50
- updateJobState() {
51
- const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || this.props.job;
52
- this.setState({ job });
53
- }
54
-
55
- onPressBack = () => {
56
- Services.navigation.goBack();
57
- };
58
-
59
- onOpenAttachment = a => {
60
- this.setState({
61
- selectedPDF: a,
62
- });
63
- };
64
-
65
- onCloseAttachment = () => {
66
- this.setState({
67
- selectedPDF: null,
68
- });
69
- };
70
-
71
- onPressDeleteNote = n => {
72
- this.setState({
73
- noteToDelete: n,
74
- });
75
- };
76
-
77
- onPressConfirmDelete = () => {
78
- maintenanceActions.deleteNote(this.props.job.id, this.state.noteToDelete.Id);
79
- const newNotes = _.filter(this.state.job.Notes, note => {
80
- return note.Id !== this.state.noteToDelete.Id;
81
- });
82
- const newJob = { ...this.props.job };
83
- newJob.Notes = newNotes;
84
- this.props.jobAdded(newJob);
85
- this.props.onChange && this.props.onChange();
86
- this.setState({
87
- job: newJob,
88
- noteToDelete: null,
89
- });
90
- };
91
-
92
- onPressCancelDelete = () => {
93
- this.setState({ noteToDelete: null });
94
- };
95
-
96
- isReadyToSaveNote = () => {
97
- const uploadingAttachments = _.some(this.state.noteAttachments, n => n.Uploading);
98
- const uploadingImages = _.some(this.state.noteImages, n => n.uploading);
99
- if (uploadingAttachments || uploadingImages) return false;
100
- return !_.isEmpty(this.state.noteInput) || !_.isEmpty(this.state.noteAttachments) || !_.isEmpty(this.state.noteImages.filter(img => !img.add));
101
- };
102
-
103
- openAddNote = () => {
104
- this.setState({
105
- addNoteOpen: true,
106
- editingNote: null,
107
- });
108
- };
109
-
110
- closeAddNote = () => {
111
- const newState = {
112
- addNoteOpen: false,
113
- editingNote: null,
114
- };
115
- if (!!this.state.editingNote) {
116
- newState.noteInput = '';
117
- newState.noteAttachments = [];
118
- newState.noteImages = [];
119
- }
120
- this.setState(newState);
121
- };
122
-
123
- openEditNote = n => {
124
- const noteImages = [...((n.Images || []).map(i => ({ url: i, thumbNailUrl: Helper.getThumb300(i) }))), { add: true }];
125
- this.setState({
126
- noteAttachments: n.Attachments || [],
127
- noteImages,
128
- noteInput: n.Note || '',
129
- addNoteOpen: true,
130
- editingNote: n.Id,
131
- noteMenuOpen: null,
132
- });
133
- };
134
-
135
- confirmAddNote = async () => {
136
- if (!this.isReadyToSaveNote()) {
137
- return;
138
- }
139
-
140
- try {
141
- this.setState({ submittingNote: true });
142
- const attachments = this.state.noteAttachments.filter(a => !a.add).map(a => {
143
- return {
144
- Title: a.Title,
145
- Source: a.Source,
146
- };
147
- });
148
- const images = this.state.noteImages.filter(img => !img.add).map(img => img.url);
149
- const res = await (this.state.editingNote
150
- ? maintenanceActions.editNote(
151
- this.props.job.id,
152
- this.state.editingNote,
153
- this.state.noteInput,
154
- attachments,
155
- images,
156
- )
157
- : maintenanceActions.addNote(
158
- this.props.job.id,
159
- this.state.noteInput,
160
- attachments,
161
- images,
162
- ));
163
- this.setState(
164
- {
165
- job: res.data.job,
166
- submittingNote: false,
167
- addNoteOpen: false,
168
- noteInput: '',
169
- noteAttachments: [],
170
- noteImages: [{ add: true }],
171
- uploadingImage: false,
172
- editingNote: null,
173
- },
174
- () => {
175
- this.props.jobAdded(res.data.job);
176
- this.props.onChange && this.props.onChange();
177
- },
178
- );
179
- } catch (err) {
180
- console.log('error');
181
- console.log(err);
182
- this.setState({
183
- submittingNote: false,
184
- uploadingImage: false,
185
- });
186
- }
187
- };
188
-
189
- setImages = (noteImages, uploadingImage = false, callback = null) => {
190
- this.setState({ noteImages, uploadingImage }, callback);
191
- };
192
-
193
- getImages = () => {
194
- const { noteImages } = this.state;
195
- const imagesList = _.cloneDeep(noteImages);
196
- if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
197
- return [{ add: true }];
198
- }
199
- return imagesList;
200
- };
201
-
202
- getImageUrls = () => {
203
- const imagesList = this.getImages();
204
- return _.filter(imagesList, img => {
205
- return !img.uploading && !img.add;
206
- }).map(img => {
207
- return img.url;
208
- });
209
- };
210
-
211
- waitForThumbnails = () => {
212
- if (this.checkThumb) return;
213
-
214
- this.checkThumb = setInterval(async () => {
215
- const imagesList = this.getImages();
216
- const imagesUpdate = [];
217
- await Promise.all(
218
- imagesList.map(image => {
219
- return new Promise(async resolve => {
220
- const newImage = { ...image };
221
- imagesUpdate.push(newImage);
222
- if (newImage.url && !newImage.thumbNailExists) {
223
- newImage.uploading = false;
224
- newImage.allowRetry = false;
225
- newImage.thumbNailExists = await Helper.imageExists(newImage.thumbNailUrl);
226
- resolve(newImage.thumbNailExists);
227
- }
228
- resolve(true);
229
- });
230
- }),
231
- );
232
- const thumbnailsExist = imagesUpdate.every(image => !image.url || image.thumbNailExists);
233
- if (thumbnailsExist) {
234
- clearInterval(this.checkThumb);
235
- this.checkThumb = null;
236
- this.setImages(imagesUpdate);
237
- }
238
- }, 2000);
239
- };
240
-
241
- removeImage = (index) => {
242
- const imagesUpdate = this.getImages();
243
- imagesUpdate.splice(index, 1);
244
-
245
- this.setImages(imagesUpdate);
246
- };
247
-
248
- showUploadMenu = () => {
249
- Keyboard.dismiss();
250
- const { uploadingImage, submittingNote } = this.state;
251
- if (uploadingImage || submittingNote) return;
252
- this.imageUploader.showUploadMenu();
253
- };
254
-
255
- toggleFullscreenVideo = url => {
256
- if (typeof url !== 'string') url = '';
257
- this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
258
- };
259
-
260
- openGallery(images, index) {
261
- this.setState({ galleryOpen: true, galleryImages: images, galleryIndex: index });
262
- }
263
-
264
- closeGallery() {
265
- this.setState({ galleryOpen: false, galleryImages: [], galleryIndex: 0 });
266
- }
267
-
268
- onUploadStarted = (uploadUri, imageUri) => {
269
- const imagesUpdate = this.getImages();
270
- imagesUpdate.splice(imagesUpdate.length - 1, 0, {
271
- uploading: true,
272
- uploadProgress: '0%',
273
- uploadUri,
274
- imageUri,
275
- allowRetry: true,
276
- });
277
-
278
- this.setImages(imagesUpdate, true);
279
- };
280
-
281
- onUploadProgress = progress => {
282
- const imagesUpdate = this.getImages();
283
- imagesUpdate.map(img => {
284
- if (img.uploadUri === progress.uri) {
285
- img.uploadProgress = progress.percentage;
286
- img.uploading = true;
287
- img.allowRetry = true;
288
- }
289
- });
290
-
291
- this.setImages(imagesUpdate, true);
292
- };
293
-
294
- onUploadSuccess = async (uri, uploadUri) => {
295
- const imagesUpdate = this.getImages();
296
- imagesUpdate.map(img => {
297
- if (img.uploadUri === uploadUri && img.uploading) {
298
- img.url = uri.replace('/general/', '/general1400/');
299
- img.thumbNailExists = false;
300
- img.thumbNailUrl = Helper.getThumb300(img.url);
301
- img.allowRetry = true;
302
- }
303
- });
304
-
305
- this.setImages(imagesUpdate, false, () => this.waitForThumbnails());
306
- };
307
-
308
- onUploadFailed = uploadUri => {
309
- const imagesUpdate = this.getImages();
310
- imagesUpdate.map(img => {
311
- if (img.uploadUri === uploadUri) {
312
- img.uploading = true; // Requried for retry
313
- img.uploadProgress = '';
314
- img.allowRetry = true;
315
- }
316
- });
317
-
318
- this.setImages(imagesUpdate);
319
- };
320
-
321
- onLibrarySelected = uri => {
322
- const imagesUpdate = this.getImages();
323
- imagesUpdate.splice(imagesUpdate.length - 1, 0, {
324
- uploading: false,
325
- allowRetry: false,
326
- url: Helper.get1400(uri),
327
- thumbNailExists: true,
328
- thumbNailUrl: Helper.getThumb300(uri),
329
- });
330
-
331
- this.setImages(imagesUpdate);
332
- };
333
-
334
- renderAttachment(a, i) {
335
- return (
336
- <Components.Attachment
337
- onPress={() => {
338
- this.onOpenAttachment(a);
339
- }}
340
- key={i}
341
- title={a.Title}
342
- />
343
- );
344
- }
345
-
346
- renderPlayableImageUrl(images, index, containerStyle) {
347
- const url = images[index];
348
- const thumbUrl = Helper.getThumb300(url);
349
-
350
- if (Helper.isVideo(url)) {
351
- return (
352
- <ImageBackground style={[{ flex: 1 }, containerStyle]} source={{ uri: thumbUrl }}>
353
- <View style={styles.imagePlayContainer}>
354
- <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, url)}>
355
- <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
356
- </TouchableOpacity>
357
- </View>
358
- </ImageBackground>
359
- );
360
- }
361
-
362
- const imageUrl = Helper.get1400(url);
363
- return (
364
- <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, images, index || 0)}>
365
- <ImageBackground resizeMode='cover' style={styles.imageContainer} source={{ uri: imageUrl }} />
366
- </TouchableOpacity>
367
- );
368
- }
369
-
370
- renderImages(note) {
371
- if (!note) return null
372
- if (!_.isNil(note.Images) && !_.isEmpty(note.Images)) {
373
- return (
374
- <ScrollView horizontal style={styles.sideBySideImages}>
375
- {note.Images.map((_p, index) =>
376
- this.renderPlayableImageUrl(note.Images, index, styles.sideBySideImageContainer),
377
- )}
378
- </ScrollView>
379
- );
380
- }
381
- return null;
382
- }
383
-
384
- renderImagePopup() {
385
- const { galleryOpen, galleryImages, galleryIndex } = this.state;
386
- console.log('renderimagepopup', { galleryOpen, galleryImages, galleryIndex });
387
- return (
388
- <Components.ImagePopup
389
- visible={galleryOpen}
390
- images={galleryImages}
391
- index={galleryIndex}
392
- onClose={this.closeGallery.bind(this)}
393
- />
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={Platform.OS === 'ios' && '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
717
+ style={styles.container}
718
+ behavior={Platform.OS === "ios" && "padding"}
719
+ >
720
+ {this.renderUploadMenu()}
721
+ <Components.Header
722
+ leftText="Cancel"
723
+ onPressLeft={this.closeAddNote}
724
+ text=""
725
+ />
726
+ <ScrollView
727
+ style={styles.scrollContainer}
728
+ contentContainerStyle={styles.innerContainer}
729
+ >
730
+ <Components.TextStyle type="pageHeading">
731
+ Add Staff Note
732
+ </Components.TextStyle>
733
+ <Components.GenericInputSection
734
+ label="Content"
735
+ placeholder="Insert your notes here..."
736
+ value={this.state.noteInput}
737
+ onChangeText={(noteInput) => this.setState({ noteInput })}
738
+ isValid={() => {
739
+ return !_.isEmpty(this.state.noteInput);
740
+ }}
741
+ autoCapitalize="sentences"
742
+ minHeight={135}
743
+ autoCorrect
744
+ multiline
745
+ autoGrow
746
+ required
747
+ squaredCorners
748
+ sectionStyle={styles.inputSection}
749
+ />
750
+ <Components.GenericInputSection
751
+ label="Attachments"
752
+ sectionStyle={styles.inputSection}
753
+ >
754
+ {this.renderImageList()}
755
+ {this.state.editingNote ? (
756
+ (this.state.noteAttachments || []).map((a, i) =>
757
+ this.renderAttachment(a, i),
758
+ )
759
+ ) : (
760
+ <Components.TextStyle type="body" style={styles.attachmentInfo}>
761
+ PDFs can only be attached to notes from the Community Manager.
762
+ </Components.TextStyle>
763
+ )}
764
+ </Components.GenericInputSection>
765
+ </ScrollView>
766
+ <View style={styles.popupFooter}>{this.renderFooterContent()}</View>
767
+ {this.renderVideoPlayerPopup()}
768
+ </KeyboardAvoidingView>
769
+ </Modal>
770
+ );
771
+ }
772
+
773
+ render() {
774
+ return (
775
+ <View style={styles.container}>
776
+ {this.renderPDF()}
777
+ {this.renderNoteDeleteConfirm()}
778
+ {this.renderAdd()}
779
+ <Components.Header
780
+ leftIcon="angle-left"
781
+ onPressLeft={this.onPressBack}
782
+ text="Staff Notes"
783
+ />
784
+ <ScrollView
785
+ style={styles.scrollContainer}
786
+ contentContainerStyle={styles.innerContainer}
787
+ >
788
+ {this.renderNotes()}
789
+ </ScrollView>
790
+ <Components.AddButton onPress={this.openAddNote} />
791
+ {this.renderImagePopup()}
792
+ </View>
793
+ );
794
+ }
651
795
  }
652
796
 
653
797
  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
- },
798
+ container: {
799
+ flex: 1,
800
+ position: "relative",
801
+ backgroundColor: "#fff",
802
+ },
803
+ scrollContainer: {
804
+ flex: 1,
805
+ },
806
+ innerContainer: {
807
+ padding: 16,
808
+ },
809
+ popupFooter: {
810
+ paddingHorizontal: 16,
811
+ paddingBottom: getBottomSpace() + 16,
812
+ },
813
+ noteContainer: {
814
+ ...Helper.getShadowStyle(),
815
+ padding: 8,
816
+ marginBottom: 16,
817
+ },
818
+ noteContainerTop: {
819
+ flexDirection: "row",
820
+ alignItems: "center",
821
+ },
822
+ profilePic: {
823
+ marginRight: 8,
824
+ },
825
+ noteContainerTopFill: {
826
+ flex: 1,
827
+ },
828
+ noteContainerName: {
829
+ fontFamily: "sf-semibold",
830
+ fontSize: 13,
831
+ color: Colours.TEXT_DARKEST,
832
+ },
833
+ noteContainerText: {
834
+ marginTop: 8,
835
+ fontFamily: "sf-regular",
836
+ fontSize: 13,
837
+ color: Colours.TEXT_DARKEST,
838
+ },
839
+ noteTimestamp: {
840
+ marginTop: 8,
841
+ fontFamily: "sf-semibold",
842
+ fontSize: 11,
843
+ color: Colours.TEXT_LIGHT,
844
+ },
845
+ noteContainerTopRight: {
846
+ flex: 1,
847
+ flexDirection: "row-reverse",
848
+ alignItems: "center",
849
+ },
850
+ noteContainerButtonContainer: {
851
+ width: 24,
852
+ height: 24,
853
+ borderRadius: 12,
854
+ alignItems: "center",
855
+ justifyContent: "center",
856
+ marginLeft: 8,
857
+ },
858
+ noteContainerButtonIcon: {
859
+ fontSize: 13,
860
+ color: "#fff",
861
+ },
862
+ inputSection: {
863
+ marginTop: 24,
864
+ },
865
+ sectionHeading: {
866
+ fontFamily: "qs-bold",
867
+ fontSize: 24,
868
+ color: Colours.TEXT_DARKEST,
869
+ },
870
+ attachmentInfo: {
871
+ marginTop: 8,
872
+ },
873
+ imageListContainer: {
874
+ marginVertical: 8,
875
+ },
876
+ imageContainer: {
877
+ width: PHOTO_SIZE,
878
+ height: PHOTO_SIZE,
879
+ borderStyle: "dashed",
880
+ justifyContent: "center",
881
+ alignItems: "center",
882
+ borderWidth: 1,
883
+ borderColor: Colours.LINEGREY,
884
+ backgroundColor: Colours.BOXGREY,
885
+ borderRadius: 4,
886
+ marginLeft: 8,
887
+ },
888
+ imageContainerNotEmpty: {
889
+ borderWidth: 1,
890
+ borderColor: Colours.LINEGREY,
891
+ backgroundColor: "#fff",
892
+ borderStyle: "dashed",
893
+ },
894
+ imageBackground: {
895
+ flex: 1,
896
+ height: "100%",
897
+ width: "100%",
898
+ borderRadius: 4,
899
+ },
900
+ imageCircle: {
901
+ width: 90,
902
+ height: 90,
903
+ borderRadius: 45,
904
+ backgroundColor: Colours.PINKISH_GREY,
905
+ justifyContent: "center",
906
+ },
907
+ addImageIcon: {
908
+ color: "#fff",
909
+ fontSize: 32,
910
+ },
911
+ imagePlayContainer: {
912
+ position: "absolute",
913
+ top: 0,
914
+ left: 0,
915
+ right: 0,
916
+ bottom: 0,
917
+ alignItems: "center",
918
+ justifyContent: "center",
919
+ },
920
+ imageControlIcon: {
921
+ color: "#fff",
922
+ fontSize: 20,
923
+ textShadowColor: "rgba(0,0,0,0.3)",
924
+ textShadowOffset: { width: 2, height: 2 },
925
+ },
926
+ removeImage: {
927
+ position: "absolute",
928
+ top: 0,
929
+ right: 0,
930
+ padding: 4,
931
+ width: 40,
932
+ height: 40,
933
+ alignItems: "center",
934
+ justifyContent: "center",
935
+ },
936
+ sideBySideImages: {
937
+ flex: 1,
938
+ flexDirection: "row",
939
+ marginTop: 12,
940
+ marginHorizontal: -8,
941
+ },
942
+ sideBySideImageContainer: {
943
+ backgroundColor: "#fff",
944
+ width: 60,
945
+ height: 50,
946
+ borderRadius: 2,
947
+ overflow: "hidden",
948
+ marginRight: 2,
949
+ },
806
950
  };
807
951
 
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
- };
952
+ const mapStateToProps = (state) => {
953
+ return {
954
+ user: state.user,
955
+ colourBrandingMain: Colours.getMainBrandingColourFromState(state),
956
+ colourBrandingLight: Colours.getLightBrandingColourFromState(state),
957
+ jobs: state[values.reducerKey].jobs,
958
+ };
815
959
  };
816
960
 
817
961
  export default connect(mapStateToProps, { jobAdded })(RequestNotes);