@iservice365/layer-common 1.1.0 → 1.2.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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/CameraForm.vue +264 -0
  3. package/components/CameraMain.vue +352 -0
  4. package/components/Card/DeleteConfirmation.vue +51 -0
  5. package/components/Card/MemberInfoSummary.vue +44 -0
  6. package/components/Chat/Information.vue +28 -11
  7. package/components/Dialog/DeleteConfirmation.vue +51 -0
  8. package/components/Dialog/UpdateMoreAction.vue +99 -0
  9. package/components/Feedback/Form.vue +17 -3
  10. package/components/FeedbackDetail.vue +0 -11
  11. package/components/FeedbackMain.vue +21 -11
  12. package/components/Input/DateTimePicker.vue +5 -1
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +111 -63
  15. package/components/Input/InputPhoneNumberV2.vue +115 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +41 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +420 -0
  21. package/components/TableMain.vue +2 -1
  22. package/components/VehicleUpdateMoreAction.vue +84 -0
  23. package/components/VisitorForm.vue +712 -0
  24. package/components/VisitorFormSelection.vue +53 -0
  25. package/components/VisitorManagement.vue +568 -0
  26. package/components/WorkOrder/Create.vue +70 -46
  27. package/components/WorkOrder/Main.vue +11 -10
  28. package/composables/useBuilding.ts +250 -0
  29. package/composables/useBuildingUnit.ts +116 -0
  30. package/composables/useFeedback.ts +3 -3
  31. package/composables/useFile.ts +7 -9
  32. package/composables/useLocal.ts +67 -0
  33. package/composables/usePeople.ts +48 -0
  34. package/composables/useSecurityUtils.ts +18 -0
  35. package/composables/useSiteSettings.ts +111 -0
  36. package/composables/useUtils.ts +30 -1
  37. package/composables/useVisitor.ts +79 -0
  38. package/package.json +1 -1
  39. package/plugins/vuetify.ts +6 -1
  40. package/types/building.d.ts +19 -0
  41. package/types/camera.d.ts +31 -0
  42. package/types/people.d.ts +22 -0
  43. package/types/select.d.ts +4 -0
  44. package/types/site.d.ts +10 -7
  45. package/types/visitor.d.ts +42 -0
  46. package/utils/phoneMasks.ts +1703 -0
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <v-card flat border="sm grey-lighten-2" color="grey-lighten-3" class="w-100">
3
+ <v-card-text>
4
+ <v-row no-gutters>
5
+ <v-col cols="10" no-gutters>
6
+ <v-row no-gutters class="ga-1">
7
+ <template v-for="(label, key) in displayFields" :key="key">
8
+ <v-col v-if="member[key]" cols="12">
9
+ <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ member[key]
10
+ }}</span>
11
+ </v-col>
12
+ </template>
13
+ </v-row>
14
+ </v-col>
15
+ <v-col cols="2" class="d-flex align-center justify-center">
16
+ <v-btn icon="mdi-trash-outline" size="small" color="red" @click="emit('remove')" />
17
+ </v-col>
18
+ </v-row>
19
+ </v-card-text>
20
+ </v-card>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+
25
+ const prop = defineProps({
26
+ member: {
27
+ type: Object as PropType<TMemberInfo>,
28
+ required: true
29
+ }
30
+ })
31
+
32
+ const emit = defineEmits(['remove'])
33
+
34
+
35
+ const displayFields: Record<keyof TMemberInfo, string> = {
36
+ name: "Name",
37
+ nric: "NRIC",
38
+ visitorPass: "Pass",
39
+ contact: "Contact"
40
+ } as const
41
+
42
+ </script>
43
+
44
+ <style scoped></style>
@@ -62,7 +62,7 @@
62
62
  <v-row dense class="my-1 pr-4">
63
63
  <v-col cols="6" class="py-1"><strong>Category:</strong></v-col>
64
64
  <v-col cols="6" class="py-1 text-right text-capitalize">{{
65
- item.categoryInfo
65
+ item.category
66
66
  }}</v-col>
67
67
  </v-row>
68
68
 
@@ -186,7 +186,7 @@
186
186
 
187
187
  <WorkOrderCreate
188
188
  v-model="showCreateDialog"
189
- :created-from="'feedback'"
189
+ created-from="feedback"
190
190
  :work-order="_workOrder"
191
191
  @update:work-order="(val: TWorkOrderCreate) => (_workOrder = val)"
192
192
  :is-edit-mode="isEditMode"
@@ -269,20 +269,37 @@ const _workOrder = ref<TWorkOrderCreate>({
269
269
  const serviceProviders = ref<
270
270
  Array<{ title: string; value: string; subtitle: string }>
271
271
  >([]);
272
- const { getAll: getAllServiceProvider } = useServiceProvider();
273
-
274
- const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
275
- getAllServiceProvider({
276
- siteId: useRoute().params.site as string,
277
- })
272
+ // const { getAll: getAllServiceProvider } = useServiceProvider();
273
+
274
+ // const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
275
+ // getAllServiceProvider({
276
+ // siteId: useRoute().params.site as string,
277
+ // })
278
+ // );
279
+
280
+ // watchEffect(() => {
281
+ // if (getAllReq.value) {
282
+ // serviceProviders.value = getAllReq.value.items.map((i: any) => ({
283
+ // title: i.nature.replace(/_/g, " "),
284
+ // value: i.serviceProviderOrgId,
285
+ // subtitle: i.name,
286
+ // }));
287
+ // }
288
+ // });
289
+
290
+ const { getBySiteAsServiceProvider } = useCustomerSite();
291
+
292
+ const { data: getAllReq } = useLazyAsyncData(
293
+ "get-by-site-as-service-provider",
294
+ () => getBySiteAsServiceProvider(useRoute().params.site as string)
278
295
  );
279
296
 
280
297
  watchEffect(() => {
281
298
  if (getAllReq.value) {
282
- serviceProviders.value = getAllReq.value.items.map((i: any) => ({
299
+ serviceProviders.value = getAllReq.value.map((i: any) => ({
283
300
  title: i.nature.replace(/_/g, " "),
284
- value: i.serviceProviderOrgId,
285
- subtitle: i.name,
301
+ subtitle: i.title,
302
+ value: i._id.org,
286
303
  }));
287
304
  }
288
305
  });
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <v-card width="100%" :disabled="loading" :loading="loading">
3
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5 px-7 text-center">
4
+ <span> {{ promptTitle }}</span>
5
+
6
+ <span v-if="message" class="text-error mt-2">
7
+ {{ message }} Do you want to delete anyway?
8
+ </span>
9
+ </v-card-text>
10
+
11
+ <v-toolbar class="pa-0" density="compact">
12
+ <v-row no-gutters>
13
+ <v-col cols="6" class="pa-0">
14
+ <v-btn block variant="text" class="text-none" size="large" tile @click="emit('close')"
15
+ height="48">
16
+ Cancel
17
+ </v-btn>
18
+ </v-col>
19
+
20
+ <v-col cols="6" class="pa-0">
21
+ <v-btn block tile variant="flat" class="text-none" size="large" height="48" color="black"
22
+ @click="emit('delete')">
23
+ Delete
24
+ </v-btn>
25
+ </v-col>
26
+ </v-row>
27
+ </v-toolbar>
28
+ </v-card>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ const props = defineProps({
33
+ message: {
34
+ type: String,
35
+ default: ""
36
+ },
37
+ promptTitle: {
38
+ type: String,
39
+ default: "Are you sure want to delete this? "
40
+ },
41
+ loading: {
42
+ type: Boolean,
43
+ default: false
44
+ }
45
+ })
46
+
47
+ const emit = defineEmits(["close", "delete"])
48
+
49
+ </script>
50
+
51
+ <style scoped></style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <template v-if="$slots.header">
5
+ <slot name="header" :title="title" :onClose="onClose" />
6
+ </template>
7
+ <template v-else>
8
+ <v-row no-gutters class="fill-height px-6" align="center">
9
+ <span class="font-weight-bold text-h5 text-capitalize">{{
10
+ title
11
+ }}</span>
12
+ </v-row>
13
+ </template>
14
+ </v-toolbar>
15
+
16
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
17
+ <slot name="content" />
18
+ </v-card-text>
19
+
20
+ <v-toolbar class="pa-0" density="compact">
21
+ <v-row no-gutters>
22
+ <v-col cols="6" class="pa-0">
23
+ <v-btn
24
+ block
25
+ variant="text"
26
+ class="text-none"
27
+ size="large"
28
+ @click="emit('close')"
29
+ height="48"
30
+ >
31
+ Close
32
+ </v-btn>
33
+ </v-col>
34
+
35
+ <v-col cols="6" class="pa-0" >
36
+ <v-menu contained>
37
+ <template #activator="{ props }">
38
+ <v-btn
39
+ block
40
+ variant="flat"
41
+ color="black"
42
+ class="text-none"
43
+ height="48"
44
+ v-bind="props"
45
+ tile
46
+ >
47
+ More actions
48
+ </v-btn>
49
+ </template>
50
+
51
+ <v-list class="pa-0">
52
+ <v-list-item v-if="canUpdate" @click="emit('edit')">
53
+ <v-list-item-title class="text-subtitle-2">
54
+ {{ editButtonLabel }}
55
+ </v-list-item-title>
56
+ </v-list-item>
57
+
58
+ <v-list-item v-if="canDelete" @click="emit('delete')" class="text-red">
59
+ <v-list-item-title class="text-subtitle-2">
60
+ {{ deleteButtonLabel }}
61
+ </v-list-item-title>
62
+ </v-list-item>
63
+ </v-list>
64
+ </v-menu>
65
+ </v-col>
66
+ </v-row>
67
+ </v-toolbar>
68
+ </v-card>
69
+ </template>
70
+
71
+ <script setup lang="ts">
72
+ const prop = defineProps({
73
+ canUpdate: {
74
+ type: Boolean,
75
+ default: true,
76
+ },
77
+ canDelete: {
78
+ type: Boolean,
79
+ default: true,
80
+ },
81
+ editButtonLabel: {
82
+ type: String,
83
+ default: "Edit",
84
+ },
85
+ deleteButtonLabel: {
86
+ type: String,
87
+ default: "Delete",
88
+ },
89
+ title: {
90
+ type: String,
91
+ default: "Details",
92
+ },
93
+ });
94
+
95
+ const emit = defineEmits(["close", "edit", "delete"]);
96
+ const { canUpdate, editButtonLabel, deleteButtonLabel, title } = prop;
97
+ </script>
98
+
99
+ <style scoped></style>
@@ -15,13 +15,18 @@
15
15
  @errored="onImageError"
16
16
  />
17
17
 
18
- <v-text-field
18
+ <v-autocomplete
19
+ v-model="localFeedback.subject"
20
+ :items="subjects"
21
+ item-title="title"
22
+ item-value="value"
23
+ item-props
19
24
  label="Subject"
20
25
  variant="outlined"
21
26
  density="compact"
22
- v-model="localFeedback.subject"
23
27
  class="mb-2"
24
- dense
28
+ clearable
29
+ :custom-filter="customFilter"
25
30
  />
26
31
 
27
32
  <v-autocomplete
@@ -133,4 +138,13 @@ function closeDialog() {
133
138
  function submitFeedback() {
134
139
  emit("submit", { ...localFeedback.value });
135
140
  }
141
+
142
+ const { subjects } = useLocal();
143
+
144
+ const customFilter = (value: string, query: string, item: any) => {
145
+ const title = item?.raw?.title?.toLowerCase() || "";
146
+ const subtitle = item?.raw?.subtitle?.toLowerCase() || "";
147
+ const search = query.toLowerCase();
148
+ return title.includes(search) || subtitle.includes(search);
149
+ };
136
150
  </script>
@@ -1,16 +1,6 @@
1
1
  <template>
2
2
  <div class="feedback-detail-wrapper">
3
3
  <v-row no-gutters class="fill-height">
4
- <!-- <v-col cols="12" xl="3" lg="4" md="4" class="fill-height">
5
- <div class="panel-container border-e">
6
- <ChatNavigation
7
- :title="'Feedbacks'"
8
- :items="items"
9
- @select="handleSelectFeedback"
10
- @search="_getFeedbacks"
11
- />
12
- </div>
13
- </v-col> -->
14
4
 
15
5
  <v-col cols="12" xl="7" lg="7" md="7" class="fill-height">
16
6
  <div class="panel-container border-e">
@@ -179,7 +169,6 @@ const {
179
169
  } = useLazyAsyncData("get-all-feedbacks", () =>
180
170
  _getFeedbacks({
181
171
  page: page.value,
182
- organization: route.params.org as string,
183
172
  site: route.params.site as string,
184
173
  })
185
174
  );
@@ -164,6 +164,10 @@ const props = defineProps({
164
164
  type: Boolean,
165
165
  default: false,
166
166
  },
167
+ category: {
168
+ type: String,
169
+ default: "",
170
+ },
167
171
  });
168
172
 
169
173
  const isEditMode = ref(false);
@@ -179,20 +183,27 @@ const { getUserFromCookie } = useLocal();
179
183
  const serviceProviders = ref<
180
184
  Array<{ title: string; value: string; subtitle: string }>
181
185
  >([]);
182
- const { getAll: getAllServiceProvider } = useServiceProvider();
186
+ // const { getAll: getAllServiceProvider } = useServiceProvider();
183
187
 
184
- const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
185
- getAllServiceProvider({
186
- siteId: useRoute().params.site as string,
187
- })
188
+ // const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
189
+ // getAllServiceProvider({
190
+ // siteId: useRoute().params.site as string,
191
+ // })
192
+ // );
193
+
194
+ const { getBySiteAsServiceProvider } = useCustomerSite();
195
+
196
+ const { data: getAllReq } = useLazyAsyncData(
197
+ "get-by-site-as-service-provider",
198
+ () => getBySiteAsServiceProvider(useRoute().params.site as string)
188
199
  );
189
200
 
190
201
  watchEffect(() => {
191
202
  if (getAllReq.value) {
192
- serviceProviders.value = getAllReq.value.items.map((i: any) => ({
203
+ serviceProviders.value = getAllReq.value.map((i: any) => ({
193
204
  title: i.nature.replace(/_/g, " "),
194
- value: i.serviceProviderOrgId,
195
- subtitle: i.name,
205
+ subtitle: i.title,
206
+ value: i.nature,
196
207
  }));
197
208
  }
198
209
  });
@@ -244,11 +255,11 @@ const {
244
255
  data: getAllFeedbackReq,
245
256
  status: getAllReqStatus,
246
257
  refresh: getAllReqRefresh,
247
- } = useLazyAsyncData("get-all-feedbacks", () =>
258
+ } = useLazyAsyncData("get-all-feedbacks-" + props.category, () =>
248
259
  _getFeedbacks({
249
260
  page: page.value,
250
- organization: route.params.org as string,
251
261
  site: route.params.site as string,
262
+ category: props.category,
252
263
  })
253
264
  );
254
265
 
@@ -268,7 +279,6 @@ async function updatePage(pageVal: any) {
268
279
  page.value = pageVal;
269
280
  const response = await _getFeedbacks({
270
281
  page: page.value,
271
- organization: route.params.org as string,
272
282
  site: route.params.site as string,
273
283
  });
274
284
  if (response) {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="d-flex flex-column">
3
3
  <v-text-field v-bind="$attrs" ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly"
4
- placeholder="MM/DD/YYYY, HH:MM AM/PM" :rules="rules" style="z-index: 10" @click="openDatePicker">
4
+ :placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker">
5
5
  <template #append-inner>
6
6
  <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
7
7
  </template>
@@ -20,6 +20,10 @@ const prop = defineProps({
20
20
  type: Array as PropType<Array<any>>,
21
21
  default: () => []
22
22
  },
23
+ placeholder: {
24
+ type: String,
25
+ default: 'MM/DD/YYYY, HH:MM AM/PM'
26
+ }
23
27
  })
24
28
 
25
29
  const { formatDateISO8601 } = useUtils()
@@ -128,7 +128,7 @@ const props = defineProps<{
128
128
  attachments: string[];
129
129
  erroredImages?: string[];
130
130
  maxFiles?: number;
131
- createdFrom: string;
131
+ createdFrom?: string;
132
132
  }>();
133
133
 
134
134
  const emit = defineEmits<{
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <v-row no-gutters class="w-100 pb-5" @click="resetErrorMessage">
3
3
  <v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
4
- :loading="processing" :disabled="processing" :height="height" title="Upload Images" accept="image/*"
4
+ :loading="processing" :disabled="processing" :height="height" :title="title" :accept="accept"
5
5
  name="upload_images" class="text-caption w-100" clearable :multiple="multiple">
6
6
  <template v-slot:item="{ props: itemProps, file }">
7
7
  <v-file-upload-item v-bind="itemProps" lines="one" nav>
@@ -22,93 +22,141 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
-
26
- const prop = defineProps({
27
- height: {
28
- type: Number || String,
29
- default: 68
30
- },
31
- multiple: {
32
- type: Boolean,
33
- default: false
34
- },
35
- maxLength: {
36
- type: Number,
37
- default: 10
38
- }
39
-
40
-
25
+ import { nextTick, ref, onMounted, watch } from 'vue'
26
+
27
+ const props = defineProps({
28
+ height: {
29
+ type: [Number, String],
30
+ default: 68,
31
+ },
32
+ multiple: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
36
+ maxLength: {
37
+ type: Number,
38
+ default: 10,
39
+ },
40
+ title: {
41
+ type: String,
42
+ default: 'Upload Images'
43
+ },
44
+ accept: {
45
+ type: String,
46
+ default: "image/*"
47
+ }
41
48
  })
42
49
 
43
50
  const { addFile, deleteFile, getFileUrl, urlToFile } = useFile()
44
51
 
52
+ // The parent v-model binding
53
+ const idsArray = defineModel<string[]>({ default: [] })
54
+
45
55
  const uploadFiles = ref<File[]>([])
46
- const filesCollection = defineModel<{ file: File, id: string }[]>({required: true, default: []}) // files collection array
47
- const showCameraDialog = ref(false)
48
- const errorMessage = ref('');
56
+ const filesCollection = ref<{ file: File; id: string }[]>([])
57
+ const errorMessage = ref('')
49
58
  const processing = ref(false)
50
59
 
51
- const video = ref<HTMLVideoElement>()
52
- const canvas = ref<HTMLCanvasElement>()
53
- const cameraFacingMode = ref('environment')
54
-
60
+ function fileKey(f: File) {
61
+ return `${f.name}_${f.size}_${f.lastModified}`
62
+ }
55
63
 
56
64
  async function handleRemove(removedFile: File) {
57
- const fileKey = (f: File) => `${f.name}_${f.size}_${f.lastModified}`
58
- const arr = filesCollection.value
59
- const fileId = arr.find(item => fileKey(item.file) === fileKey(removedFile))?.id
65
+ const key = fileKey(removedFile)
66
+ const arr = [...filesCollection.value]
60
67
 
61
- uploadFiles.value = uploadFiles.value.filter(f => fileKey(f) !== fileKey(removedFile))
62
- filesCollection.value = arr.filter(item => item.id !== fileId)
68
+ const removedItem = arr.find((item) => fileKey(item.file) === key)
69
+ if (!removedItem) return
70
+
71
+ filesCollection.value = arr.filter((item) => item.id !== removedItem.id)
72
+ uploadFiles.value = uploadFiles.value.filter((f) => fileKey(f) !== key)
73
+
74
+
75
+ idsArray.value = filesCollection.value.map((x) => x.id)
76
+
77
+ try {
78
+ await deleteFile(removedItem.id)
79
+ } catch (err) {
80
+ console.warn('Failed to delete file', err)
81
+ }
63
82
  }
64
83
 
65
84
  async function handleUpdateValue(value: File[]) {
66
- await nextTick()
67
- const max = prop.maxLength
68
- const existingLength = filesCollection.value.length
69
- errorMessage.value = ''
70
- if ((existingLength + value.length) > max) {
71
- value = value.slice(0, (max - existingLength))
72
- errorMessage.value = `Max value of allowed image is ${max}`
73
- }
74
- uploadFiles.value = []
75
- processing.value = true
76
- // Determine which files are newly added or removed
77
- const fileKey = (f: File) => `${f.name}_${f.size}_${f.lastModified}`
85
+ await nextTick()
86
+ const max = props.maxLength
87
+ const existingLength = filesCollection.value.length
88
+ errorMessage.value = ''
78
89
 
79
- const arr = filesCollection.value
80
- const collectionKeys = arr.map(x => fileKey(x.file))
81
- const addedFiles = value.filter(f => !collectionKeys.includes(fileKey(f)))
90
+ if (existingLength + value.length > max) {
91
+ value = value.slice(0, max - existingLength)
92
+ errorMessage.value = `Max allowed images is ${max}`
93
+ }
82
94
 
83
- // Upload new files
84
- processing.value = true
95
+ const collectionKeys = filesCollection.value.map((x) => fileKey(x.file))
96
+ const addedFiles = value.filter((f) => !collectionKeys.includes(fileKey(f)))
97
+
98
+ processing.value = true
99
+ try {
85
100
  for (const file of addedFiles) {
86
- try {
87
- const res = await addFile(file) // expected to return { id, url } or similar
88
- if (res?.id) {
89
- filesCollection.value.push({ file, id: res?.id })
90
- }
91
- } catch (err) {
92
- console.error("Upload failed", err)
93
- errorMessage.value = `Failed to upload ${file.name}`
94
- } finally {
95
- processing.value = false
96
- uploadFiles.value = filesCollection.value.map(x => x.file)
97
- }
101
+ const res = await addFile(file) // should return { id, url }
102
+ if (res?.id) {
103
+ filesCollection.value.push({ file, id: res.id })
104
+ }
98
105
  }
99
106
 
107
+ uploadFiles.value = filesCollection.value.map((x) => x.file)
108
+ idsArray.value = filesCollection.value.map((x) => x.id)
109
+ } catch (err) {
110
+ console.error('Upload failed', err)
111
+ errorMessage.value = 'Failed to upload some files.'
112
+ } finally {
113
+ processing.value = false
114
+ }
115
+ }
116
+
117
+
118
+ async function loadFilesFromIds(ids: string[]) {
119
+ const result: { file: File; id: string }[] = []
120
+ for (const id of ids) {
121
+ try {
122
+ const url = await getFileUrl(id)
123
+ const name = decodeURIComponent(url.split('/').pop() || `file_${id}`)
124
+ const file = await urlToFile(url, name)
125
+ result.push({ file, id })
126
+ } catch (err) {
127
+ console.warn('Failed to load file from ID:', id, err)
128
+ }
129
+ }
130
+ return result
100
131
  }
101
132
 
102
- function resetErrorMessage(){
103
- errorMessage.value = '';
104
-
133
+
134
+ function resetErrorMessage() {
135
+ errorMessage.value = ''
105
136
  }
106
137
 
107
- onMounted(() => {
108
- filesCollection.value = []
138
+
139
+ onMounted(async () => {
140
+ if (idsArray.value.length > 0) {
141
+ processing.value = true
142
+ const loaded = await loadFilesFromIds(idsArray.value)
143
+ filesCollection.value = loaded
144
+ uploadFiles.value = loaded.map((x) => x.file)
145
+ processing.value = false
146
+ }
109
147
  })
148
+
149
+
150
+ watch(
151
+ filesCollection,
152
+ (newVal) => {
153
+ idsArray.value = newVal.map((x) => x.id)
154
+ },
155
+ { deep: true }
156
+ )
110
157
  </script>
111
158
 
159
+
112
160
  <style scoped>
113
161
  * :deep(.v-file-upload-title) {
114
162
  font-size: 1rem;