@7365admin1/layer-common 1.10.4 → 1.10.6
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/AccessCardDeleteDialog.vue +109 -0
- package/components/AccessCardDetailsDialog.vue +144 -0
- package/components/AccessCardPreviewDialog.vue +18 -2
- package/components/AccessCardQrTagging.vue +183 -0
- package/components/AccessManagement.vue +22 -57
- package/components/BulletinBoardManagement.vue +6 -1
- package/components/CleaningScheduleMain.vue +18 -7
- package/components/IncidentReport/affectedEntities.vue +29 -0
- package/components/Input/InputPhoneNumberV2.vue +3 -0
- package/components/PlateNumberDisplay.vue +53 -0
- package/components/ScheduleAreaMain.vue +3 -0
- package/components/TableHygiene.vue +238 -113
- package/components/VehicleAddSelection.vue +58 -0
- package/components/VehicleForm.vue +663 -0
- package/components/VehicleManagement.vue +300 -0
- package/components/VisitorManagement.vue +1 -1
- package/components/WorkOrder/Main.vue +2 -1
- package/composables/useAccessManagement.ts +20 -1
- package/composables/useBulletin.ts +6 -4
- package/composables/useFeedback.ts +2 -2
- package/composables/usePeople.ts +10 -0
- package/composables/useVehicle.ts +114 -0
- package/composables/useWorkOrder.ts +2 -2
- package/package.json +1 -1
- package/types/people.d.ts +3 -0
- package/types/vehicle.d.ts +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.10.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 49fefa8: Update layer-common package for March 9,2026
|
|
8
|
+
|
|
9
|
+
## 1.10.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- eaec446: Update changes for March 6, 2024
|
|
14
|
+
|
|
3
15
|
## 1.10.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog :model-value="modelValue" width="450" persistent>
|
|
3
|
+
<v-card width="100%">
|
|
4
|
+
<v-toolbar density="compact" class="pl-4">
|
|
5
|
+
<span class="font-weight-medium text-h5">Delete Card</span>
|
|
6
|
+
</v-toolbar>
|
|
7
|
+
|
|
8
|
+
<v-card-text>
|
|
9
|
+
<p class="text-subtitle-2 text-center mb-4">
|
|
10
|
+
Are you sure you want to delete this card? This action cannot be
|
|
11
|
+
undone.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<v-form v-model="validForm" :disabled="loading">
|
|
15
|
+
<InputLabel class="text-capitalize font-weight-bold" title="Remarks" required />
|
|
16
|
+
<v-textarea
|
|
17
|
+
v-model="remarks"
|
|
18
|
+
placeholder="Enter remarks..."
|
|
19
|
+
persistent-placeholder
|
|
20
|
+
rows="3"
|
|
21
|
+
auto-grow
|
|
22
|
+
hide-details="auto"
|
|
23
|
+
:rules="[requiredRule]"
|
|
24
|
+
/>
|
|
25
|
+
</v-form>
|
|
26
|
+
|
|
27
|
+
<v-row v-if="error" no-gutters justify="center" class="mt-3">
|
|
28
|
+
<span class="text-caption text-error text-center">{{ error }}</span>
|
|
29
|
+
</v-row>
|
|
30
|
+
</v-card-text>
|
|
31
|
+
|
|
32
|
+
<v-toolbar density="compact">
|
|
33
|
+
<v-row no-gutters>
|
|
34
|
+
<v-col cols="6">
|
|
35
|
+
<v-btn
|
|
36
|
+
tile
|
|
37
|
+
block
|
|
38
|
+
size="48"
|
|
39
|
+
variant="text"
|
|
40
|
+
class="text-none"
|
|
41
|
+
:disabled="loading"
|
|
42
|
+
@click="handleClose"
|
|
43
|
+
>
|
|
44
|
+
Close
|
|
45
|
+
</v-btn>
|
|
46
|
+
</v-col>
|
|
47
|
+
<v-col cols="6">
|
|
48
|
+
<v-btn
|
|
49
|
+
tile
|
|
50
|
+
block
|
|
51
|
+
size="48"
|
|
52
|
+
color="black"
|
|
53
|
+
variant="flat"
|
|
54
|
+
class="text-none"
|
|
55
|
+
:loading="loading"
|
|
56
|
+
:disabled="!validForm || loading"
|
|
57
|
+
@click="handleConfirm"
|
|
58
|
+
>
|
|
59
|
+
Delete Card
|
|
60
|
+
</v-btn>
|
|
61
|
+
</v-col>
|
|
62
|
+
</v-row>
|
|
63
|
+
</v-toolbar>
|
|
64
|
+
</v-card>
|
|
65
|
+
</v-dialog>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
const props = defineProps({
|
|
70
|
+
modelValue: {
|
|
71
|
+
type: Boolean,
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
74
|
+
loading: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
78
|
+
error: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: "",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const emit = defineEmits<{
|
|
85
|
+
"update:modelValue": [value: boolean];
|
|
86
|
+
confirm: [remarks: string];
|
|
87
|
+
}>();
|
|
88
|
+
|
|
89
|
+
const { requiredRule } = useUtils();
|
|
90
|
+
|
|
91
|
+
const validForm = ref(false);
|
|
92
|
+
const remarks = ref("");
|
|
93
|
+
|
|
94
|
+
watch(
|
|
95
|
+
() => props.modelValue,
|
|
96
|
+
(val) => {
|
|
97
|
+
if (val) remarks.value = "";
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
function handleClose() {
|
|
102
|
+
emit("update:modelValue", false);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleConfirm() {
|
|
106
|
+
if (!validForm.value) return;
|
|
107
|
+
emit("confirm", remarks.value.trim());
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog :model-value="modelValue" width="480" persistent>
|
|
3
|
+
<v-card width="100%">
|
|
4
|
+
<v-toolbar density="compact" color="black">
|
|
5
|
+
<v-toolbar-title class="text-subtitle-1 font-weight-medium">
|
|
6
|
+
Card Details — {{ card?.cardNo ?? "N/A" }}
|
|
7
|
+
</v-toolbar-title>
|
|
8
|
+
<v-btn icon @click="emit('update:modelValue', false)">
|
|
9
|
+
<v-icon>mdi-close</v-icon>
|
|
10
|
+
</v-btn>
|
|
11
|
+
</v-toolbar>
|
|
12
|
+
|
|
13
|
+
<v-card-text style="max-height: 70vh; overflow-y: auto" class="pa-4">
|
|
14
|
+
<div v-if="pending" class="d-flex justify-center align-center py-8">
|
|
15
|
+
<v-progress-circular indeterminate color="black" />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div v-else-if="!details" class="text-center text-grey py-8">
|
|
19
|
+
No details available.
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div v-else class="d-flex flex-column ga-3">
|
|
23
|
+
<div>
|
|
24
|
+
<div class="text-caption text-grey">Card No</div>
|
|
25
|
+
<div class="text-body-2 font-weight-medium">{{ details.cardNo ?? "N/A" }}</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div>
|
|
29
|
+
<div class="text-caption text-grey">Type</div>
|
|
30
|
+
<div class="text-body-2 font-weight-medium">{{ details.type ?? "N/A" }}</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div>
|
|
34
|
+
<div class="text-caption text-grey">Status</div>
|
|
35
|
+
<v-chip
|
|
36
|
+
:color="statusColor(details.status)"
|
|
37
|
+
variant="flat"
|
|
38
|
+
size="x-small"
|
|
39
|
+
class="text-capitalize font-weight-medium mt-1"
|
|
40
|
+
>
|
|
41
|
+
{{ details.status ?? "N/A" }}
|
|
42
|
+
</v-chip>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div>
|
|
46
|
+
<div class="text-caption text-grey">Activated</div>
|
|
47
|
+
<div class="text-body-2 font-weight-medium">{{ details.isActivated ? "Yes" : "No" }}</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div>
|
|
51
|
+
<div class="text-caption text-grey">Site</div>
|
|
52
|
+
<div class="text-body-2 font-weight-medium">{{ details.site?.name ?? "N/A" }}</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div>
|
|
56
|
+
<div class="text-caption text-grey">User</div>
|
|
57
|
+
<div class="text-body-2 font-weight-medium">{{ details.user?.name ?? "N/A" }} ({{ details.user?.email ?? "N/A" }})</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div>
|
|
61
|
+
<div class="text-caption text-grey">Created At</div>
|
|
62
|
+
<div class="text-body-2 font-weight-medium">{{ formatDate(details.createdAt) }}</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div>
|
|
66
|
+
<div class="text-caption text-grey">Updated At</div>
|
|
67
|
+
<div class="text-body-2 font-weight-medium">{{ formatDate(details.updatedAt) }}</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div v-if="details.remarks">
|
|
71
|
+
<div class="text-caption text-grey">Remarks</div>
|
|
72
|
+
<div class="text-body-2 font-weight-medium">{{ details.remarks }}</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</v-card-text>
|
|
76
|
+
</v-card>
|
|
77
|
+
</v-dialog>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<script setup lang="ts">
|
|
81
|
+
const props = defineProps({
|
|
82
|
+
modelValue: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false,
|
|
85
|
+
},
|
|
86
|
+
card: {
|
|
87
|
+
type: Object as PropType<Record<string, any> | null>,
|
|
88
|
+
default: null,
|
|
89
|
+
},
|
|
90
|
+
siteId: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: "",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const emit = defineEmits<{
|
|
97
|
+
"update:modelValue": [value: boolean];
|
|
98
|
+
}>();
|
|
99
|
+
|
|
100
|
+
const { getCardDetails } = useAccessManagement();
|
|
101
|
+
|
|
102
|
+
const details = ref<Record<string, any> | null>(null);
|
|
103
|
+
const pending = ref(false);
|
|
104
|
+
|
|
105
|
+
watch(
|
|
106
|
+
() => props.modelValue,
|
|
107
|
+
async (val) => {
|
|
108
|
+
if (val && props.card?._id && props.siteId) {
|
|
109
|
+
pending.value = true;
|
|
110
|
+
try {
|
|
111
|
+
const res = await getCardDetails({ siteId: props.siteId, cardId: props.card._id });
|
|
112
|
+
details.value = res?.data ?? null;
|
|
113
|
+
} finally {
|
|
114
|
+
pending.value = false;
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
details.value = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
function statusColor(status: string) {
|
|
123
|
+
const map: Record<string, string> = {
|
|
124
|
+
active: "success",
|
|
125
|
+
assigned: "primary",
|
|
126
|
+
replaced: "orange",
|
|
127
|
+
deleted: "error",
|
|
128
|
+
available: "grey",
|
|
129
|
+
};
|
|
130
|
+
return map[status] ?? "grey";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatDate(date: string) {
|
|
134
|
+
if (!date) return "N/A";
|
|
135
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
136
|
+
day: "2-digit",
|
|
137
|
+
month: "short",
|
|
138
|
+
year: "numeric",
|
|
139
|
+
hour: "2-digit",
|
|
140
|
+
minute: "2-digit",
|
|
141
|
+
hour12: true,
|
|
142
|
+
}).format(new Date(date));
|
|
143
|
+
}
|
|
144
|
+
</script>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<AccessCardDetailsDialog
|
|
3
|
+
v-model="historyDialog"
|
|
4
|
+
:card="selectedCardInUnit"
|
|
5
|
+
:site-id="siteId"
|
|
6
|
+
/>
|
|
7
|
+
|
|
2
8
|
<v-dialog :model-value="modelValue" width="450" persistent>
|
|
3
9
|
<v-card width="100%">
|
|
4
10
|
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
|
|
@@ -214,9 +220,9 @@
|
|
|
214
220
|
Replace Card
|
|
215
221
|
</v-list-item-title>
|
|
216
222
|
</v-list-item>
|
|
217
|
-
<v-list-item :disabled="
|
|
223
|
+
<v-list-item :disabled="!isSelectedCardPhysical" @click="historyDialog = true">
|
|
218
224
|
<v-list-item-title class="text-subtitle-2 cursor-pointer">
|
|
219
|
-
Card
|
|
225
|
+
Card Details
|
|
220
226
|
</v-list-item-title>
|
|
221
227
|
</v-list-item>
|
|
222
228
|
<v-list-item
|
|
@@ -268,6 +274,14 @@ const props = defineProps({
|
|
|
268
274
|
type: Boolean,
|
|
269
275
|
default: false,
|
|
270
276
|
},
|
|
277
|
+
isSelectedCardPhysical: {
|
|
278
|
+
type: Boolean,
|
|
279
|
+
default: false,
|
|
280
|
+
},
|
|
281
|
+
siteId: {
|
|
282
|
+
type: String,
|
|
283
|
+
default: "",
|
|
284
|
+
},
|
|
271
285
|
});
|
|
272
286
|
|
|
273
287
|
const emit = defineEmits<{
|
|
@@ -277,6 +291,8 @@ const emit = defineEmits<{
|
|
|
277
291
|
delete: [];
|
|
278
292
|
}>();
|
|
279
293
|
|
|
294
|
+
const historyDialog = ref(false);
|
|
295
|
+
|
|
280
296
|
const isSelectedCardDeletable = computed(() => {
|
|
281
297
|
if (!props.selectedCardInUnit?._id) return false;
|
|
282
298
|
const id = props.selectedCardInUnit._id;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12" class="mb-2">
|
|
4
|
+
<v-row no-gutters align="center" justify="space-between">
|
|
5
|
+
<v-row no-gutters class="ga-2">
|
|
6
|
+
<v-btn
|
|
7
|
+
class="text-none"
|
|
8
|
+
rounded="pill"
|
|
9
|
+
variant="tonal"
|
|
10
|
+
size="large"
|
|
11
|
+
:disabled="!items.length"
|
|
12
|
+
>
|
|
13
|
+
Print All
|
|
14
|
+
</v-btn>
|
|
15
|
+
<v-btn
|
|
16
|
+
class="text-none"
|
|
17
|
+
rounded="pill"
|
|
18
|
+
variant="tonal"
|
|
19
|
+
size="large"
|
|
20
|
+
:disabled="!selected.length"
|
|
21
|
+
>
|
|
22
|
+
Generate QR Code
|
|
23
|
+
</v-btn>
|
|
24
|
+
<v-btn
|
|
25
|
+
class="text-none"
|
|
26
|
+
rounded="pill"
|
|
27
|
+
variant="tonal"
|
|
28
|
+
size="large"
|
|
29
|
+
:disabled="!selected.length"
|
|
30
|
+
>
|
|
31
|
+
Print QR Code
|
|
32
|
+
</v-btn>
|
|
33
|
+
</v-row>
|
|
34
|
+
|
|
35
|
+
<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
|
+
<v-text-field
|
|
47
|
+
v-model="searchText"
|
|
48
|
+
placeholder="Search Card..."
|
|
49
|
+
variant="outlined"
|
|
50
|
+
density="comfortable"
|
|
51
|
+
clearable
|
|
52
|
+
hide-details
|
|
53
|
+
style="max-width: 240px"
|
|
54
|
+
/>
|
|
55
|
+
</v-row>
|
|
56
|
+
</v-row>
|
|
57
|
+
</v-col>
|
|
58
|
+
|
|
59
|
+
<v-col cols="12">
|
|
60
|
+
<v-card
|
|
61
|
+
width="100%"
|
|
62
|
+
variant="outlined"
|
|
63
|
+
border="thin"
|
|
64
|
+
rounded="lg"
|
|
65
|
+
:loading="loading"
|
|
66
|
+
>
|
|
67
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
68
|
+
<template #prepend>
|
|
69
|
+
<v-btn fab icon density="comfortable" @click="fetchItems">
|
|
70
|
+
<v-icon>mdi-refresh</v-icon>
|
|
71
|
+
</v-btn>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<template #append>
|
|
75
|
+
<v-row no-gutters justify="end" align="center">
|
|
76
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
77
|
+
{{ pageRange }}
|
|
78
|
+
</span>
|
|
79
|
+
<local-pagination
|
|
80
|
+
v-model="page"
|
|
81
|
+
:length="pages"
|
|
82
|
+
@update:value="fetchItems"
|
|
83
|
+
/>
|
|
84
|
+
</v-row>
|
|
85
|
+
</template>
|
|
86
|
+
</v-toolbar>
|
|
87
|
+
|
|
88
|
+
<v-data-table
|
|
89
|
+
v-model="selected"
|
|
90
|
+
:headers="tableHeaders"
|
|
91
|
+
:items="items"
|
|
92
|
+
item-value="_id"
|
|
93
|
+
items-per-page="10"
|
|
94
|
+
fixed-header
|
|
95
|
+
hide-default-footer
|
|
96
|
+
show-select
|
|
97
|
+
style="max-height: calc(100vh - 200px)"
|
|
98
|
+
>
|
|
99
|
+
<template #item.card="{ item }">
|
|
100
|
+
<v-row no-gutters align="center" class="ga-2">
|
|
101
|
+
<v-icon size="18" color="grey-darken-1">mdi-card-account-details</v-icon>
|
|
102
|
+
<span>{{ item.hid ?? "N/A" }}</span>
|
|
103
|
+
</v-row>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<template #item.qrCode="{ item }">
|
|
107
|
+
<v-icon
|
|
108
|
+
:color="item.qrCode ? 'success' : 'grey-lighten-1'"
|
|
109
|
+
size="22"
|
|
110
|
+
>
|
|
111
|
+
{{ item.qrCode ? "mdi-check-circle" : "mdi-circle-outline" }}
|
|
112
|
+
</v-icon>
|
|
113
|
+
</template>
|
|
114
|
+
</v-data-table>
|
|
115
|
+
</v-card>
|
|
116
|
+
</v-col>
|
|
117
|
+
|
|
118
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
119
|
+
</v-row>
|
|
120
|
+
</template>
|
|
121
|
+
|
|
122
|
+
<script setup lang="ts">
|
|
123
|
+
definePageMeta({
|
|
124
|
+
middleware: ["01-auth", "02-org"],
|
|
125
|
+
memberOnly: true,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const tableHeaders = [
|
|
129
|
+
{ title: "Card", key: "card", sortable: false },
|
|
130
|
+
{ title: "Location", key: "location", sortable: false },
|
|
131
|
+
{ title: "Card No.", key: "cardNo", sortable: false },
|
|
132
|
+
{ title: "HID", key: "hid", sortable: false },
|
|
133
|
+
{ title: "QR Code", key: "qrCode", sortable: false },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const typeOptions = ["Physical", "Non-Physical"];
|
|
137
|
+
|
|
138
|
+
const route = useRoute();
|
|
139
|
+
const siteId = computed(() => route.params.site as string);
|
|
140
|
+
const orgId = computed(() => route.params.org as string);
|
|
141
|
+
|
|
142
|
+
const page = ref(1);
|
|
143
|
+
const pages = ref(0);
|
|
144
|
+
const pageRange = ref("-- - -- of --");
|
|
145
|
+
|
|
146
|
+
const message = ref("");
|
|
147
|
+
const messageSnackbar = ref(false);
|
|
148
|
+
const messageColor = ref("");
|
|
149
|
+
|
|
150
|
+
const items = ref<Record<string, any>[]>([]);
|
|
151
|
+
const selected = ref<string[]>([]);
|
|
152
|
+
const searchText = ref("");
|
|
153
|
+
const typeFilter = ref<string | null>(null);
|
|
154
|
+
|
|
155
|
+
const {
|
|
156
|
+
data: qrTaggingReq,
|
|
157
|
+
refresh: fetchItems,
|
|
158
|
+
status: fetchStatus,
|
|
159
|
+
} = useLazyAsyncData(
|
|
160
|
+
"get-qr-tagging",
|
|
161
|
+
() => {
|
|
162
|
+
// TODO: wire up QR tagging API
|
|
163
|
+
return Promise.resolve({ data: { items: [], pages: 0, pageRange: "0 - 0 of 0" } });
|
|
164
|
+
},
|
|
165
|
+
{ watch: [page, searchText, typeFilter] }
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const loading = computed(() => fetchStatus.value === "pending");
|
|
169
|
+
|
|
170
|
+
watchEffect(() => {
|
|
171
|
+
if (qrTaggingReq.value?.data) {
|
|
172
|
+
items.value = qrTaggingReq.value.data.items;
|
|
173
|
+
pages.value = qrTaggingReq.value.data.pages;
|
|
174
|
+
pageRange.value = qrTaggingReq.value.data.pageRange;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
function showMessage(msg: string, color: string) {
|
|
179
|
+
message.value = msg;
|
|
180
|
+
messageColor.value = color;
|
|
181
|
+
messageSnackbar.value = true;
|
|
182
|
+
}
|
|
183
|
+
</script>
|
|
@@ -111,6 +111,8 @@
|
|
|
111
111
|
:can-replace-access-card="canReplaceAccessCard"
|
|
112
112
|
:can-delete-access-card="canDeleteAccessCard"
|
|
113
113
|
:is-selected-card-assigned-physical="isSelectedCardAssignedPhysical"
|
|
114
|
+
:is-selected-card-physical="isSelectedCardPhysical"
|
|
115
|
+
:site-id="siteId"
|
|
114
116
|
@replace="openReplaceDialog()"
|
|
115
117
|
@delete="openDeleteDialog()"
|
|
116
118
|
/>
|
|
@@ -129,61 +131,12 @@
|
|
|
129
131
|
</v-dialog>
|
|
130
132
|
|
|
131
133
|
<!-- Delete Dialog -->
|
|
132
|
-
<
|
|
134
|
+
<AccessCardDeleteDialog
|
|
133
135
|
v-model="confirmDialog"
|
|
134
136
|
:loading="deleteLoading"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<v-card width="100%">
|
|
139
|
-
<v-toolbar density="compact" class="pl-4">
|
|
140
|
-
<span class="font-weight-medium text-h5">Delete Card</span>
|
|
141
|
-
</v-toolbar>
|
|
142
|
-
<v-card-text>
|
|
143
|
-
<p class="text-subtitle-2 text-center">
|
|
144
|
-
Are you sure you want to delete this card? This action cannot be
|
|
145
|
-
undone.
|
|
146
|
-
</p>
|
|
147
|
-
|
|
148
|
-
<v-row v-if="message" no-gutters justify="center" class="mt-4">
|
|
149
|
-
<span class="text-caption text-error text-center">
|
|
150
|
-
{{ message }}
|
|
151
|
-
</span>
|
|
152
|
-
</v-row></v-card-text
|
|
153
|
-
>
|
|
154
|
-
<v-toolbar density="compact">
|
|
155
|
-
<v-row no-gutters>
|
|
156
|
-
<v-col cols="6">
|
|
157
|
-
<v-btn
|
|
158
|
-
tile
|
|
159
|
-
block
|
|
160
|
-
size="48"
|
|
161
|
-
variant="text"
|
|
162
|
-
class="text-none"
|
|
163
|
-
@click="confirmDialog = false"
|
|
164
|
-
:disabled="deleteLoading"
|
|
165
|
-
>
|
|
166
|
-
Close
|
|
167
|
-
</v-btn>
|
|
168
|
-
</v-col>
|
|
169
|
-
<v-col cols="6">
|
|
170
|
-
<v-btn
|
|
171
|
-
tile
|
|
172
|
-
block
|
|
173
|
-
size="48"
|
|
174
|
-
color="black"
|
|
175
|
-
variant="flat"
|
|
176
|
-
class="text-none"
|
|
177
|
-
@click="handleDeleteCard"
|
|
178
|
-
:loading="deleteLoading"
|
|
179
|
-
>
|
|
180
|
-
Delete Card
|
|
181
|
-
</v-btn>
|
|
182
|
-
</v-col>
|
|
183
|
-
</v-row></v-toolbar
|
|
184
|
-
>
|
|
185
|
-
</v-card>
|
|
186
|
-
</v-dialog>
|
|
137
|
+
:error="deleteError"
|
|
138
|
+
@confirm="handleDeleteCard"
|
|
139
|
+
/>
|
|
187
140
|
|
|
188
141
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
189
142
|
</v-row>
|
|
@@ -276,6 +229,7 @@ const createDialog = ref(false);
|
|
|
276
229
|
const assignDialog = ref(false);
|
|
277
230
|
const previewDialog = ref(false);
|
|
278
231
|
const deleteLoading = ref(false);
|
|
232
|
+
const deleteError = ref("");
|
|
279
233
|
const confirmDialog = ref(false);
|
|
280
234
|
const searchText = ref("");
|
|
281
235
|
const replaceDialog = ref(false);
|
|
@@ -300,6 +254,14 @@ const isSelectedCardAssignedPhysical = computed(() =>
|
|
|
300
254
|
) ?? false
|
|
301
255
|
);
|
|
302
256
|
|
|
257
|
+
const isSelectedCardPhysical = computed(() =>
|
|
258
|
+
["available", "assigned", "replaced", "deleted"].some((state) =>
|
|
259
|
+
selectedCard.value?.[state]?.physical?.some(
|
|
260
|
+
(c: any) => c._id === selectedCardInUnit.value?._id
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
|
|
303
265
|
const { getUserTypeAccessCards, deleteCard: _deleteCard } = useAccessManagement();
|
|
304
266
|
|
|
305
267
|
const statsRef = ref<{ refresh: () => void } | null>(null);
|
|
@@ -369,7 +331,7 @@ function tableRowClickHandler(_: any, data: any) {
|
|
|
369
331
|
|
|
370
332
|
function openDeleteDialog() {
|
|
371
333
|
confirmDialog.value = true;
|
|
372
|
-
|
|
334
|
+
deleteError.value = "";
|
|
373
335
|
}
|
|
374
336
|
|
|
375
337
|
function openReplaceDialog() {
|
|
@@ -384,17 +346,20 @@ function successReplace() {
|
|
|
384
346
|
showMessage("Access card replaced successfully!", "success");
|
|
385
347
|
}
|
|
386
348
|
|
|
387
|
-
async function handleDeleteCard() {
|
|
349
|
+
async function handleDeleteCard(remarks: string) {
|
|
388
350
|
deleteLoading.value = true;
|
|
389
351
|
try {
|
|
390
|
-
await _deleteCard({ cardId: selectedCardInUnit.value?._id ?? "" });
|
|
352
|
+
await _deleteCard({ cardId: selectedCardInUnit.value?._id ?? "", remarks });
|
|
391
353
|
await getCards();
|
|
392
354
|
statsRef.value?.refresh();
|
|
393
355
|
selectedCardId.value = null;
|
|
394
356
|
confirmDialog.value = false;
|
|
395
357
|
previewDialog.value = false;
|
|
358
|
+
showMessage("Access card deleted successfully!", "success");
|
|
396
359
|
} catch (error: any) {
|
|
397
|
-
|
|
360
|
+
const msg = error?.response?._data?.message || "Failed to delete card";
|
|
361
|
+
deleteError.value = msg;
|
|
362
|
+
showMessage(msg, "error");
|
|
398
363
|
} finally {
|
|
399
364
|
deleteLoading.value = false;
|
|
400
365
|
}
|
|
@@ -83,6 +83,11 @@ const props = defineProps({
|
|
|
83
83
|
}, canDeleteBulletinBoard: {
|
|
84
84
|
type: Boolean,
|
|
85
85
|
default: true
|
|
86
|
+
},
|
|
87
|
+
recipients: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: "",
|
|
90
|
+
required: false
|
|
86
91
|
}
|
|
87
92
|
})
|
|
88
93
|
|
|
@@ -158,7 +163,7 @@ const {
|
|
|
158
163
|
pending: getAnnouncementPending,
|
|
159
164
|
} = await useLazyAsyncData(
|
|
160
165
|
`get-all-announcements-${page.value}`,
|
|
161
|
-
() => getAll({ page: page.value, site: siteId, status: status.value }),
|
|
166
|
+
() => getAll({ page: page.value, site: siteId, status: status.value, recipients: props.recipients }),
|
|
162
167
|
{
|
|
163
168
|
watch: [page, () => route.query],
|
|
164
169
|
}
|