@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.10.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8d61e72: Update layer-common changes as of March 17, 2028
|
|
8
|
+
|
|
9
|
+
## 1.10.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a17cf02: Update Changes for march 11, 2026
|
|
14
|
+
|
|
3
15
|
## 1.10.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
variant="tonal"
|
|
10
10
|
size="large"
|
|
11
11
|
:disabled="!items.length"
|
|
12
|
+
@click="handlePrintAll"
|
|
12
13
|
>
|
|
13
14
|
Print All
|
|
14
15
|
</v-btn>
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
rounded="pill"
|
|
18
19
|
variant="tonal"
|
|
19
20
|
size="large"
|
|
20
|
-
:disabled="!
|
|
21
|
+
:disabled="!qrCodeAccessCards.length"
|
|
22
|
+
@click="generateQrCodes"
|
|
21
23
|
>
|
|
22
24
|
Generate QR Code
|
|
23
25
|
</v-btn>
|
|
@@ -26,23 +28,14 @@
|
|
|
26
28
|
rounded="pill"
|
|
27
29
|
variant="tonal"
|
|
28
30
|
size="large"
|
|
29
|
-
:disabled="!
|
|
31
|
+
:disabled="!qrCodeAccessCardsToPrint.length"
|
|
32
|
+
@click="handlePrintQrCode"
|
|
30
33
|
>
|
|
31
34
|
Print QR Code
|
|
32
35
|
</v-btn>
|
|
33
36
|
</v-row>
|
|
34
37
|
|
|
35
38
|
<v-row no-gutters class="ga-2" justify="end" style="max-width: 420px">
|
|
36
|
-
<v-select
|
|
37
|
-
v-model="typeFilter"
|
|
38
|
-
:items="typeOptions"
|
|
39
|
-
placeholder="Type"
|
|
40
|
-
variant="outlined"
|
|
41
|
-
density="comfortable"
|
|
42
|
-
hide-details
|
|
43
|
-
clearable
|
|
44
|
-
style="max-width: 160px"
|
|
45
|
-
/>
|
|
46
39
|
<v-text-field
|
|
47
40
|
v-model="searchText"
|
|
48
41
|
placeholder="Search Card..."
|
|
@@ -86,55 +79,133 @@
|
|
|
86
79
|
</v-toolbar>
|
|
87
80
|
|
|
88
81
|
<v-data-table
|
|
89
|
-
v-model="selected"
|
|
90
82
|
:headers="tableHeaders"
|
|
91
83
|
:items="items"
|
|
92
84
|
item-value="_id"
|
|
93
85
|
items-per-page="10"
|
|
94
86
|
fixed-header
|
|
95
87
|
hide-default-footer
|
|
96
|
-
show-select
|
|
97
88
|
style="max-height: calc(100vh - 200px)"
|
|
98
89
|
>
|
|
99
|
-
<template #
|
|
90
|
+
<template #header.selectGenerate>
|
|
91
|
+
<v-checkbox
|
|
92
|
+
:model-value="isAllGenerateSelected"
|
|
93
|
+
@update:model-value="toggleSelectAllGenerate"
|
|
94
|
+
hide-details
|
|
95
|
+
density="compact"
|
|
96
|
+
:disabled="!hasGeneratableItems"
|
|
97
|
+
/>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<template #header.selectPrint>
|
|
101
|
+
<v-checkbox
|
|
102
|
+
:model-value="isAllPrintSelected"
|
|
103
|
+
@update:model-value="toggleSelectAllPrint"
|
|
104
|
+
hide-details
|
|
105
|
+
density="compact"
|
|
106
|
+
:disabled="!hasPrintableItems"
|
|
107
|
+
style="margin-left: -12px"
|
|
108
|
+
/>
|
|
109
|
+
</template>
|
|
110
|
+
|
|
111
|
+
<template #item.selectGenerate="{ item }">
|
|
112
|
+
<v-checkbox
|
|
113
|
+
v-if="!item.qrTag"
|
|
114
|
+
:model-value="qrCodeAccessCards.includes(item._id)"
|
|
115
|
+
@update:model-value="toggleGenerateSelection(item._id)"
|
|
116
|
+
hide-details
|
|
117
|
+
density="compact"
|
|
118
|
+
/>
|
|
119
|
+
</template>
|
|
120
|
+
|
|
121
|
+
<template #item.cardNo="{ item }">
|
|
100
122
|
<v-row no-gutters align="center" class="ga-2">
|
|
101
123
|
<v-icon size="18" color="grey-darken-1">mdi-card-account-details</v-icon>
|
|
102
|
-
<span>{{ item.
|
|
124
|
+
<span>{{ item.cardNo ?? "N/A" }}</span>
|
|
103
125
|
</v-row>
|
|
104
126
|
</template>
|
|
105
127
|
|
|
106
|
-
<template #item.
|
|
128
|
+
<template #item.qrTagCardNo="{ item }">
|
|
129
|
+
<span>{{ item.qrTagCardNo != null ? `C${item.qrTagCardNo}` : "N/A" }}</span>
|
|
130
|
+
</template>
|
|
131
|
+
|
|
132
|
+
<template #item.qrTag="{ item }">
|
|
107
133
|
<v-icon
|
|
108
|
-
:color="item.
|
|
134
|
+
:color="item.qrTag ? 'success' : 'grey-lighten-1'"
|
|
109
135
|
size="22"
|
|
110
136
|
>
|
|
111
|
-
{{ item.
|
|
137
|
+
{{ item.qrTag ? "mdi-check-circle" : "mdi-circle-outline" }}
|
|
112
138
|
</v-icon>
|
|
113
139
|
</template>
|
|
140
|
+
|
|
141
|
+
<template #item.selectPrint="{ item }">
|
|
142
|
+
<v-checkbox
|
|
143
|
+
v-if="item.qrTag"
|
|
144
|
+
:model-value="qrCodeAccessCardsToPrint.includes(item._id)"
|
|
145
|
+
@update:model-value="togglePrintSelection(item._id)"
|
|
146
|
+
hide-details
|
|
147
|
+
density="compact"
|
|
148
|
+
style="margin-left: -12px"
|
|
149
|
+
/>
|
|
150
|
+
</template>
|
|
114
151
|
</v-data-table>
|
|
115
152
|
</v-card>
|
|
116
153
|
</v-col>
|
|
117
154
|
|
|
155
|
+
<!-- Hidden QR canvases for generation -->
|
|
156
|
+
<ClientOnly>
|
|
157
|
+
<div style="display: none">
|
|
158
|
+
<qrcode-vue
|
|
159
|
+
v-for="qrCode in qrCodesToGenerate"
|
|
160
|
+
:key="qrCode._id"
|
|
161
|
+
:value="qrCode.qrData"
|
|
162
|
+
:size="150"
|
|
163
|
+
:data-card-id="qrCode._id"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
</ClientOnly>
|
|
167
|
+
|
|
168
|
+
<!-- Print dialog -->
|
|
169
|
+
<v-dialog v-model="showPrintDialog" max-width="fit-content">
|
|
170
|
+
<AccessCardQrTaggingPrintQr
|
|
171
|
+
ref="printQrRef"
|
|
172
|
+
:access-cards="printPayload"
|
|
173
|
+
@close="showPrintDialog = false"
|
|
174
|
+
/>
|
|
175
|
+
</v-dialog>
|
|
176
|
+
|
|
177
|
+
<!-- Processing loader -->
|
|
178
|
+
<v-dialog v-model="isGenerating" persistent width="300">
|
|
179
|
+
<v-card>
|
|
180
|
+
<v-card-text class="text-center">
|
|
181
|
+
<v-progress-circular indeterminate color="deep-purple" size="50" />
|
|
182
|
+
<div class="mt-3">Processing QR Codes...</div>
|
|
183
|
+
</v-card-text>
|
|
184
|
+
</v-card>
|
|
185
|
+
</v-dialog>
|
|
186
|
+
|
|
118
187
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
119
188
|
</v-row>
|
|
120
189
|
</template>
|
|
121
190
|
|
|
122
191
|
<script setup lang="ts">
|
|
192
|
+
import QrcodeVue from "qrcode.vue";
|
|
193
|
+
|
|
123
194
|
definePageMeta({
|
|
124
195
|
middleware: ["01-auth", "02-org"],
|
|
125
196
|
memberOnly: true,
|
|
126
197
|
});
|
|
127
198
|
|
|
128
199
|
const tableHeaders = [
|
|
129
|
-
{ title: "
|
|
130
|
-
{ title: "
|
|
131
|
-
{ title: "
|
|
132
|
-
{ title: "
|
|
133
|
-
{ title: "
|
|
200
|
+
{ title: "", key: "selectGenerate", sortable: false, width: "50px" },
|
|
201
|
+
{ title: "Card", key: "cardNo", sortable: false },
|
|
202
|
+
{ title: "Location", key: "accessLevel", sortable: false },
|
|
203
|
+
{ title: "Card No.", key: "qrTagCardNo", sortable: false },
|
|
204
|
+
{ title: "HID", key: "extractedCardNo", sortable: false },
|
|
205
|
+
{ title: "QR Code", key: "qrTag", sortable: false },
|
|
206
|
+
{ title: "", key: "selectPrint", sortable: false, width: "50px" },
|
|
134
207
|
];
|
|
135
208
|
|
|
136
|
-
const typeOptions = ["Physical", "Non-Physical"];
|
|
137
|
-
|
|
138
209
|
const route = useRoute();
|
|
139
210
|
const siteId = computed(() => route.params.site as string);
|
|
140
211
|
const orgId = computed(() => route.params.org as string);
|
|
@@ -148,9 +219,24 @@ const messageSnackbar = ref(false);
|
|
|
148
219
|
const messageColor = ref("");
|
|
149
220
|
|
|
150
221
|
const items = ref<Record<string, any>[]>([]);
|
|
151
|
-
const selected = ref<string[]>([]);
|
|
152
222
|
const searchText = ref("");
|
|
153
|
-
|
|
223
|
+
|
|
224
|
+
const qrCodeAccessCards = ref<string[]>([]);
|
|
225
|
+
const qrCodeAccessCardsToPrint = ref<string[]>([]);
|
|
226
|
+
const qrCodesToGenerate = ref<{ _id: string; cardNo: string; qrData: string; qrTagCardNo: string }[]>([]);
|
|
227
|
+
|
|
228
|
+
const isGenerating = ref(false);
|
|
229
|
+
const showPrintDialog = ref(false);
|
|
230
|
+
const printPayload = ref<any[]>([]);
|
|
231
|
+
const printQrRef = ref();
|
|
232
|
+
|
|
233
|
+
const {
|
|
234
|
+
getVisitorAccessCards,
|
|
235
|
+
saveVisitorAccessCardQrTag,
|
|
236
|
+
getAllVisitorAccessCardsQrTags,
|
|
237
|
+
} = useAccessManagement();
|
|
238
|
+
|
|
239
|
+
const { addFile, getFileUrl } = useFile();
|
|
154
240
|
|
|
155
241
|
const {
|
|
156
242
|
data: qrTaggingReq,
|
|
@@ -158,23 +244,217 @@ const {
|
|
|
158
244
|
status: fetchStatus,
|
|
159
245
|
} = useLazyAsyncData(
|
|
160
246
|
"get-qr-tagging",
|
|
161
|
-
() =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
247
|
+
() =>
|
|
248
|
+
getVisitorAccessCards({
|
|
249
|
+
page: page.value,
|
|
250
|
+
limit: 10,
|
|
251
|
+
site: siteId.value,
|
|
252
|
+
search: searchText.value || undefined,
|
|
253
|
+
}),
|
|
254
|
+
{ watch: [page, searchText] }
|
|
166
255
|
);
|
|
167
256
|
|
|
168
257
|
const loading = computed(() => fetchStatus.value === "pending");
|
|
169
258
|
|
|
170
259
|
watchEffect(() => {
|
|
171
260
|
if (qrTaggingReq.value?.data) {
|
|
172
|
-
items.value = qrTaggingReq.value.data.items
|
|
261
|
+
items.value = qrTaggingReq.value.data.items.map((item: any) => ({
|
|
262
|
+
...item,
|
|
263
|
+
qrTagCardNo: item.qrTagCardNo ?? item.extractedCardNo,
|
|
264
|
+
}));
|
|
173
265
|
pages.value = qrTaggingReq.value.data.pages;
|
|
174
266
|
pageRange.value = qrTaggingReq.value.data.pageRange;
|
|
175
267
|
}
|
|
176
268
|
});
|
|
177
269
|
|
|
270
|
+
const hasGeneratableItems = computed(() =>
|
|
271
|
+
items.value.some((item) => !item.qrTag)
|
|
272
|
+
);
|
|
273
|
+
const hasPrintableItems = computed(() =>
|
|
274
|
+
items.value.some((item) => !!item.qrTag)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const isAllGenerateSelected = computed(() => {
|
|
278
|
+
const generatable = items.value.filter((item) => !item.qrTag);
|
|
279
|
+
return (
|
|
280
|
+
generatable.length > 0 &&
|
|
281
|
+
generatable.every((item) => qrCodeAccessCards.value.includes(item._id))
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const isAllPrintSelected = computed(() => {
|
|
286
|
+
const printable = items.value.filter((item) => !!item.qrTag);
|
|
287
|
+
return (
|
|
288
|
+
printable.length > 0 &&
|
|
289
|
+
printable.every((item) =>
|
|
290
|
+
qrCodeAccessCardsToPrint.value.includes(item._id)
|
|
291
|
+
)
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
function toggleSelectAllGenerate(value: boolean | null) {
|
|
296
|
+
if (value) {
|
|
297
|
+
qrCodeAccessCards.value = items.value
|
|
298
|
+
.filter((item) => !item.qrTag)
|
|
299
|
+
.map((item) => item._id);
|
|
300
|
+
} else {
|
|
301
|
+
qrCodeAccessCards.value = [];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function toggleSelectAllPrint(value: boolean | null) {
|
|
306
|
+
if (value) {
|
|
307
|
+
qrCodeAccessCardsToPrint.value = items.value
|
|
308
|
+
.filter((item) => !!item.qrTag)
|
|
309
|
+
.map((item) => item._id);
|
|
310
|
+
} else {
|
|
311
|
+
qrCodeAccessCardsToPrint.value = [];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function toggleGenerateSelection(id: string) {
|
|
316
|
+
if (qrCodeAccessCards.value.includes(id)) {
|
|
317
|
+
qrCodeAccessCards.value = qrCodeAccessCards.value.filter((i) => i !== id);
|
|
318
|
+
} else {
|
|
319
|
+
qrCodeAccessCards.value.push(id);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function togglePrintSelection(id: string) {
|
|
324
|
+
if (qrCodeAccessCardsToPrint.value.includes(id)) {
|
|
325
|
+
qrCodeAccessCardsToPrint.value = qrCodeAccessCardsToPrint.value.filter(
|
|
326
|
+
(i) => i !== id
|
|
327
|
+
);
|
|
328
|
+
} else {
|
|
329
|
+
qrCodeAccessCardsToPrint.value.push(id);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function generateQrCodes() {
|
|
334
|
+
isGenerating.value = true;
|
|
335
|
+
|
|
336
|
+
const filteredCards = items.value.filter((card) =>
|
|
337
|
+
qrCodeAccessCards.value.includes(card._id)
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
qrCodesToGenerate.value = filteredCards.map((card) => ({
|
|
341
|
+
_id: card._id,
|
|
342
|
+
cardNo: card.cardNo,
|
|
343
|
+
qrData: card.qrData,
|
|
344
|
+
qrTagCardNo: card.qrTagCardNo ?? "",
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
await nextTick();
|
|
348
|
+
await nextTick();
|
|
349
|
+
|
|
350
|
+
const payloads: Array<{ _id: string; cardNo: string; qrTag: string; qrTagCardNo: string }> = [];
|
|
351
|
+
|
|
352
|
+
await Promise.all(
|
|
353
|
+
qrCodesToGenerate.value.map(async (qrCode) => {
|
|
354
|
+
const fileUrl = await saveQrCodeToStorage(qrCode._id);
|
|
355
|
+
if (fileUrl) {
|
|
356
|
+
payloads.push({
|
|
357
|
+
_id: qrCode._id,
|
|
358
|
+
cardNo: qrCode.cardNo,
|
|
359
|
+
qrTag: fileUrl,
|
|
360
|
+
qrTagCardNo: qrCode.qrTagCardNo,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
if (payloads.length > 0) {
|
|
368
|
+
await saveVisitorAccessCardQrTag(siteId.value, payloads);
|
|
369
|
+
showMessage(`${payloads.length} QR code(s) generated successfully`, "success");
|
|
370
|
+
} else {
|
|
371
|
+
showMessage("No QR codes could be generated", "warning");
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
showMessage("Failed to save QR codes", "error");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await fetchItems();
|
|
378
|
+
|
|
379
|
+
qrCodesToGenerate.value = [];
|
|
380
|
+
qrCodeAccessCards.value = [];
|
|
381
|
+
isGenerating.value = false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function saveQrCodeToStorage(_id: string): Promise<string | null> {
|
|
385
|
+
const canvas = document.querySelector<HTMLCanvasElement>(
|
|
386
|
+
`canvas[data-card-id="${_id}"]`
|
|
387
|
+
);
|
|
388
|
+
if (!canvas) return null;
|
|
389
|
+
|
|
390
|
+
return new Promise((resolve) => {
|
|
391
|
+
canvas.toBlob(async (blob) => {
|
|
392
|
+
if (!blob) return resolve(null);
|
|
393
|
+
const file = new File([blob], `${_id}.png`, { type: "image/png" });
|
|
394
|
+
try {
|
|
395
|
+
const res = await addFile(file);
|
|
396
|
+
resolve(res?.id ? getFileUrl(res.id) : null);
|
|
397
|
+
} catch {
|
|
398
|
+
resolve(null);
|
|
399
|
+
}
|
|
400
|
+
}, "image/png");
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function handlePrintQrCode() {
|
|
405
|
+
const payload = items.value
|
|
406
|
+
.filter((card) => qrCodeAccessCardsToPrint.value.includes(card._id))
|
|
407
|
+
.map((card) => ({
|
|
408
|
+
accessLevel: card.accessLevel,
|
|
409
|
+
cardNumber: card.extractedCardNo,
|
|
410
|
+
qrDataImage: card.qrTag,
|
|
411
|
+
qrTagCardNo: card.qrTagCardNo,
|
|
412
|
+
}));
|
|
413
|
+
|
|
414
|
+
if (!payload.length) return;
|
|
415
|
+
|
|
416
|
+
printPayload.value = payload;
|
|
417
|
+
showPrintDialog.value = true;
|
|
418
|
+
|
|
419
|
+
await nextTick();
|
|
420
|
+
await nextTick();
|
|
421
|
+
|
|
422
|
+
printQrRef.value?.downloadPDF();
|
|
423
|
+
qrCodeAccessCardsToPrint.value = [];
|
|
424
|
+
showMessage("Printing QR codes...", "success");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function handlePrintAll() {
|
|
428
|
+
isGenerating.value = true;
|
|
429
|
+
try {
|
|
430
|
+
const response: any = await getAllVisitorAccessCardsQrTags(siteId.value);
|
|
431
|
+
|
|
432
|
+
if (!response?.data?.items?.length) {
|
|
433
|
+
showMessage("No QR codes found to print", "warning");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
printPayload.value = response.data.items.map((card: any) => ({
|
|
438
|
+
accessLevel: card.accessLevel,
|
|
439
|
+
cardNumber: card.extractedCardNo,
|
|
440
|
+
qrDataImage: card.qrTag,
|
|
441
|
+
qrTagCardNo: card.qrTagCardNo,
|
|
442
|
+
}));
|
|
443
|
+
|
|
444
|
+
showPrintDialog.value = true;
|
|
445
|
+
|
|
446
|
+
await nextTick();
|
|
447
|
+
await nextTick();
|
|
448
|
+
|
|
449
|
+
printQrRef.value?.downloadPDF();
|
|
450
|
+
showMessage("Printing all QR codes...", "success");
|
|
451
|
+
} catch (error: any) {
|
|
452
|
+
showMessage(error || "Failed to fetch QR codes", "error");
|
|
453
|
+
} finally {
|
|
454
|
+
isGenerating.value = false;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
178
458
|
function showMessage(msg: string, color: string) {
|
|
179
459
|
message.value = msg;
|
|
180
460
|
messageColor.value = color;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container ref="printContent" fluid style="overflow-y: auto">
|
|
3
|
+
<v-row no-gutters class="main-container">
|
|
4
|
+
<v-col v-for="(card, idx) in accessCards" :key="idx" cols="auto">
|
|
5
|
+
<div class="qr-box">
|
|
6
|
+
<p><strong>Access Level:</strong> {{ card.accessLevel }}</p>
|
|
7
|
+
<p><strong>HID NO:</strong> {{ card.cardNumber }}</p>
|
|
8
|
+
<img :src="card.qrDataImage" alt="QR Code" />
|
|
9
|
+
<p><strong>Card No.:</strong> C{{ card.qrTagCardNo }}</p>
|
|
10
|
+
</div>
|
|
11
|
+
</v-col>
|
|
12
|
+
</v-row>
|
|
13
|
+
</v-container>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import type { ComponentPublicInstance } from "vue";
|
|
18
|
+
|
|
19
|
+
defineProps<{ accessCards?: any[] }>();
|
|
20
|
+
defineEmits(["close"]);
|
|
21
|
+
|
|
22
|
+
const printContent = ref<HTMLElement | null>(null);
|
|
23
|
+
|
|
24
|
+
async function downloadPDF() {
|
|
25
|
+
const html2pdf = (await import("html2pdf.js")).default;
|
|
26
|
+
const component = printContent.value as unknown as ComponentPublicInstance;
|
|
27
|
+
const el = component.$el ?? printContent.value;
|
|
28
|
+
|
|
29
|
+
if (!el || !(el instanceof HTMLElement)) return;
|
|
30
|
+
|
|
31
|
+
const opt = {
|
|
32
|
+
margin: 0.1,
|
|
33
|
+
filename: `Access-cards-qr.pdf`,
|
|
34
|
+
image: { type: "jpeg", quality: 0.98 },
|
|
35
|
+
html2canvas: { scale: 2, useCORS: true },
|
|
36
|
+
jsPDF: { unit: "in", format: "a4", orientation: "portrait" },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
await html2pdf().set(opt).from(el).save();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
defineExpose({ downloadPDF });
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<style scoped>
|
|
46
|
+
.main-container {
|
|
47
|
+
background-color: #fff;
|
|
48
|
+
padding: 8px;
|
|
49
|
+
display: grid;
|
|
50
|
+
grid-template-columns: repeat(6, 1fr);
|
|
51
|
+
gap: 4px;
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
.qr-box {
|
|
55
|
+
height: 3cm;
|
|
56
|
+
width: 2.7cm;
|
|
57
|
+
border: 1px solid #ccc;
|
|
58
|
+
padding: 8px;
|
|
59
|
+
text-align: center;
|
|
60
|
+
page-break-inside: avoid;
|
|
61
|
+
margin: 0 auto;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 6px;
|
|
66
|
+
}
|
|
67
|
+
.qr-box p {
|
|
68
|
+
font-size: 8px;
|
|
69
|
+
line-height: 1.2;
|
|
70
|
+
}
|
|
71
|
+
img {
|
|
72
|
+
width: 1cm;
|
|
73
|
+
height: 1cm;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<v-toolbar>
|
|
5
5
|
<v-row no-gutters class="fill-height px-6" align="center">
|
|
6
6
|
<span class="font-weight-bold text-h6 text-capitalize">
|
|
7
|
-
{{ prop.mode === "edit" ? "Edit
|
|
7
|
+
{{ prop.mode === "edit" ? "Edit Equipment" : "Add New Equipment" }}
|
|
8
8
|
</span>
|
|
9
9
|
</v-row>
|
|
10
10
|
</v-toolbar>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
:rules="[requiredRule]"
|
|
20
20
|
density="comfortable"
|
|
21
21
|
variant="outlined"
|
|
22
|
-
placeholder="Enter
|
|
22
|
+
placeholder="Enter equipment name"
|
|
23
23
|
hide-details="auto"
|
|
24
24
|
class="mb-0"
|
|
25
25
|
/>
|
|
@@ -94,7 +94,7 @@ const prop = defineProps({
|
|
|
94
94
|
type: String,
|
|
95
95
|
default: "add",
|
|
96
96
|
},
|
|
97
|
-
|
|
97
|
+
equipmentData: {
|
|
98
98
|
type: Object,
|
|
99
99
|
default: null,
|
|
100
100
|
},
|
|
@@ -122,8 +122,8 @@ const unitOfMeasurementOptions = [
|
|
|
122
122
|
];
|
|
123
123
|
|
|
124
124
|
watchEffect(() => {
|
|
125
|
-
nameModel.value = prop.
|
|
126
|
-
unitOfMeasurementModel.value = prop.
|
|
125
|
+
nameModel.value = prop.equipmentData?.name || "";
|
|
126
|
+
unitOfMeasurementModel.value = prop.equipmentData?.unitOfMeasurement || "";
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
function close() {
|
|
@@ -119,6 +119,15 @@
|
|
|
119
119
|
class="text-subtitle-2 font-weight-bold px-0"
|
|
120
120
|
>
|
|
121
121
|
Set {{ checklistSet.set }}
|
|
122
|
+
<v-chip
|
|
123
|
+
v-if="checklistSet.isScheduleTask"
|
|
124
|
+
size="x-small"
|
|
125
|
+
color="info"
|
|
126
|
+
variant="tonal"
|
|
127
|
+
class="text-none ml-2"
|
|
128
|
+
>
|
|
129
|
+
Schedule Task
|
|
130
|
+
</v-chip>
|
|
122
131
|
</v-list-subheader>
|
|
123
132
|
|
|
124
133
|
<v-list-item
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<v-toolbar>
|
|
4
4
|
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
5
|
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
-
{{ prop.mode }}
|
|
6
|
+
{{ prop.mode }} Block
|
|
7
7
|
</span>
|
|
8
8
|
</v-row>
|
|
9
9
|
</v-toolbar>
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
<v-select
|
|
41
41
|
v-model.number="building.block"
|
|
42
42
|
:items="blocks"
|
|
43
|
+
item-title="label"
|
|
44
|
+
item-value="value"
|
|
43
45
|
density="comfortable"
|
|
46
|
+
:disabled="prop.mode === 'edit'"
|
|
44
47
|
:rules="[
|
|
45
48
|
requiredRule,
|
|
46
49
|
() =>
|
|
@@ -48,7 +51,12 @@
|
|
|
48
51
|
'Block is required',
|
|
49
52
|
]"
|
|
50
53
|
type="number"
|
|
51
|
-
|
|
54
|
+
>
|
|
55
|
+
<template v-slot:item="{ props: itemProps, item}">
|
|
56
|
+
<v-list-item v-bind="itemProps" :disabled="item?.raw?.disabled" ></v-list-item>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
</v-select>
|
|
52
60
|
</v-col>
|
|
53
61
|
</v-row>
|
|
54
62
|
</v-col>
|
|
@@ -114,10 +122,10 @@
|
|
|
114
122
|
</v-row>
|
|
115
123
|
</v-expand-transition>
|
|
116
124
|
|
|
117
|
-
<v-col cols="12" class="px-6">
|
|
125
|
+
<!-- <v-col cols="12" class="px-6">
|
|
118
126
|
<InputLabel class="text-capitalize" title="Building Floor plan" />
|
|
119
127
|
<InputFileV2 v-model="building.buildingFloorPlan" :multiple="true" :max-length="10" title="Upload Images" />
|
|
120
|
-
</v-col>
|
|
128
|
+
</v-col> -->
|
|
121
129
|
|
|
122
130
|
<v-col cols="12" class="mt-2">
|
|
123
131
|
<v-checkbox v-model="createMore" density="comfortable" hide-details>
|
|
@@ -199,6 +207,10 @@ const prop = defineProps({
|
|
|
199
207
|
buildingFloorPlan: []
|
|
200
208
|
}),
|
|
201
209
|
},
|
|
210
|
+
blocks: {
|
|
211
|
+
type: Array as PropType<TBuilding[]>,
|
|
212
|
+
default: () => [],
|
|
213
|
+
},
|
|
202
214
|
});
|
|
203
215
|
|
|
204
216
|
const { getSiteById } = useSite();
|
|
@@ -218,7 +230,20 @@ watchEffect(() => {
|
|
|
218
230
|
|
|
219
231
|
const blocks = computed(() => {
|
|
220
232
|
if (!site.value) return [];
|
|
221
|
-
return
|
|
233
|
+
// return
|
|
234
|
+
// return [ { label: "1", value: 1, disabled: true }, { label: "2", value: 2 }, { label: "3", value: 3 } ];
|
|
235
|
+
const array = Array.from({ length: site.value?.metadata?.block || 0 }, (_, i) => i + 1);
|
|
236
|
+
|
|
237
|
+
const formattedArray = array.map((num) => {
|
|
238
|
+
const isDisabled = prop.blocks.some((block) => block.block === num)
|
|
239
|
+
return {
|
|
240
|
+
label: num.toString(),
|
|
241
|
+
value: num,
|
|
242
|
+
disabled: isDisabled
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return formattedArray;
|
|
222
247
|
});
|
|
223
248
|
|
|
224
249
|
const emit = defineEmits(["cancel", "success", "success:create-more"]);
|
|
@@ -295,6 +320,12 @@ async function submit() {
|
|
|
295
320
|
}
|
|
296
321
|
}
|
|
297
322
|
|
|
323
|
+
watch(buildingLevels, (newVal) => {
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
show.value = newVal > 0 && !!newVal;
|
|
326
|
+
}, 500);
|
|
327
|
+
});
|
|
328
|
+
|
|
298
329
|
function cancel() {
|
|
299
330
|
createMore.value = false;
|
|
300
331
|
message.value = "";
|