@plusscommunities/pluss-maintenance-app-forms 6.0.21-beta.0 → 6.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/module/actions/JobActions.js +4 -4
  2. package/dist/module/actions/JobActions.js.map +1 -1
  3. package/dist/module/actions/index.js +1 -1
  4. package/dist/module/actions/index.js.map +1 -1
  5. package/dist/module/actions/types.js +1 -1
  6. package/dist/module/actions/types.js.map +1 -1
  7. package/dist/module/apis/index.js +3 -3
  8. package/dist/module/apis/index.js.map +1 -1
  9. package/dist/module/apis/maintenanceActions.js +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 +0 -1
  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 +194 -190
  50. package/dist/module/screens/ServiceRequest.js.map +1 -1
  51. package/dist/module/values.config.a.js +31 -30
  52. package/dist/module/values.config.a.js.map +1 -1
  53. package/dist/module/values.config.default.js +35 -34
  54. package/dist/module/values.config.default.js.map +1 -1
  55. package/dist/module/values.config.enquiry.js +35 -34
  56. package/dist/module/values.config.enquiry.js.map +1 -1
  57. package/dist/module/values.config.feedback.js +35 -34
  58. package/dist/module/values.config.feedback.js.map +1 -1
  59. package/dist/module/values.config.food.js +35 -34
  60. package/dist/module/values.config.food.js.map +1 -1
  61. package/dist/module/values.config.forms.js +34 -34
  62. package/dist/module/values.config.forms.js.map +1 -1
  63. package/dist/module/values.config.js +34 -34
  64. package/dist/module/values.config.js.map +1 -1
  65. package/package.json +51 -51
  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 +100 -252
  88. package/src/screens/RequestDetail.js +1 -1
  89. package/src/screens/RequestNotes.js +806 -950
  90. package/src/screens/ServiceRequest.js +1557 -1781
  91. package/src/values.config.a.js +34 -33
  92. package/src/values.config.default.js +40 -39
  93. package/src/values.config.enquiry.js +40 -39
  94. package/src/values.config.feedback.js +40 -39
  95. package/src/values.config.food.js +40 -39
  96. package/src/values.config.forms.js +39 -39
  97. package/src/values.config.js +39 -39
@@ -1,961 +1,817 @@
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;
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;
27
15
  const PHOTO_SIZE = (SCREEN_WIDTH - 64) / 3;
28
16
 
29
17
  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
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
- }
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
+ }
795
651
  }
796
652
 
797
653
  const styles = {
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
- },
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
+ },
950
806
  };
951
807
 
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
- };
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
+ };
959
815
  };
960
816
 
961
817
  export default connect(mapStateToProps, { jobAdded })(RequestNotes);