@7365admin1/layer-common 1.10.7 → 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.
Files changed (32) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/{AddSupplyForm.vue → AddEqupmentForm.vue} +5 -5
  3. package/components/BuildingManagement/units.vue +2 -2
  4. package/components/BuildingUnitFormAdd.vue +4 -4
  5. package/components/BuildingUnitFormEdit.vue +114 -68
  6. package/components/EntryPassInformation.vue +251 -23
  7. package/components/{CheckoutItemMain.vue → EquipmentItemMain.vue} +88 -85
  8. package/components/{SupplyManagement.vue → EquipmentManagement.vue} +3 -3
  9. package/components/Input/DateTimePicker.vue +17 -11
  10. package/components/ManageChecklistMain.vue +379 -41
  11. package/components/TableHygiene.vue +42 -452
  12. package/components/UnitPersonCard.vue +74 -14
  13. package/components/VisitorForm.vue +77 -21
  14. package/components/VisitorFormSelection.vue +13 -2
  15. package/components/VisitorManagement.vue +83 -55
  16. package/composables/useCleaningPermission.ts +7 -7
  17. package/composables/useDashboardData.ts +2 -2
  18. package/composables/{useSupply.ts → useEquipment.ts} +11 -11
  19. package/composables/{useCheckout.ts → useEquipmentItem.ts} +7 -7
  20. package/composables/{useCheckoutPermission.ts → useEquipmentItemPermission.ts} +13 -13
  21. package/composables/useEquipmentManagementPermission.ts +96 -0
  22. package/composables/{useSupplyPermission.ts → useEquipmentPermission.ts} +9 -9
  23. package/composables/useVehicle.ts +21 -2
  24. package/composables/useVisitor.ts +3 -3
  25. package/composables/useWorkOrder.ts +25 -3
  26. package/package.json +1 -1
  27. package/types/building.d.ts +1 -1
  28. package/types/{checkout-item.d.ts → equipment-item.d.ts} +3 -3
  29. package/types/{supply.d.ts → equipment.d.ts} +2 -2
  30. package/types/people.d.ts +3 -1
  31. package/types/vehicle.d.ts +2 -0
  32. package/types/visitor.d.ts +2 -1
@@ -104,46 +104,34 @@
104
104
  >
105
105
  Schedule Task
106
106
  </v-chip>
107
- <v-chip
108
- v-if="group.completedByName && isGroupComplete(group)"
109
- size="x-small"
110
- color="success"
111
- variant="tonal"
112
- prepend-icon="mdi-check-circle-outline"
113
- class="text-none"
114
- >
115
- Completed · {{ group.completedByName }}
116
- </v-chip>
117
- <v-chip
118
- v-else-if="
119
- group.completedByName && isGroupInProgress(group)
120
- "
121
- size="x-small"
122
- color="warning"
123
- variant="tonal"
124
- prepend-icon="mdi-progress-clock"
125
- class="text-none"
126
- >
127
- Ongoing · {{ group.completedByName }}
128
- </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>
129
131
  </v-col>
130
132
  <v-spacer />
131
133
  <v-col cols="auto">
132
- <v-btn
133
- v-if="group.attachments && group.attachments.length > 0"
134
- size="x-small"
135
- variant="tonal"
136
- color="primary"
137
- class="text-none"
138
- prepend-icon="mdi-paperclip"
139
- @click.stop="
140
- openAttachmentDialog(group.set, group.attachments)
141
- "
142
- >
143
- {{ group.attachments.length }} attachment{{
144
- group.attachments.length > 1 ? "s" : ""
145
- }}
146
- </v-btn>
134
+ <slot name="group-header-append" :group="group" />
147
135
  </v-col>
148
136
  </v-row>
149
137
  </v-sheet>
@@ -151,45 +139,20 @@
151
139
  <v-sheet
152
140
  v-for="item in group.items"
153
141
  :key="item[itemValue]"
154
- :color="
155
- isItemSelected(item, group.set) ? 'grey-lighten-4' : 'white'
156
- "
142
+ color="white"
157
143
  border="b"
158
144
  >
159
145
  <v-row no-gutters align="center" class="px-4 py-2">
160
146
  <v-col cols="auto" class="mr-3">
161
- <v-icon
162
- size="20"
163
- :color="
164
- activeActions[getKey(item, group.set)] === 'approve'
165
- ? 'success'
166
- : activeActions[getKey(item, group.set)] === 'reject'
167
- ? 'error'
168
- : 'grey-lighten-2'
169
- "
170
- >
171
- {{
172
- activeActions[getKey(item, group.set)] === "approve"
173
- ? "mdi-check-circle"
174
- : activeActions[getKey(item, group.set)] === "reject"
175
- ? "mdi-close-circle"
176
- : "mdi-circle-outline"
177
- }}
178
- </v-icon>
147
+ <slot name="item-prepend" :item="item" :group="group" />
179
148
  </v-col>
149
+
180
150
  <v-col>
181
- <slot name="list-item" :item="item">
182
- <v-row no-gutters align-center>
151
+ <slot name="item-content" :item="item" :group="group">
152
+ <v-row no-gutters>
183
153
  <v-col cols="12">
184
- <span
185
- class="text-body-2 font-weight-medium"
186
- :class="
187
- activeActions[getKey(item, group.set)] === 'approve'
188
- ? 'text-decoration-line-through text-medium-emphasis'
189
- : ''
190
- "
191
- >
192
- {{ getItemValue(item, headers[0].value) }}
154
+ <span class="text-body-2 font-weight-medium">
155
+ {{ getItemValue(item, headers[0]?.value) }}
193
156
  </span>
194
157
  </v-col>
195
158
  <v-col
@@ -239,50 +202,7 @@
239
202
  </v-col>
240
203
 
241
204
  <v-col cols="auto">
242
- <slot
243
- name="list-item-append"
244
- :item="item"
245
- :isSelected="isItemSelected(item, group.set)"
246
- >
247
- <v-row
248
- v-if="canManageScheduleTasks"
249
- no-gutters
250
- align="center"
251
- >
252
- <v-col cols="auto">
253
- <v-btn
254
- icon="mdi-close"
255
- size="small"
256
- :variant="
257
- activeActions[getKey(item, group.set)] === 'reject'
258
- ? 'flat'
259
- : 'text'
260
- "
261
- color="error"
262
- @click.stop="
263
- handleActionClick(item, group.set, 'reject')
264
- "
265
- />
266
- </v-col>
267
- <v-col cols="auto">
268
- <v-btn
269
- icon="mdi-check"
270
- size="small"
271
- :variant="
272
- activeActions[getKey(item, group.set)] === 'approve'
273
- ? 'flat'
274
- : 'text'
275
- "
276
- color="success"
277
- :loading="!!loadingActions[getKey(item, group.set)]"
278
- :disabled="!!loadingActions[getKey(item, group.set)]"
279
- @click.stop="
280
- handleActionClick(item, group.set, 'approve')
281
- "
282
- />
283
- </v-col>
284
- </v-row>
285
- </slot>
205
+ <slot name="item-append" :item="item" :group="group" />
286
206
  </v-col>
287
207
  </v-row>
288
208
  </v-sheet>
@@ -293,81 +213,6 @@
293
213
  </v-card>
294
214
  </v-col>
295
215
  </v-row>
296
-
297
- <v-dialog v-model="showAttachmentDialog" max-width="700" scrollable>
298
- <v-card>
299
- <v-card-title class="d-flex align-center pa-4">
300
- <span class="text-h6 font-weight-bold">
301
- Set {{ attachmentDialogSet }} — Attachments
302
- </span>
303
- <v-spacer />
304
- <v-btn
305
- icon="mdi-close"
306
- variant="text"
307
- size="small"
308
- @click="showAttachmentDialog = false"
309
- />
310
- </v-card-title>
311
-
312
- <v-divider />
313
-
314
- <v-card-text class="pa-4">
315
- <v-row>
316
- <v-col
317
- v-for="(id, index) in attachmentDialogIds"
318
- :key="index"
319
- cols="6"
320
- sm="4"
321
- >
322
- <v-sheet
323
- rounded="lg"
324
- class="overflow-hidden"
325
- style="aspect-ratio: 1"
326
- >
327
- <v-img
328
- :src="getFileUrl(id)"
329
- aspect-ratio="1"
330
- cover
331
- class="rounded-lg"
332
- @click="openFullImage(getFileUrl(id))"
333
- style="cursor: zoom-in"
334
- >
335
- <template v-slot:placeholder>
336
- <v-row
337
- class="fill-height ma-0"
338
- align="center"
339
- justify="center"
340
- >
341
- <v-progress-circular indeterminate color="grey-lighten-4" />
342
- </v-row>
343
- </template>
344
- <template v-slot:error>
345
- <v-row
346
- class="fill-height ma-0"
347
- align="center"
348
- justify="center"
349
- >
350
- <v-icon icon="mdi-image-broken" size="40" color="grey" />
351
- </v-row>
352
- </template>
353
- </v-img>
354
- </v-sheet>
355
- </v-col>
356
- </v-row>
357
- </v-card-text>
358
- </v-card>
359
- </v-dialog>
360
-
361
- <v-dialog v-model="showLightbox" max-width="900">
362
- <v-card>
363
- <v-card-actions class="pa-2 justify-end">
364
- <v-btn icon="mdi-close" variant="text" @click="showLightbox = false" />
365
- </v-card-actions>
366
- <v-card-text class="pa-2 pt-0">
367
- <v-img :src="lightboxSrc" contain max-height="80vh" />
368
- </v-card-text>
369
- </v-card>
370
- </v-dialog>
371
216
  </template>
372
217
 
373
218
  <script lang="ts" setup>
@@ -426,14 +271,6 @@ const props = defineProps({
426
271
  type: Boolean,
427
272
  default: false,
428
273
  },
429
- canManageScheduleTasks: {
430
- type: Boolean,
431
- default: true,
432
- },
433
- canAddRemarks: {
434
- type: Boolean,
435
- default: true,
436
- },
437
274
  extensionHeight: {
438
275
  type: Number,
439
276
  default: 50,
@@ -442,71 +279,28 @@ const props = defineProps({
442
279
  type: Number,
443
280
  default: 200,
444
281
  },
445
- selected: {
446
- type: Array as PropType<any[]>,
447
- default: () => [],
448
- },
449
282
  });
450
283
 
451
- const emits = defineEmits([
452
- "create",
453
- "refresh",
454
- "update:page",
455
- "row-click",
456
- "update:selected",
457
- "action-click",
458
- "request-completion-dialog",
459
- ]);
460
-
461
- defineExpose({
462
- revertSetApprovals,
463
- stopLoadingAction,
464
- });
284
+ const emits = defineEmits(["create", "refresh", "update:page"]);
465
285
 
466
286
  const internalPage = ref(props.page);
467
- const selected = shallowRef<any[]>(props.selected);
468
- const activeActions = reactive<Record<string, "approve" | "reject">>({});
469
- const persistedActions = reactive<Record<string, "approve" | "reject">>({});
470
- const loadingActions = reactive<Record<string, boolean>>({});
471
- const completedSets = ref<Set<number>>(new Set());
472
287
  const itemOrderMap = new Map<string, number>();
473
288
 
474
- const showAttachmentDialog = ref(false);
475
- const attachmentDialogSet = ref<number | undefined>(undefined);
476
- const attachmentDialogIds = ref<string[]>([]);
477
- const showLightbox = ref(false);
478
- const lightboxSrc = ref("");
479
-
480
- const { getFileUrl } = useFile();
481
-
482
- function openAttachmentDialog(setNumber: number, attachments: string[]) {
483
- attachmentDialogSet.value = setNumber;
484
- attachmentDialogIds.value = attachments;
485
- showAttachmentDialog.value = true;
486
- }
487
-
488
- function openFullImage(src: string) {
489
- lightboxSrc.value = src;
490
- showLightbox.value = true;
491
- }
492
-
493
289
  const groupedItems = computed(() => {
494
290
  return props.items.map((item: any) => {
495
291
  const units = item.units || [item];
496
292
 
497
- units.forEach((unit: any, index: number) => {
498
- const key = getKey(unit, item.set);
293
+ units.forEach((unit: any) => {
294
+ const key = `${unit[props.itemValue]}_${item.set}`;
499
295
  if (!itemOrderMap.has(key)) {
500
296
  itemOrderMap.set(key, itemOrderMap.size);
501
297
  }
502
298
  });
503
299
 
504
300
  const sortedUnits = [...units].sort((a: any, b: any) => {
505
- const keyA = getKey(a, item.set);
506
- const keyB = getKey(b, item.set);
507
- const orderA = itemOrderMap.get(keyA) ?? 0;
508
- const orderB = itemOrderMap.get(keyB) ?? 0;
509
- 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);
510
304
  });
511
305
 
512
306
  return {
@@ -522,27 +316,6 @@ const groupedItems = computed(() => {
522
316
  });
523
317
  });
524
318
 
525
- const totalItemsCount = computed(() => {
526
- return groupedItems.value.reduce((acc, group) => acc + group.items.length, 0);
527
- });
528
-
529
- const approvedItemsCount = computed(() => {
530
- return Object.values(activeActions).filter((action) => action === "approve")
531
- .length;
532
- });
533
-
534
- const hasRejectedItems = computed(() => {
535
- return Object.values(activeActions).some((action) => action === "reject");
536
- });
537
-
538
- const allItemsApproved = computed(() => {
539
- return (
540
- totalItemsCount.value > 0 &&
541
- approvedItemsCount.value === totalItemsCount.value &&
542
- !hasRejectedItems.value
543
- );
544
- });
545
-
546
319
  function formatTimestamp(ts: string): string {
547
320
  if (!ts) return "";
548
321
  const date = new Date(ts);
@@ -571,199 +344,16 @@ function isGroupInProgress(group: { items: any[] }): boolean {
571
344
  );
572
345
  }
573
346
 
574
- function isSetFullyApproved(setNumber: number): boolean {
575
- const group = groupedItems.value.find((g) => g.set === setNumber);
576
- if (!group) return false;
577
-
578
- return group.items.every((item: any) => {
579
- const key = getKey(item, setNumber);
580
- return activeActions[key] === "approve";
581
- });
582
- }
583
-
584
- function getNewApprovedItemsForSet(
585
- setNumber: number
586
- ): Array<{ key: string; item: any; action: "approve" }> {
587
- const group = groupedItems.value.find((g) => g.set === setNumber);
588
- if (!group) return [];
589
-
590
- const approvedItems: Array<{ key: string; item: any; action: "approve" }> =
591
- [];
592
-
593
- group.items.forEach((item: any) => {
594
- const key = getKey(item, setNumber);
595
- if (activeActions[key] === "approve" && !(key in persistedActions)) {
596
- approvedItems.push({
597
- key,
598
- item: { ...item, set: setNumber },
599
- action: "approve",
600
- });
601
- }
602
- });
603
-
604
- 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) ?? "";
605
350
  }
606
351
 
607
- const lastApprovedKey = ref<string | null>(null);
608
-
609
352
  watch(
610
353
  () => props.page,
611
354
  (val) => {
612
355
  internalPage.value = val;
613
-
614
356
  itemOrderMap.clear();
615
357
  }
616
358
  );
617
-
618
- watch(
619
- () => props.selected,
620
- (val) => {
621
- selected.value = val;
622
- }
623
- );
624
-
625
- watch(selected, (val) => {
626
- emits("update:selected", val);
627
- });
628
-
629
- watch(
630
- () => props.items,
631
- (items) => {
632
- if (!items || !Array.isArray(items)) return;
633
-
634
- Object.keys(persistedActions).forEach(
635
- (key) => delete persistedActions[key]
636
- );
637
-
638
- items.forEach((group: any) => {
639
- const set = group.set;
640
- const units = group.units || [];
641
-
642
- units.forEach((unit: any) => {
643
- const key = getKey(unit, set);
644
- if (unit.approve === true) {
645
- activeActions[key] = "approve";
646
- persistedActions[key] = "approve";
647
- } else if (unit.reject === true) {
648
- activeActions[key] = "reject";
649
- persistedActions[key] = "reject";
650
- }
651
- });
652
- });
653
- },
654
- { immediate: true }
655
- );
656
-
657
- function getKey(item: any, set?: number): string {
658
- return `${item[props.itemValue]}_${set ?? ""}`;
659
- }
660
-
661
- function getItemValue(item: any, key: string): string {
662
- if (!key) return "";
663
- return key.split(".").reduce((obj, k) => obj?.[k], item) ?? "";
664
- }
665
-
666
- function isItemSelected(item: any, set?: number): boolean {
667
- if (!Array.isArray(selected.value) || selected.value.length === 0) {
668
- return false;
669
- }
670
-
671
- if (typeof selected.value[0] === "object" && "unit" in selected.value[0]) {
672
- return selected.value.some(
673
- (s: any) => s.unit === item[props.itemValue] && s.set === set
674
- );
675
- }
676
-
677
- return selected.value.includes(item[props.itemValue]);
678
- }
679
-
680
- function handleActionClick(
681
- item: any,
682
- set: number | undefined,
683
- action: "approve" | "reject"
684
- ): void {
685
- const key = getKey(item, set);
686
-
687
- const isPersisted = key in persistedActions;
688
-
689
- if (activeActions[key] === action && !isPersisted) {
690
- delete activeActions[key];
691
- return;
692
- }
693
-
694
- activeActions[key] = action;
695
-
696
- if (action === "reject") {
697
- console.debug("TableHygiene: emitting action-click", {
698
- item: { ...item, set },
699
- action,
700
- });
701
- emits("action-click", { item: { ...item, set }, action });
702
- }
703
-
704
- if (action === "approve") {
705
- lastApprovedKey.value = key;
706
-
707
- if (
708
- set !== undefined &&
709
- isSetFullyApproved(set) &&
710
- !completedSets.value.has(set)
711
- ) {
712
- const newApprovedItems = getNewApprovedItemsForSet(set);
713
-
714
- if (newApprovedItems.length > 0) {
715
- // Mark this set as completed
716
- completedSets.value.add(set);
717
-
718
- // Emit request to open completion dialog
719
- emits("request-completion-dialog", {
720
- setNumber: set,
721
- approvedItems: newApprovedItems,
722
- lastApprovedKey: lastApprovedKey.value,
723
- });
724
- return;
725
- }
726
- }
727
-
728
- // Non-completing approve: undo optimistic check and show loading instead
729
- if (!isPersisted) {
730
- delete activeActions[key];
731
- loadingActions[key] = true;
732
- }
733
-
734
- console.debug("TableHygiene: emitting action-click (approve immediate)", {
735
- item: { ...item, set },
736
- action,
737
- });
738
- emits("action-click", { item: { ...item, set }, action });
739
- }
740
- }
741
-
742
- function stopLoadingAction(key: string): void {
743
- delete loadingActions[key];
744
- }
745
-
746
- function revertSetApprovals(setNumber: number): void {
747
- const group = groupedItems.value.find((g) => g.set === setNumber);
748
- if (group) {
749
- group.items.forEach((item: any) => {
750
- const key = getKey(item, setNumber);
751
-
752
- if (!(key in persistedActions)) {
753
- delete activeActions[key];
754
- }
755
- });
756
- }
757
-
758
- completedSets.value.delete(setNumber);
759
- }
760
-
761
- watch(
762
- () => props.items,
763
- () => {
764
- completedSets.value.clear();
765
- Object.keys(loadingActions).forEach((k) => delete loadingActions[k]);
766
- },
767
- { deep: true }
768
- );
769
359
  </script>