@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.
- package/CHANGELOG.md +12 -0
- package/components/AcceptDialog.vue +44 -0
- package/components/AccessCard/AvailableStats.vue +55 -0
- package/components/AccessCardAddForm.vue +284 -19
- package/components/AccessCardAssignToUnitForm.vue +440 -0
- package/components/AccessManagement.vue +218 -85
- package/components/AddSupplyForm.vue +165 -0
- package/components/AreaChecklistHistoryLogs.vue +235 -0
- package/components/AreaChecklistHistoryMain.vue +176 -0
- package/components/AreaFormDialog.vue +266 -0
- package/components/AreaMain.vue +863 -0
- package/components/AttendanceCheckInOutDialog.vue +416 -0
- package/components/AttendanceDetailsDialog.vue +184 -0
- package/components/AttendanceMain.vue +155 -0
- package/components/AttendanceMapSearchDialog.vue +393 -0
- package/components/AttendanceSettingsDialog.vue +398 -0
- package/components/BuildingManagement/buildings.vue +5 -5
- package/components/BuildingManagement/units.vue +5 -5
- package/components/BulletinBoardManagement.vue +322 -0
- package/components/ChecklistItemRow.vue +54 -0
- package/components/CheckoutItemMain.vue +705 -0
- package/components/CleaningScheduleMain.vue +271 -0
- package/components/DocumentManagement.vue +4 -0
- package/components/EntryPass/QrTemplatePreview.vue +104 -0
- package/components/EntryPassMain.vue +252 -200
- package/components/HygieneUpdateMoreAction.vue +238 -0
- package/components/ManageChecklistMain.vue +384 -0
- package/components/MemberMain.vue +48 -20
- package/components/MyAttendanceMain.vue +224 -0
- package/components/OnlineFormsConfiguration.vue +9 -2
- package/components/PhotoUpload.vue +410 -0
- package/components/ScheduleAreaMain.vue +313 -0
- package/components/ScheduleTaskAreaFormDialog.vue +144 -0
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
- package/components/ScheduleTaskForm.vue +471 -0
- package/components/ScheduleTaskMain.vue +345 -0
- package/components/ScheduleTastTicketMain.vue +182 -0
- package/components/SignaturePad.vue +17 -5
- package/components/StockCard.vue +191 -0
- package/components/SupplyManagementMain.vue +557 -0
- package/components/TableHygiene.vue +617 -0
- package/components/UnitMain.vue +451 -0
- package/components/VisitorManagement.vue +28 -15
- package/composables/useAccessManagement.ts +163 -0
- package/composables/useAreaPermission.ts +51 -0
- package/composables/useAreas.ts +99 -0
- package/composables/useAttendance.ts +89 -0
- package/composables/useAttendancePermission.ts +68 -0
- package/composables/useBuilding.ts +2 -2
- package/composables/useBuildingUnit.ts +2 -2
- package/composables/useBulletin.ts +82 -0
- package/composables/useCard.ts +2 -0
- package/composables/useCheckout.ts +61 -0
- package/composables/useCheckoutPermission.ts +80 -0
- package/composables/useCleaningPermission.ts +229 -0
- package/composables/useCleaningSchedulePermission.ts +58 -0
- package/composables/useCleaningSchedules.ts +233 -0
- package/composables/useCountry.ts +8 -0
- package/composables/useDashboardData.ts +2 -2
- package/composables/useFeedback.ts +1 -1
- package/composables/useLocation.ts +78 -0
- package/composables/useOnlineForm.ts +16 -9
- package/composables/usePeople.ts +87 -72
- package/composables/useQR.ts +29 -0
- package/composables/useScheduleTask.ts +89 -0
- package/composables/useScheduleTaskArea.ts +85 -0
- package/composables/useScheduleTaskPermission.ts +68 -0
- package/composables/useSiteEntryPassSettings.ts +4 -15
- package/composables/useStock.ts +45 -0
- package/composables/useSupply.ts +63 -0
- package/composables/useSupplyPermission.ts +92 -0
- package/composables/useUnitPermission.ts +51 -0
- package/composables/useUnits.ts +82 -0
- package/composables/useWebUsb.ts +389 -0
- package/composables/useWorkOrder.ts +1 -1
- package/nuxt.config.ts +3 -0
- package/package.json +4 -1
- package/types/area.d.ts +22 -0
- package/types/attendance.d.ts +38 -0
- package/types/checkout-item.d.ts +27 -0
- package/types/cleaner-schedule.d.ts +54 -0
- package/types/location.d.ts +42 -0
- package/types/schedule-task.d.ts +18 -0
- package/types/stock.d.ts +16 -0
- package/types/supply.d.ts +11 -0
- 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>
|