@7365admin1/layer-common 1.10.6 → 1.10.8
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.
- package/CHANGELOG.md +12 -0
- package/components/AccessCardQrTagging.vue +314 -34
- package/components/AccessCardQrTaggingPrintQr.vue +75 -0
- package/components/{AddSupplyForm.vue → AddEqupmentForm.vue} +5 -5
- package/components/AreaChecklistHistoryLogs.vue +9 -0
- package/components/BuildingForm.vue +36 -5
- package/components/BuildingManagement/buildings.vue +18 -9
- package/components/BuildingManagement/units.vue +13 -115
- package/components/BuildingUnitFormAdd.vue +42 -33
- package/components/BuildingUnitFormEdit.vue +334 -139
- package/components/CleaningScheduleMain.vue +60 -13
- package/components/Dialog/DeleteConfirmation.vue +2 -2
- package/components/Dialog/UpdateMoreAction.vue +2 -2
- package/components/EntryPassInformation.vue +443 -0
- package/components/{CheckoutItemMain.vue → EquipmentItemMain.vue} +88 -85
- package/components/{SupplyManagement.vue → EquipmentManagement.vue} +3 -3
- package/components/Input/DateTimePicker.vue +17 -11
- package/components/Input/InputPhoneNumberV2.vue +8 -0
- package/components/ManageChecklistMain.vue +400 -36
- package/components/ScheduleAreaMain.vue +56 -0
- package/components/TableHygiene.vue +47 -430
- package/components/UnitPersonCard.vue +123 -0
- package/components/VehicleAddSelection.vue +2 -2
- package/components/VehicleForm.vue +78 -19
- package/components/VehicleManagement.vue +164 -40
- package/components/VisitorForm.vue +95 -20
- package/components/VisitorFormSelection.vue +13 -2
- package/components/VisitorManagement.vue +83 -55
- package/composables/useAccessManagement.ts +52 -0
- package/composables/useCleaningPermission.ts +7 -7
- package/composables/useDashboardData.ts +2 -2
- package/composables/{useSupply.ts → useEquipment.ts} +11 -11
- package/composables/{useCheckout.ts → useEquipmentItem.ts} +7 -7
- package/composables/{useCheckoutPermission.ts → useEquipmentItemPermission.ts} +13 -13
- package/composables/useEquipmentManagementPermission.ts +96 -0
- package/composables/{useSupplyPermission.ts → useEquipmentPermission.ts} +9 -9
- package/composables/usePeople.ts +4 -3
- package/composables/useVehicle.ts +35 -2
- package/composables/useVisitor.ts +3 -3
- package/composables/useWorkOrder.ts +25 -3
- package/package.json +3 -2
- package/types/building.d.ts +1 -1
- package/types/cleaner-schedule.d.ts +1 -0
- package/types/{checkout-item.d.ts → equipment-item.d.ts} +3 -3
- package/types/{supply.d.ts → equipment.d.ts} +2 -2
- package/types/html2pdf.d.ts +19 -0
- package/types/people.d.ts +5 -2
- package/types/site.d.ts +8 -0
- package/types/vehicle.d.ts +4 -3
- package/types/visitor.d.ts +2 -1
- package/.playground/app.vue +0 -41
- package/.playground/eslint.config.mjs +0 -6
- package/.playground/nuxt.config.ts +0 -22
- package/.playground/pages/feedback.vue +0 -30
|
@@ -95,45 +95,43 @@
|
|
|
95
95
|
Set {{ group.set }}
|
|
96
96
|
</span>
|
|
97
97
|
<v-chip
|
|
98
|
-
v-if="group.
|
|
98
|
+
v-if="group.isScheduleTask"
|
|
99
99
|
size="x-small"
|
|
100
|
-
color="
|
|
100
|
+
color="info"
|
|
101
101
|
variant="tonal"
|
|
102
|
-
prepend-icon="mdi-
|
|
102
|
+
prepend-icon="mdi-calendar-clock"
|
|
103
103
|
class="text-none"
|
|
104
104
|
>
|
|
105
|
-
|
|
106
|
-
</v-chip>
|
|
107
|
-
<v-chip
|
|
108
|
-
v-else-if="
|
|
109
|
-
group.completedByName && isGroupInProgress(group)
|
|
110
|
-
"
|
|
111
|
-
size="x-small"
|
|
112
|
-
color="warning"
|
|
113
|
-
variant="tonal"
|
|
114
|
-
prepend-icon="mdi-progress-clock"
|
|
115
|
-
class="text-none"
|
|
116
|
-
>
|
|
117
|
-
Ongoing · {{ group.completedByName }}
|
|
105
|
+
Schedule Task
|
|
118
106
|
</v-chip>
|
|
107
|
+
<slot name="group-header-chips" :group="group">
|
|
108
|
+
<v-chip
|
|
109
|
+
v-if="group.completedByName && isGroupComplete(group)"
|
|
110
|
+
size="x-small"
|
|
111
|
+
color="success"
|
|
112
|
+
variant="tonal"
|
|
113
|
+
prepend-icon="mdi-check-circle-outline"
|
|
114
|
+
class="text-none"
|
|
115
|
+
>
|
|
116
|
+
Completed · {{ group.completedByName }}
|
|
117
|
+
</v-chip>
|
|
118
|
+
<v-chip
|
|
119
|
+
v-else-if="
|
|
120
|
+
group.completedByName && isGroupInProgress(group)
|
|
121
|
+
"
|
|
122
|
+
size="x-small"
|
|
123
|
+
color="warning"
|
|
124
|
+
variant="tonal"
|
|
125
|
+
prepend-icon="mdi-progress-clock"
|
|
126
|
+
class="text-none"
|
|
127
|
+
>
|
|
128
|
+
Ongoing · {{ group.completedByName }}
|
|
129
|
+
</v-chip>
|
|
130
|
+
</slot>
|
|
119
131
|
</v-col>
|
|
120
132
|
<v-spacer />
|
|
121
133
|
<v-col cols="auto">
|
|
122
|
-
<
|
|
123
|
-
v-if="group.attachments && group.attachments.length > 0"
|
|
124
|
-
size="x-small"
|
|
125
|
-
variant="tonal"
|
|
126
|
-
color="primary"
|
|
127
|
-
class="text-none"
|
|
128
|
-
prepend-icon="mdi-paperclip"
|
|
129
|
-
@click.stop="
|
|
130
|
-
openAttachmentDialog(group.set, group.attachments)
|
|
131
|
-
"
|
|
132
|
-
>
|
|
133
|
-
{{ group.attachments.length }} attachment{{
|
|
134
|
-
group.attachments.length > 1 ? "s" : ""
|
|
135
|
-
}}
|
|
136
|
-
</v-btn>
|
|
134
|
+
<slot name="group-header-append" :group="group" />
|
|
137
135
|
</v-col>
|
|
138
136
|
</v-row>
|
|
139
137
|
</v-sheet>
|
|
@@ -141,45 +139,20 @@
|
|
|
141
139
|
<v-sheet
|
|
142
140
|
v-for="item in group.items"
|
|
143
141
|
:key="item[itemValue]"
|
|
144
|
-
|
|
145
|
-
isItemSelected(item, group.set) ? 'grey-lighten-4' : 'white'
|
|
146
|
-
"
|
|
142
|
+
color="white"
|
|
147
143
|
border="b"
|
|
148
144
|
>
|
|
149
145
|
<v-row no-gutters align="center" class="px-4 py-2">
|
|
150
146
|
<v-col cols="auto" class="mr-3">
|
|
151
|
-
<
|
|
152
|
-
size="20"
|
|
153
|
-
:color="
|
|
154
|
-
activeActions[getKey(item, group.set)] === 'approve'
|
|
155
|
-
? 'success'
|
|
156
|
-
: activeActions[getKey(item, group.set)] === 'reject'
|
|
157
|
-
? 'error'
|
|
158
|
-
: 'grey-lighten-2'
|
|
159
|
-
"
|
|
160
|
-
>
|
|
161
|
-
{{
|
|
162
|
-
activeActions[getKey(item, group.set)] === "approve"
|
|
163
|
-
? "mdi-check-circle"
|
|
164
|
-
: activeActions[getKey(item, group.set)] === "reject"
|
|
165
|
-
? "mdi-close-circle"
|
|
166
|
-
: "mdi-circle-outline"
|
|
167
|
-
}}
|
|
168
|
-
</v-icon>
|
|
147
|
+
<slot name="item-prepend" :item="item" :group="group" />
|
|
169
148
|
</v-col>
|
|
149
|
+
|
|
170
150
|
<v-col>
|
|
171
|
-
<slot name="
|
|
172
|
-
<v-row no-gutters
|
|
151
|
+
<slot name="item-content" :item="item" :group="group">
|
|
152
|
+
<v-row no-gutters>
|
|
173
153
|
<v-col cols="12">
|
|
174
|
-
<span
|
|
175
|
-
|
|
176
|
-
:class="
|
|
177
|
-
activeActions[getKey(item, group.set)] === 'approve'
|
|
178
|
-
? 'text-decoration-line-through text-medium-emphasis'
|
|
179
|
-
: ''
|
|
180
|
-
"
|
|
181
|
-
>
|
|
182
|
-
{{ getItemValue(item, headers[0].value) }}
|
|
154
|
+
<span class="text-body-2 font-weight-medium">
|
|
155
|
+
{{ getItemValue(item, headers[0]?.value) }}
|
|
183
156
|
</span>
|
|
184
157
|
</v-col>
|
|
185
158
|
<v-col
|
|
@@ -229,48 +202,7 @@
|
|
|
229
202
|
</v-col>
|
|
230
203
|
|
|
231
204
|
<v-col cols="auto">
|
|
232
|
-
<slot
|
|
233
|
-
name="list-item-append"
|
|
234
|
-
:item="item"
|
|
235
|
-
:isSelected="isItemSelected(item, group.set)"
|
|
236
|
-
>
|
|
237
|
-
<v-row
|
|
238
|
-
v-if="canManageScheduleTasks"
|
|
239
|
-
no-gutters
|
|
240
|
-
align="center"
|
|
241
|
-
>
|
|
242
|
-
<v-col cols="auto">
|
|
243
|
-
<v-btn
|
|
244
|
-
icon="mdi-close"
|
|
245
|
-
size="small"
|
|
246
|
-
:variant="
|
|
247
|
-
activeActions[getKey(item, group.set)] === 'reject'
|
|
248
|
-
? 'flat'
|
|
249
|
-
: 'text'
|
|
250
|
-
"
|
|
251
|
-
color="error"
|
|
252
|
-
@click.stop="
|
|
253
|
-
handleActionClick(item, group.set, 'reject')
|
|
254
|
-
"
|
|
255
|
-
/>
|
|
256
|
-
</v-col>
|
|
257
|
-
<v-col cols="auto">
|
|
258
|
-
<v-btn
|
|
259
|
-
icon="mdi-check"
|
|
260
|
-
size="small"
|
|
261
|
-
:variant="
|
|
262
|
-
activeActions[getKey(item, group.set)] === 'approve'
|
|
263
|
-
? 'flat'
|
|
264
|
-
: 'text'
|
|
265
|
-
"
|
|
266
|
-
color="success"
|
|
267
|
-
@click.stop="
|
|
268
|
-
handleActionClick(item, group.set, 'approve')
|
|
269
|
-
"
|
|
270
|
-
/>
|
|
271
|
-
</v-col>
|
|
272
|
-
</v-row>
|
|
273
|
-
</slot>
|
|
205
|
+
<slot name="item-append" :item="item" :group="group" />
|
|
274
206
|
</v-col>
|
|
275
207
|
</v-row>
|
|
276
208
|
</v-sheet>
|
|
@@ -281,81 +213,6 @@
|
|
|
281
213
|
</v-card>
|
|
282
214
|
</v-col>
|
|
283
215
|
</v-row>
|
|
284
|
-
|
|
285
|
-
<v-dialog v-model="showAttachmentDialog" max-width="700" scrollable>
|
|
286
|
-
<v-card>
|
|
287
|
-
<v-card-title class="d-flex align-center pa-4">
|
|
288
|
-
<span class="text-h6 font-weight-bold">
|
|
289
|
-
Set {{ attachmentDialogSet }} — Attachments
|
|
290
|
-
</span>
|
|
291
|
-
<v-spacer />
|
|
292
|
-
<v-btn
|
|
293
|
-
icon="mdi-close"
|
|
294
|
-
variant="text"
|
|
295
|
-
size="small"
|
|
296
|
-
@click="showAttachmentDialog = false"
|
|
297
|
-
/>
|
|
298
|
-
</v-card-title>
|
|
299
|
-
|
|
300
|
-
<v-divider />
|
|
301
|
-
|
|
302
|
-
<v-card-text class="pa-4">
|
|
303
|
-
<v-row>
|
|
304
|
-
<v-col
|
|
305
|
-
v-for="(id, index) in attachmentDialogIds"
|
|
306
|
-
:key="index"
|
|
307
|
-
cols="6"
|
|
308
|
-
sm="4"
|
|
309
|
-
>
|
|
310
|
-
<v-sheet
|
|
311
|
-
rounded="lg"
|
|
312
|
-
class="overflow-hidden"
|
|
313
|
-
style="aspect-ratio: 1"
|
|
314
|
-
>
|
|
315
|
-
<v-img
|
|
316
|
-
:src="getFileUrl(id)"
|
|
317
|
-
aspect-ratio="1"
|
|
318
|
-
cover
|
|
319
|
-
class="rounded-lg"
|
|
320
|
-
@click="openFullImage(getFileUrl(id))"
|
|
321
|
-
style="cursor: zoom-in"
|
|
322
|
-
>
|
|
323
|
-
<template v-slot:placeholder>
|
|
324
|
-
<v-row
|
|
325
|
-
class="fill-height ma-0"
|
|
326
|
-
align="center"
|
|
327
|
-
justify="center"
|
|
328
|
-
>
|
|
329
|
-
<v-progress-circular indeterminate color="grey-lighten-4" />
|
|
330
|
-
</v-row>
|
|
331
|
-
</template>
|
|
332
|
-
<template v-slot:error>
|
|
333
|
-
<v-row
|
|
334
|
-
class="fill-height ma-0"
|
|
335
|
-
align="center"
|
|
336
|
-
justify="center"
|
|
337
|
-
>
|
|
338
|
-
<v-icon icon="mdi-image-broken" size="40" color="grey" />
|
|
339
|
-
</v-row>
|
|
340
|
-
</template>
|
|
341
|
-
</v-img>
|
|
342
|
-
</v-sheet>
|
|
343
|
-
</v-col>
|
|
344
|
-
</v-row>
|
|
345
|
-
</v-card-text>
|
|
346
|
-
</v-card>
|
|
347
|
-
</v-dialog>
|
|
348
|
-
|
|
349
|
-
<v-dialog v-model="showLightbox" max-width="900">
|
|
350
|
-
<v-card>
|
|
351
|
-
<v-card-actions class="pa-2 justify-end">
|
|
352
|
-
<v-btn icon="mdi-close" variant="text" @click="showLightbox = false" />
|
|
353
|
-
</v-card-actions>
|
|
354
|
-
<v-card-text class="pa-2 pt-0">
|
|
355
|
-
<v-img :src="lightboxSrc" contain max-height="80vh" />
|
|
356
|
-
</v-card-text>
|
|
357
|
-
</v-card>
|
|
358
|
-
</v-dialog>
|
|
359
216
|
</template>
|
|
360
217
|
|
|
361
218
|
<script lang="ts" setup>
|
|
@@ -414,14 +271,6 @@ const props = defineProps({
|
|
|
414
271
|
type: Boolean,
|
|
415
272
|
default: false,
|
|
416
273
|
},
|
|
417
|
-
canManageScheduleTasks: {
|
|
418
|
-
type: Boolean,
|
|
419
|
-
default: true,
|
|
420
|
-
},
|
|
421
|
-
canAddRemarks: {
|
|
422
|
-
type: Boolean,
|
|
423
|
-
default: true,
|
|
424
|
-
},
|
|
425
274
|
extensionHeight: {
|
|
426
275
|
type: Number,
|
|
427
276
|
default: 50,
|
|
@@ -430,73 +279,33 @@ const props = defineProps({
|
|
|
430
279
|
type: Number,
|
|
431
280
|
default: 200,
|
|
432
281
|
},
|
|
433
|
-
selected: {
|
|
434
|
-
type: Array as PropType<any[]>,
|
|
435
|
-
default: () => [],
|
|
436
|
-
},
|
|
437
282
|
});
|
|
438
283
|
|
|
439
|
-
const emits = defineEmits([
|
|
440
|
-
"create",
|
|
441
|
-
"refresh",
|
|
442
|
-
"update:page",
|
|
443
|
-
"row-click",
|
|
444
|
-
"update:selected",
|
|
445
|
-
"action-click",
|
|
446
|
-
"request-completion-dialog",
|
|
447
|
-
]);
|
|
448
|
-
|
|
449
|
-
defineExpose({
|
|
450
|
-
revertSetApprovals,
|
|
451
|
-
});
|
|
284
|
+
const emits = defineEmits(["create", "refresh", "update:page"]);
|
|
452
285
|
|
|
453
286
|
const internalPage = ref(props.page);
|
|
454
|
-
const selected = shallowRef<any[]>(props.selected);
|
|
455
|
-
const activeActions = reactive<Record<string, "approve" | "reject">>({});
|
|
456
|
-
const persistedActions = reactive<Record<string, "approve" | "reject">>({});
|
|
457
|
-
const completedSets = ref<Set<number>>(new Set());
|
|
458
287
|
const itemOrderMap = new Map<string, number>();
|
|
459
288
|
|
|
460
|
-
const showAttachmentDialog = ref(false);
|
|
461
|
-
const attachmentDialogSet = ref<number | undefined>(undefined);
|
|
462
|
-
const attachmentDialogIds = ref<string[]>([]);
|
|
463
|
-
const showLightbox = ref(false);
|
|
464
|
-
const lightboxSrc = ref("");
|
|
465
|
-
|
|
466
|
-
const { getFileUrl } = useFile();
|
|
467
|
-
|
|
468
|
-
function openAttachmentDialog(setNumber: number, attachments: string[]) {
|
|
469
|
-
attachmentDialogSet.value = setNumber;
|
|
470
|
-
attachmentDialogIds.value = attachments;
|
|
471
|
-
showAttachmentDialog.value = true;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function openFullImage(src: string) {
|
|
475
|
-
lightboxSrc.value = src;
|
|
476
|
-
showLightbox.value = true;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
289
|
const groupedItems = computed(() => {
|
|
480
290
|
return props.items.map((item: any) => {
|
|
481
291
|
const units = item.units || [item];
|
|
482
292
|
|
|
483
|
-
units.forEach((unit: any
|
|
484
|
-
const key =
|
|
293
|
+
units.forEach((unit: any) => {
|
|
294
|
+
const key = `${unit[props.itemValue]}_${item.set}`;
|
|
485
295
|
if (!itemOrderMap.has(key)) {
|
|
486
296
|
itemOrderMap.set(key, itemOrderMap.size);
|
|
487
297
|
}
|
|
488
298
|
});
|
|
489
299
|
|
|
490
300
|
const sortedUnits = [...units].sort((a: any, b: any) => {
|
|
491
|
-
const keyA =
|
|
492
|
-
const keyB =
|
|
493
|
-
|
|
494
|
-
const orderB = itemOrderMap.get(keyB) ?? 0;
|
|
495
|
-
return orderA - orderB;
|
|
301
|
+
const keyA = `${a[props.itemValue]}_${item.set}`;
|
|
302
|
+
const keyB = `${b[props.itemValue]}_${item.set}`;
|
|
303
|
+
return (itemOrderMap.get(keyA) ?? 0) - (itemOrderMap.get(keyB) ?? 0);
|
|
496
304
|
});
|
|
497
305
|
|
|
498
306
|
return {
|
|
499
307
|
set: item.set,
|
|
308
|
+
isScheduleTask: item.isScheduleTask ?? false,
|
|
500
309
|
completedByName: item.completedByName ?? null,
|
|
501
310
|
attachments:
|
|
502
311
|
(item.attachment as string[] | undefined) ??
|
|
@@ -507,27 +316,6 @@ const groupedItems = computed(() => {
|
|
|
507
316
|
});
|
|
508
317
|
});
|
|
509
318
|
|
|
510
|
-
const totalItemsCount = computed(() => {
|
|
511
|
-
return groupedItems.value.reduce((acc, group) => acc + group.items.length, 0);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const approvedItemsCount = computed(() => {
|
|
515
|
-
return Object.values(activeActions).filter((action) => action === "approve")
|
|
516
|
-
.length;
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
const hasRejectedItems = computed(() => {
|
|
520
|
-
return Object.values(activeActions).some((action) => action === "reject");
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const allItemsApproved = computed(() => {
|
|
524
|
-
return (
|
|
525
|
-
totalItemsCount.value > 0 &&
|
|
526
|
-
approvedItemsCount.value === totalItemsCount.value &&
|
|
527
|
-
!hasRejectedItems.value
|
|
528
|
-
);
|
|
529
|
-
});
|
|
530
|
-
|
|
531
319
|
function formatTimestamp(ts: string): string {
|
|
532
320
|
if (!ts) return "";
|
|
533
321
|
const date = new Date(ts);
|
|
@@ -556,187 +344,16 @@ function isGroupInProgress(group: { items: any[] }): boolean {
|
|
|
556
344
|
);
|
|
557
345
|
}
|
|
558
346
|
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return group.items.every((item: any) => {
|
|
564
|
-
const key = getKey(item, setNumber);
|
|
565
|
-
return activeActions[key] === "approve";
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function getNewApprovedItemsForSet(
|
|
570
|
-
setNumber: number
|
|
571
|
-
): Array<{ key: string; item: any; action: "approve" }> {
|
|
572
|
-
const group = groupedItems.value.find((g) => g.set === setNumber);
|
|
573
|
-
if (!group) return [];
|
|
574
|
-
|
|
575
|
-
const approvedItems: Array<{ key: string; item: any; action: "approve" }> =
|
|
576
|
-
[];
|
|
577
|
-
|
|
578
|
-
group.items.forEach((item: any) => {
|
|
579
|
-
const key = getKey(item, setNumber);
|
|
580
|
-
if (activeActions[key] === "approve" && !(key in persistedActions)) {
|
|
581
|
-
approvedItems.push({
|
|
582
|
-
key,
|
|
583
|
-
item: { ...item, set: setNumber },
|
|
584
|
-
action: "approve",
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
return approvedItems;
|
|
347
|
+
function getItemValue(item: any, key: string): string {
|
|
348
|
+
if (!key) return "";
|
|
349
|
+
return key.split(".").reduce((obj: any, k: string) => obj?.[k], item) ?? "";
|
|
590
350
|
}
|
|
591
351
|
|
|
592
|
-
const lastApprovedKey = ref<string | null>(null);
|
|
593
|
-
|
|
594
352
|
watch(
|
|
595
353
|
() => props.page,
|
|
596
354
|
(val) => {
|
|
597
355
|
internalPage.value = val;
|
|
598
|
-
|
|
599
356
|
itemOrderMap.clear();
|
|
600
357
|
}
|
|
601
358
|
);
|
|
602
|
-
|
|
603
|
-
watch(
|
|
604
|
-
() => props.selected,
|
|
605
|
-
(val) => {
|
|
606
|
-
selected.value = val;
|
|
607
|
-
}
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
watch(selected, (val) => {
|
|
611
|
-
emits("update:selected", val);
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
watch(
|
|
615
|
-
() => props.items,
|
|
616
|
-
(items) => {
|
|
617
|
-
if (!items || !Array.isArray(items)) return;
|
|
618
|
-
|
|
619
|
-
Object.keys(persistedActions).forEach(
|
|
620
|
-
(key) => delete persistedActions[key]
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
items.forEach((group: any) => {
|
|
624
|
-
const set = group.set;
|
|
625
|
-
const units = group.units || [];
|
|
626
|
-
|
|
627
|
-
units.forEach((unit: any) => {
|
|
628
|
-
const key = getKey(unit, set);
|
|
629
|
-
if (unit.approve === true) {
|
|
630
|
-
activeActions[key] = "approve";
|
|
631
|
-
persistedActions[key] = "approve";
|
|
632
|
-
} else if (unit.reject === true) {
|
|
633
|
-
activeActions[key] = "reject";
|
|
634
|
-
persistedActions[key] = "reject";
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
},
|
|
639
|
-
{ immediate: true }
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
function getKey(item: any, set?: number): string {
|
|
643
|
-
return `${item[props.itemValue]}_${set ?? ""}`;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function getItemValue(item: any, key: string): string {
|
|
647
|
-
if (!key) return "";
|
|
648
|
-
return key.split(".").reduce((obj, k) => obj?.[k], item) ?? "";
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
function isItemSelected(item: any, set?: number): boolean {
|
|
652
|
-
if (!Array.isArray(selected.value) || selected.value.length === 0) {
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (typeof selected.value[0] === "object" && "unit" in selected.value[0]) {
|
|
657
|
-
return selected.value.some(
|
|
658
|
-
(s: any) => s.unit === item[props.itemValue] && s.set === set
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
return selected.value.includes(item[props.itemValue]);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function handleActionClick(
|
|
666
|
-
item: any,
|
|
667
|
-
set: number | undefined,
|
|
668
|
-
action: "approve" | "reject"
|
|
669
|
-
): void {
|
|
670
|
-
const key = getKey(item, set);
|
|
671
|
-
|
|
672
|
-
const isPersisted = key in persistedActions;
|
|
673
|
-
|
|
674
|
-
if (activeActions[key] === action && !isPersisted) {
|
|
675
|
-
delete activeActions[key];
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
activeActions[key] = action;
|
|
680
|
-
|
|
681
|
-
if (action === "reject") {
|
|
682
|
-
console.debug("TableHygiene: emitting action-click", {
|
|
683
|
-
item: { ...item, set },
|
|
684
|
-
action,
|
|
685
|
-
});
|
|
686
|
-
emits("action-click", { item: { ...item, set }, action });
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (action === "approve") {
|
|
690
|
-
lastApprovedKey.value = key;
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
set !== undefined &&
|
|
694
|
-
isSetFullyApproved(set) &&
|
|
695
|
-
!completedSets.value.has(set)
|
|
696
|
-
) {
|
|
697
|
-
const newApprovedItems = getNewApprovedItemsForSet(set);
|
|
698
|
-
|
|
699
|
-
if (newApprovedItems.length > 0) {
|
|
700
|
-
// Mark this set as completed
|
|
701
|
-
completedSets.value.add(set);
|
|
702
|
-
|
|
703
|
-
// Emit request to open completion dialog
|
|
704
|
-
emits("request-completion-dialog", {
|
|
705
|
-
setNumber: set,
|
|
706
|
-
approvedItems: newApprovedItems,
|
|
707
|
-
lastApprovedKey: lastApprovedKey.value,
|
|
708
|
-
});
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
console.debug("TableHygiene: emitting action-click (approve immediate)", {
|
|
713
|
-
item: { ...item, set },
|
|
714
|
-
action,
|
|
715
|
-
});
|
|
716
|
-
emits("action-click", { item: { ...item, set }, action });
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
function revertSetApprovals(setNumber: number): void {
|
|
721
|
-
const group = groupedItems.value.find((g) => g.set === setNumber);
|
|
722
|
-
if (group) {
|
|
723
|
-
group.items.forEach((item: any) => {
|
|
724
|
-
const key = getKey(item, setNumber);
|
|
725
|
-
|
|
726
|
-
if (!(key in persistedActions)) {
|
|
727
|
-
delete activeActions[key];
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
completedSets.value.delete(setNumber);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
watch(
|
|
736
|
-
() => props.items,
|
|
737
|
-
() => {
|
|
738
|
-
completedSets.value.clear();
|
|
739
|
-
},
|
|
740
|
-
{ deep: true }
|
|
741
|
-
);
|
|
742
359
|
</script>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card-text>
|
|
3
|
+
<v-row>
|
|
4
|
+
<v-col cols="12" md="4" class="mt-2">
|
|
5
|
+
<InputLabel class="font-weight-bold" title="Name" />
|
|
6
|
+
<v-text-field :model-value="person.name" density="compact" :readonly="readOnly"
|
|
7
|
+
:class="{ 'no-pointer': readOnly }" />
|
|
8
|
+
</v-col>
|
|
9
|
+
|
|
10
|
+
<v-col cols="12" md="4" class="mt-2">
|
|
11
|
+
<InputLabel class="font-weight-bold" title="Email Address" />
|
|
12
|
+
<v-text-field :model-value="person.email" density="compact" :readonly="readOnly"
|
|
13
|
+
:class="{ 'no-pointer': readOnly }" />
|
|
14
|
+
</v-col>
|
|
15
|
+
|
|
16
|
+
<v-col cols="12" md="4" class="mt-2">
|
|
17
|
+
<InputLabel class="font-weight-bold" title="NRIC" />
|
|
18
|
+
<v-text-field :model-value="person.nric" density="compact" :readonly="readOnly"
|
|
19
|
+
:class="{ 'no-pointer': readOnly }" />
|
|
20
|
+
</v-col>
|
|
21
|
+
|
|
22
|
+
<v-col cols="12" md="4" class="mt-2">
|
|
23
|
+
<InputLabel class="font-weight-bold" title="Mobile Number" />
|
|
24
|
+
<v-text-field :model-value="person.contact" density="compact" :readonly="readOnly"
|
|
25
|
+
:class="{ 'no-pointer': readOnly }" />
|
|
26
|
+
</v-col>
|
|
27
|
+
</v-row>
|
|
28
|
+
|
|
29
|
+
<v-row>
|
|
30
|
+
<v-col cols="12" md="4" class="mt-2">
|
|
31
|
+
<InputLabel class="font-weight-bold" title="Other Documents and Files" :viewMode="readOnly" />
|
|
32
|
+
|
|
33
|
+
<p v-if="!person.files?.length" class="text-blue-darken-2 font-weight-thin text-caption ml-2 mt-5">
|
|
34
|
+
**No documents to display**
|
|
35
|
+
</p>
|
|
36
|
+
<v-list v-else dense nav class="mt-2">
|
|
37
|
+
<v-list-item v-for="file in person.files" :key="file.id" class="text-blue-darken-2"
|
|
38
|
+
:href="getFileUrl(file.id)" target="_blank">
|
|
39
|
+
<v-list-item-content>
|
|
40
|
+
<v-list-item-title class="d-flex align-center">
|
|
41
|
+
<span>
|
|
42
|
+
<v-icon size="18" class="mr-1">mdi-file-document-outline</v-icon>
|
|
43
|
+
</span>
|
|
44
|
+
<span> {{ file.name }}</span>
|
|
45
|
+
</v-list-item-title>
|
|
46
|
+
</v-list-item-content>
|
|
47
|
+
</v-list-item>
|
|
48
|
+
</v-list>
|
|
49
|
+
|
|
50
|
+
</v-col>
|
|
51
|
+
|
|
52
|
+
<v-col cols="0" md="4"></v-col>
|
|
53
|
+
<v-col v-if="prop.showOwnerOption" cols="12" md="4">
|
|
54
|
+
<v-checkbox :model-value="person.isOwner" readonly hide-details density="compact" @click="handleClick">
|
|
55
|
+
<template #label>
|
|
56
|
+
<span class="font-weight-bold text-subtitle-2 ml-2">Is Owner?</span>
|
|
57
|
+
</template>
|
|
58
|
+
</v-checkbox>
|
|
59
|
+
|
|
60
|
+
<span class="d-flex text-caption text-gray-5 ml-7">As owner, you will be receiving emails and updates
|
|
61
|
+
related to this unit.</span>
|
|
62
|
+
</v-col>
|
|
63
|
+
</v-row>
|
|
64
|
+
|
|
65
|
+
<v-dialog v-model="dialog.showPrompt" persistent width="540">
|
|
66
|
+
<DialogReusablePrompt :loading="loadingOwnerProcessing"
|
|
67
|
+
:prompt-title="`Are you sure want to ${person.isOwner ? 'remove' : 'add'} (${person.name}) as owner ?`"
|
|
68
|
+
@proceed="handleApprove(person)" @close="dialog.showPrompt = false" />
|
|
69
|
+
</v-dialog>
|
|
70
|
+
</v-card-text>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script setup lang="ts">
|
|
74
|
+
import useFile from '../composables/useFile';
|
|
75
|
+
|
|
76
|
+
const prop = defineProps({
|
|
77
|
+
person: {
|
|
78
|
+
type: Object as PropType<TPeople>,
|
|
79
|
+
required: true,
|
|
80
|
+
},
|
|
81
|
+
readOnly: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: false,
|
|
84
|
+
},
|
|
85
|
+
showOwnerOption: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
default: false,
|
|
88
|
+
},
|
|
89
|
+
loadingOwnerProcessing: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: false,
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const { getFileUrl } = useFile()
|
|
96
|
+
|
|
97
|
+
const emit = defineEmits(['update:owner'])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
const dialog = reactive({
|
|
101
|
+
showPrompt: false,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const handleApprove = (person: TPeople) => {
|
|
109
|
+
dialog.showPrompt = false;
|
|
110
|
+
emit('update:owner', { person, isOwner: !person.isOwner })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const handleClick = () => {
|
|
114
|
+
dialog.showPrompt = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<style lang="scss" scoped>
|
|
120
|
+
.no-pointer {
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
}
|
|
123
|
+
</style>
|