@plusscommunities/pluss-maintenance-app-forms 7.0.21 → 7.0.23-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 +22 -129
  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 +189 -189
  50. package/dist/module/screens/ServiceRequest.js.map +1 -1
  51. package/dist/module/values.config.a.js +31 -31
  52. package/dist/module/values.config.a.js.map +1 -1
  53. package/dist/module/values.config.default.js +35 -35
  54. package/dist/module/values.config.default.js.map +1 -1
  55. package/dist/module/values.config.enquiry.js +35 -35
  56. package/dist/module/values.config.enquiry.js.map +1 -1
  57. package/dist/module/values.config.feedback.js +35 -35
  58. package/dist/module/values.config.feedback.js.map +1 -1
  59. package/dist/module/values.config.food.js +35 -35
  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 -52
  66. package/src/actions/JobActions.js +60 -67
  67. package/src/actions/index.js +1 -1
  68. package/src/actions/types.js +2 -1
  69. package/src/apis/index.js +3 -3
  70. package/src/apis/maintenanceActions.js +178 -189
  71. package/src/apis/userActions.js +17 -17
  72. package/src/components/FilterPopupMenu.js +256 -313
  73. package/src/components/MaintenanceList.js +317 -396
  74. package/src/components/MaintenanceListItem.js +288 -347
  75. package/src/components/MaintenanceWidgetItem.js +124 -145
  76. package/src/components/PrioritySelectorPopup.js +68 -81
  77. package/src/components/StatusSelectorPopup.js +70 -81
  78. package/src/components/WidgetLarge.js +5 -5
  79. package/src/components/WidgetSmall.js +133 -153
  80. package/src/core.config.js +3 -27
  81. package/src/feature.config.js +62 -62
  82. package/src/helper.js +53 -58
  83. package/src/index.js +22 -22
  84. package/src/reducers/JobsReducer.js +66 -85
  85. package/src/screens/JobTypePicker.js +92 -115
  86. package/src/screens/MaintenancePage.js +80 -89
  87. package/src/screens/MaintenanceUserPicker.js +99 -262
  88. package/src/screens/RequestDetail.js +1125 -1345
  89. package/src/screens/RequestNotes.js +805 -946
  90. package/src/screens/ServiceRequest.js +1557 -1773
  91. package/src/values.config.a.js +34 -34
  92. package/src/values.config.default.js +40 -40
  93. package/src/values.config.enquiry.js +40 -40
  94. package/src/values.config.feedback.js +40 -40
  95. package/src/values.config.food.js +40 -40
  96. package/src/values.config.forms.js +39 -39
  97. package/src/values.config.js +39 -39
@@ -1,958 +1,817 @@
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 =
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
- }
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
+ }
792
651
  }
793
652
 
794
653
  const styles = {
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
- },
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
+ },
947
806
  };
948
807
 
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
- };
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
+ };
956
815
  };
957
816
 
958
817
  export default connect(mapStateToProps, { jobAdded })(RequestNotes);