@7365admin1/layer-common 1.10.0 → 1.10.2

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 (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCard/AvailableStats.vue +55 -0
  4. package/components/AccessCardAddForm.vue +284 -19
  5. package/components/AccessCardAssignToUnitForm.vue +440 -0
  6. package/components/AccessManagement.vue +218 -85
  7. package/components/AddSupplyForm.vue +165 -0
  8. package/components/AreaChecklistHistoryLogs.vue +235 -0
  9. package/components/AreaChecklistHistoryMain.vue +176 -0
  10. package/components/AreaFormDialog.vue +266 -0
  11. package/components/AreaMain.vue +863 -0
  12. package/components/AttendanceCheckInOutDialog.vue +416 -0
  13. package/components/AttendanceDetailsDialog.vue +184 -0
  14. package/components/AttendanceMain.vue +155 -0
  15. package/components/AttendanceMapSearchDialog.vue +393 -0
  16. package/components/AttendanceSettingsDialog.vue +398 -0
  17. package/components/BuildingManagement/buildings.vue +5 -5
  18. package/components/BuildingManagement/units.vue +5 -5
  19. package/components/BulletinBoardManagement.vue +322 -0
  20. package/components/ChecklistItemRow.vue +54 -0
  21. package/components/CheckoutItemMain.vue +705 -0
  22. package/components/CleaningScheduleMain.vue +271 -0
  23. package/components/DocumentManagement.vue +4 -0
  24. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  25. package/components/EntryPassMain.vue +252 -200
  26. package/components/HygieneUpdateMoreAction.vue +238 -0
  27. package/components/ManageChecklistMain.vue +384 -0
  28. package/components/MemberMain.vue +48 -20
  29. package/components/MyAttendanceMain.vue +224 -0
  30. package/components/OnlineFormsConfiguration.vue +9 -2
  31. package/components/PhotoUpload.vue +410 -0
  32. package/components/ScheduleAreaMain.vue +313 -0
  33. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  34. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  35. package/components/ScheduleTaskForm.vue +471 -0
  36. package/components/ScheduleTaskMain.vue +345 -0
  37. package/components/ScheduleTastTicketMain.vue +182 -0
  38. package/components/SignaturePad.vue +17 -5
  39. package/components/StockCard.vue +191 -0
  40. package/components/SupplyManagementMain.vue +557 -0
  41. package/components/TableHygiene.vue +617 -0
  42. package/components/UnitMain.vue +451 -0
  43. package/components/VisitorManagement.vue +28 -15
  44. package/composables/useAccessManagement.ts +163 -0
  45. package/composables/useAreaPermission.ts +51 -0
  46. package/composables/useAreas.ts +99 -0
  47. package/composables/useAttendance.ts +89 -0
  48. package/composables/useAttendancePermission.ts +68 -0
  49. package/composables/useBuilding.ts +2 -2
  50. package/composables/useBuildingUnit.ts +2 -2
  51. package/composables/useBulletin.ts +82 -0
  52. package/composables/useCard.ts +2 -0
  53. package/composables/useCheckout.ts +61 -0
  54. package/composables/useCheckoutPermission.ts +80 -0
  55. package/composables/useCleaningPermission.ts +229 -0
  56. package/composables/useCleaningSchedulePermission.ts +58 -0
  57. package/composables/useCleaningSchedules.ts +233 -0
  58. package/composables/useCountry.ts +8 -0
  59. package/composables/useDashboardData.ts +2 -2
  60. package/composables/useFeedback.ts +1 -1
  61. package/composables/useLocation.ts +78 -0
  62. package/composables/useOnlineForm.ts +16 -9
  63. package/composables/usePeople.ts +87 -72
  64. package/composables/useQR.ts +29 -0
  65. package/composables/useScheduleTask.ts +89 -0
  66. package/composables/useScheduleTaskArea.ts +85 -0
  67. package/composables/useScheduleTaskPermission.ts +68 -0
  68. package/composables/useSiteEntryPassSettings.ts +4 -15
  69. package/composables/useStock.ts +45 -0
  70. package/composables/useSupply.ts +63 -0
  71. package/composables/useSupplyPermission.ts +92 -0
  72. package/composables/useUnitPermission.ts +51 -0
  73. package/composables/useUnits.ts +82 -0
  74. package/composables/useWebUsb.ts +389 -0
  75. package/composables/useWorkOrder.ts +1 -1
  76. package/nuxt.config.ts +3 -0
  77. package/package.json +4 -1
  78. package/types/area.d.ts +22 -0
  79. package/types/attendance.d.ts +38 -0
  80. package/types/checkout-item.d.ts +27 -0
  81. package/types/cleaner-schedule.d.ts +54 -0
  82. package/types/location.d.ts +42 -0
  83. package/types/schedule-task.d.ts +18 -0
  84. package/types/stock.d.ts +16 -0
  85. package/types/supply.d.ts +11 -0
  86. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,440 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6" align="center">
5
+ <span class="font-weight-bold text-h5">Assign Access Card to Unit</span>
6
+ </v-row>
7
+ </v-toolbar>
8
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
9
+ <v-form v-model="validForm" :disabled="loading">
10
+ <v-row no-gutters class="px-6 pt-4 pb-2">
11
+ <!-- Building -->
12
+ <v-col cols="12" class="px-1">
13
+ <InputLabel
14
+ class="text-capitalize font-weight-bold"
15
+ title="Building"
16
+ required
17
+ />
18
+ <v-autocomplete
19
+ v-model="form.building"
20
+ density="compact"
21
+ :items="buildingItems"
22
+ hide-details="auto"
23
+ item-title="name"
24
+ item-value="value"
25
+ placeholder="Select building..."
26
+ persistent-placeholder
27
+ :rules="[requiredRule]"
28
+ :loading="buildingLoading"
29
+ />
30
+ </v-col>
31
+
32
+ <!-- Level -->
33
+ <v-col cols="12" class="px-1 mt-2">
34
+ <InputLabel
35
+ class="text-capitalize font-weight-bold"
36
+ title="Level"
37
+ required
38
+ />
39
+ <v-autocomplete
40
+ v-model="form.level"
41
+ density="compact"
42
+ :items="levelItems"
43
+ hide-details="auto"
44
+ item-title="name"
45
+ item-value="value"
46
+ placeholder="Select level..."
47
+ persistent-placeholder
48
+ :rules="[requiredRule]"
49
+ :disabled="!form.building"
50
+ />
51
+ </v-col>
52
+
53
+ <!-- Units (multi-select) -->
54
+ <v-col cols="12" class="px-1 mt-2">
55
+ <InputLabel
56
+ class="text-capitalize font-weight-bold"
57
+ title="Unit(s)"
58
+ required
59
+ />
60
+ <v-autocomplete
61
+ v-model="form.units"
62
+ density="compact"
63
+ :items="unitItems"
64
+ hide-details="auto"
65
+ item-title="name"
66
+ item-value="value"
67
+ placeholder="Select unit..."
68
+ persistent-placeholder
69
+ :rules="[requiredArrayRule]"
70
+ :disabled="!form.level"
71
+ :loading="unitLoading"
72
+ multiple
73
+ chips
74
+ closable-chips
75
+ />
76
+ </v-col>
77
+
78
+ <!-- Type -->
79
+ <v-col cols="12" class="px-1 mt-2">
80
+ <InputLabel
81
+ class="text-capitalize font-weight-bold"
82
+ title="Type of Access Card"
83
+ required
84
+ />
85
+ <v-select
86
+ v-model="form.type"
87
+ density="compact"
88
+ :items="typeItems"
89
+ hide-details="auto"
90
+ item-title="label"
91
+ item-value="value"
92
+ :rules="[requiredRule]"
93
+ />
94
+ </v-col>
95
+
96
+ <!-- Access Level -->
97
+ <v-col cols="12" class="px-1 mt-2">
98
+ <InputLabel
99
+ class="text-capitalize font-weight-bold"
100
+ title="Access Level"
101
+ required
102
+ />
103
+ <v-select
104
+ v-model="form.accessLevel"
105
+ density="compact"
106
+ :items="accessLevelItems"
107
+ hide-details="auto"
108
+ item-title="name"
109
+ item-value="no"
110
+ :rules="[requiredRule]"
111
+ :loading="levelsLoading"
112
+ placeholder="Select access level..."
113
+ />
114
+ </v-col>
115
+
116
+ <!-- Lift Access Level -->
117
+ <v-col cols="12" class="px-1 mt-2 mb-4">
118
+ <InputLabel
119
+ class="text-capitalize font-weight-bold"
120
+ title="Lift Access Level"
121
+ required
122
+ />
123
+ <v-select
124
+ v-model="form.liftAccessLevel"
125
+ density="compact"
126
+ :items="liftAccessLevelItems"
127
+ hide-details="auto"
128
+ item-title="name"
129
+ item-value="no"
130
+ :rules="[requiredRule]"
131
+ :loading="levelsLoading"
132
+ placeholder="Select lift access level..."
133
+ />
134
+ </v-col>
135
+
136
+ <!-- Quantity -->
137
+ <v-col cols="12" class="px-1 mt-2">
138
+ <InputLabel
139
+ class="text-capitalize font-weight-bold"
140
+ title="Quantity"
141
+ required
142
+ />
143
+ <v-text-field
144
+ v-model.number="form.quantity"
145
+ density="compact"
146
+ hide-details="auto"
147
+ type="number"
148
+ :min="1"
149
+ hide-spin-buttons
150
+ :rules="[requiredRule, minQuantityRule, maxQuantityRule]"
151
+ @blur="enforceMinQuantity"
152
+ />
153
+ <div class="text-caption mt-1">
154
+ <template v-if="availableLoading">
155
+ <span class="text-grey">Loading available count...</span>
156
+ </template>
157
+ <template v-else-if="availableCount !== null">
158
+ <span :class="availableCount === 0 ? 'text-error' : 'text-grey'">
159
+ Available: {{ availableCount }}
160
+ </span>
161
+ </template>
162
+ </div>
163
+ </v-col>
164
+ </v-row>
165
+ </v-form>
166
+ </v-card-text>
167
+
168
+ <v-toolbar density="compact">
169
+ <v-row no-gutters>
170
+ <v-col cols="6">
171
+ <v-btn
172
+ tile
173
+ block
174
+ variant="text"
175
+ class="text-none"
176
+ size="48"
177
+ @click="cancel"
178
+ :disabled="loading"
179
+ >
180
+ Cancel
181
+ </v-btn>
182
+ </v-col>
183
+ <v-col cols="6">
184
+ <v-btn
185
+ tile
186
+ block
187
+ variant="flat"
188
+ color="black"
189
+ class="text-none"
190
+ size="48"
191
+ :disabled="!validForm || loading || availableCount === 0 || (availableCount !== null && form.quantity > availableCount)"
192
+ :loading="loading"
193
+ @click="submit"
194
+ >
195
+ Assign
196
+ </v-btn>
197
+ </v-col>
198
+ </v-row>
199
+ </v-toolbar>
200
+ </v-card>
201
+ </template>
202
+ <script setup lang="ts">
203
+ const props = defineProps({
204
+ siteId: {
205
+ type: String,
206
+ default: "",
207
+ },
208
+ });
209
+ const emit = defineEmits(["cancel", "success", "error"]);
210
+
211
+ const {
212
+ getDoorAccessLevels: _getDoorAccessLevels,
213
+ getLiftAccessLevels: _getLiftAccessLevels,
214
+ assignAccessCard: _assignAccessCard,
215
+ getAvailableAccessCards: _getAvailableAccessCards,
216
+ } = useAccessManagement();
217
+ const { getAll: _getBuildings } = useBuilding();
218
+ const { getAllUnits: _getUnits } = useBuildingUnit();
219
+ const { requiredRule } = useUtils();
220
+
221
+ const route = useRoute();
222
+ const siteId = computed(() => props.siteId || (route.params.site as string));
223
+
224
+ const validForm = ref(false);
225
+ const loading = ref(false);
226
+ const levelsLoading = ref(false);
227
+ const buildingLoading = ref(false);
228
+ const unitLoading = ref(false);
229
+ const availableLoading = ref(false);
230
+ const availableCount = ref<number | null>(null);
231
+
232
+ const form = ref({
233
+ building: null as string | null,
234
+ level: null as string | null,
235
+ units: [] as string[],
236
+ quantity: 1,
237
+ type: "NFC",
238
+ userType: "Visitor/Resident",
239
+ accessLevel: null as string | null,
240
+ liftAccessLevel: null as string | null,
241
+ });
242
+
243
+ const typeItems = [
244
+ { label: "Physical Access Card", value: "NFC" },
245
+ { label: "Non Physical Access Card", value: "QRCODE" },
246
+ ];
247
+
248
+ const accessLevelItems = ref<{ name: string; no: string }[]>([]);
249
+ const liftAccessLevelItems = ref<{ name: string; no: string }[]>([]);
250
+ const buildingItems = ref<{ name: string; value: string }[]>([]);
251
+ const buildingsData = ref<Record<string, any>[]>([]);
252
+ const levelItems = ref<{ name: string; value: string }[]>([]);
253
+ const unitItems = ref<{ name: string; value: string }[]>([]);
254
+
255
+ function requiredArrayRule(v: string[]) {
256
+ return (v && v.length > 0) || "This field is required";
257
+ }
258
+
259
+ function minQuantityRule(v: number) {
260
+ return v >= 1 || "Quantity must be at least 1";
261
+ }
262
+
263
+ function maxQuantityRule(v: number) {
264
+ if (availableCount.value !== null && v > availableCount.value) {
265
+ return `Quantity cannot exceed available (${availableCount.value})`;
266
+ }
267
+ return true;
268
+ }
269
+
270
+ async function fetchAvailableCount() {
271
+ if (!form.value.accessLevel || !form.value.liftAccessLevel) {
272
+ availableCount.value = null;
273
+ return;
274
+ }
275
+ availableLoading.value = true;
276
+ availableCount.value = null;
277
+ try {
278
+ const res = await _getAvailableAccessCards({
279
+ site: siteId.value,
280
+ userType: form.value.userType,
281
+ type: form.value.type,
282
+ accessLevel: form.value.accessLevel,
283
+ liftAccessLevel: form.value.liftAccessLevel,
284
+ });
285
+ const data: { counts: { count: number }[] }[] = res.data ?? [];
286
+ availableCount.value = data.reduce(
287
+ (total, item) =>
288
+ total + (item.counts ?? []).reduce((s, c) => s + (c.count ?? 0), 0),
289
+ 0
290
+ );
291
+ } catch {
292
+ availableCount.value = null;
293
+ } finally {
294
+ availableLoading.value = false;
295
+ }
296
+ }
297
+
298
+ function enforceMinQuantity() {
299
+ if (!form.value.quantity || form.value.quantity < 1) {
300
+ form.value.quantity = 1;
301
+ }
302
+ }
303
+
304
+ onMounted(async () => {
305
+ // Fetch buildings and ACM levels independently so one failure doesn't block the other
306
+ buildingLoading.value = true;
307
+ levelsLoading.value = true;
308
+
309
+ const [buildingsResult, acmResult] = await Promise.allSettled([
310
+ _getBuildings({ page: 1, status: "active", site: siteId.value }),
311
+ $fetch<{ encrypted: string }>("/api/encrypt-acm-url"),
312
+ ]);
313
+
314
+ // Buildings
315
+ if (buildingsResult.status === "fulfilled") {
316
+ buildingsData.value = buildingsResult.value.items || [];
317
+ buildingItems.value = buildingsData.value.map((b: any) => ({
318
+ name: b.name,
319
+ value: b._id,
320
+ }));
321
+ }
322
+ buildingLoading.value = false;
323
+
324
+ // ACM levels (door + lift)
325
+ if (acmResult.status === "fulfilled") {
326
+ const acmUrl = acmResult.value.encrypted;
327
+ try {
328
+ const [doorLevels, liftLevels] = await Promise.all([
329
+ _getDoorAccessLevels(acmUrl),
330
+ _getLiftAccessLevels(acmUrl),
331
+ ]);
332
+ accessLevelItems.value = doorLevels.data ?? [];
333
+ liftAccessLevelItems.value = liftLevels.data ?? [];
334
+ } catch {
335
+ emit(
336
+ "error",
337
+ "EntryPass server is unavailable. Please contact your administrator."
338
+ );
339
+ }
340
+ } else {
341
+ emit(
342
+ "error",
343
+ "EntryPass server is unavailable. Please contact your administrator."
344
+ );
345
+ }
346
+ levelsLoading.value = false;
347
+ });
348
+
349
+ watch(
350
+ () => form.value.building,
351
+ async (newVal) => {
352
+ form.value.level = null;
353
+ form.value.units = [];
354
+ levelItems.value = [];
355
+ unitItems.value = [];
356
+
357
+ if (newVal) {
358
+ const selectedBuilding = buildingsData.value.find(
359
+ (b: any) => b._id === newVal
360
+ );
361
+ if (selectedBuilding?.levels) {
362
+ levelItems.value = selectedBuilding.levels.map((level: string) => ({
363
+ name: level,
364
+ value: level,
365
+ }));
366
+ }
367
+ }
368
+ }
369
+ );
370
+
371
+ watch(
372
+ () => form.value.level,
373
+ async (newVal) => {
374
+ form.value.units = [];
375
+ unitItems.value = [];
376
+
377
+ if (newVal && form.value.building) {
378
+ unitLoading.value = true;
379
+ try {
380
+ const response = await _getUnits({
381
+ page: 1,
382
+ status: "active",
383
+ site: siteId.value,
384
+ building: form.value.building,
385
+ });
386
+ const units = response.items || [];
387
+ const filtered = units.filter((u: any) => u.level === newVal);
388
+ unitItems.value = filtered.map((u: any) => ({
389
+ name: u.name,
390
+ value: u._id,
391
+ }));
392
+ } catch {
393
+ console.error("Failed to fetch units");
394
+ } finally {
395
+ unitLoading.value = false;
396
+ }
397
+ }
398
+ }
399
+ );
400
+
401
+ watch(
402
+ [
403
+ () => form.value.type,
404
+ () => form.value.accessLevel,
405
+ () => form.value.liftAccessLevel,
406
+ ],
407
+ () => {
408
+ fetchAvailableCount();
409
+ },
410
+ { immediate: true }
411
+ );
412
+
413
+ function cancel() {
414
+ emit("cancel");
415
+ }
416
+
417
+ async function submit() {
418
+ loading.value = true;
419
+ try {
420
+ await _assignAccessCard({
421
+ units: form.value.units,
422
+ quantity: form.value.quantity,
423
+ type: form.value.type,
424
+ site: siteId.value,
425
+ userType: form.value.userType,
426
+ accessLevel: form.value.accessLevel ?? "",
427
+ liftAccessLevel: form.value.liftAccessLevel ?? "",
428
+ });
429
+ emit("success");
430
+ } catch (error: any) {
431
+ const msg =
432
+ error?.response?._data?.message ||
433
+ error?.data?.message ||
434
+ "Failed to assign access card.";
435
+ emit("error", msg);
436
+ } finally {
437
+ loading.value = false;
438
+ }
439
+ }
440
+ </script>