@iservice365/layer-common 1.6.0 → 1.7.0
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 +6 -0
- package/components/AccessCardAddForm.vue +363 -0
- package/components/AccessManagement.vue +286 -8
- package/components/BuildingUnitFormEdit.vue +47 -0
- package/components/EntryPassMain.vue +231 -0
- package/components/ImageCarousel.vue +74 -34
- package/components/Input/FileV2.vue +1 -1
- package/components/Input/NRICNumber.vue +2 -2
- package/components/Input/VehicleNumber.vue +1 -1
- package/components/Nfc/NFCTagForm.vue +210 -0
- package/components/Nfc/NFCTagMain.vue +342 -0
- package/components/VideoPlayer.vue +125 -0
- package/components/VisitorForm.vue +143 -62
- package/components/VisitorManagement.vue +1 -14
- package/composables/useCard.ts +46 -0
- package/composables/useNFCPatrolTag.ts +51 -0
- package/composables/usePeople.ts +19 -1
- package/composables/useVisitor.ts +1 -1
- package/package.json +1 -1
- package/types/building.d.ts +2 -0
- package/types/card.d.ts +22 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12">
|
|
4
|
+
<v-card width="100%" variant="outlined" border="thin" rounded="lg">
|
|
5
|
+
<v-toolbar density="compact" color="grey-lighten-4 pl-2 pr-4">
|
|
6
|
+
<template #prepend>
|
|
7
|
+
<v-btn fab icon density="comfortable" @click="getNFCTagRefresh()">
|
|
8
|
+
<v-icon>mdi-refresh</v-icon>
|
|
9
|
+
</v-btn>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #append>
|
|
13
|
+
<v-btn
|
|
14
|
+
variant="flat"
|
|
15
|
+
color="primary"
|
|
16
|
+
class="text-none"
|
|
17
|
+
@click="dialogAdd = true"
|
|
18
|
+
>
|
|
19
|
+
Add
|
|
20
|
+
</v-btn>
|
|
21
|
+
</template>
|
|
22
|
+
</v-toolbar>
|
|
23
|
+
|
|
24
|
+
<v-card-text class="pa-0">
|
|
25
|
+
<v-data-table
|
|
26
|
+
:headers="headers"
|
|
27
|
+
:items="items"
|
|
28
|
+
fixed-header
|
|
29
|
+
hide-default-footer
|
|
30
|
+
@click:row="handleRowClick"
|
|
31
|
+
style="max-height: 300px"
|
|
32
|
+
>
|
|
33
|
+
</v-data-table>
|
|
34
|
+
</v-card-text>
|
|
35
|
+
|
|
36
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
37
|
+
<template #append>
|
|
38
|
+
<v-row no-gutters justify="end" align="center">
|
|
39
|
+
<span class="mr-2 text-caption text-font gray">
|
|
40
|
+
{{ pageRange }}
|
|
41
|
+
</span>
|
|
42
|
+
<local-pagination
|
|
43
|
+
v-model="page"
|
|
44
|
+
:length="pages"
|
|
45
|
+
@update:value="$emit('update:value', $event)"
|
|
46
|
+
/>
|
|
47
|
+
</v-row>
|
|
48
|
+
</template>
|
|
49
|
+
</v-toolbar>
|
|
50
|
+
</v-card>
|
|
51
|
+
</v-col>
|
|
52
|
+
|
|
53
|
+
<!-- Add Dialog -->
|
|
54
|
+
<v-dialog v-model="dialogAdd" persistent width="450">
|
|
55
|
+
<NFCTagForm
|
|
56
|
+
title="Add NFC Tag"
|
|
57
|
+
:site="props.site"
|
|
58
|
+
:org-id="props.orgId"
|
|
59
|
+
@cancel="dialogAdd = false"
|
|
60
|
+
@success="handleSuccess"
|
|
61
|
+
@error="handleError"
|
|
62
|
+
/>
|
|
63
|
+
</v-dialog>
|
|
64
|
+
|
|
65
|
+
<!-- Edit Dialog -->
|
|
66
|
+
<v-dialog v-model="dialogEdit" persistent width="450">
|
|
67
|
+
<NFCTagForm
|
|
68
|
+
title="Edit NFC Tag"
|
|
69
|
+
:site="props.site"
|
|
70
|
+
:org-id="props.orgId"
|
|
71
|
+
@cancel="dialogEdit = false"
|
|
72
|
+
@success="handleSuccess"
|
|
73
|
+
@error="handleError"
|
|
74
|
+
mode="edit"
|
|
75
|
+
:nfc-tag="selectedTag"
|
|
76
|
+
/>
|
|
77
|
+
</v-dialog>
|
|
78
|
+
|
|
79
|
+
<!-- Preview Dialog -->
|
|
80
|
+
<v-dialog v-model="dialogPreview" persistent width="540">
|
|
81
|
+
<v-card width="100%">
|
|
82
|
+
<v-toolbar>
|
|
83
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
84
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
85
|
+
NFC Tag Details
|
|
86
|
+
</span>
|
|
87
|
+
</v-row>
|
|
88
|
+
</v-toolbar>
|
|
89
|
+
|
|
90
|
+
<v-card-text
|
|
91
|
+
style="max-height: 100vh; overflow-y: auto"
|
|
92
|
+
class="pa-5 my-5 px-7"
|
|
93
|
+
>
|
|
94
|
+
<v-row no-gutters>
|
|
95
|
+
<v-col cols="12" class="mb-2">
|
|
96
|
+
Name:
|
|
97
|
+
<span class="font-weight-bold">
|
|
98
|
+
{{ selectedTag.name }}
|
|
99
|
+
</span>
|
|
100
|
+
</v-col>
|
|
101
|
+
|
|
102
|
+
<v-col cols="12" class="mb-2">
|
|
103
|
+
Tag ID:
|
|
104
|
+
<span class="font-weight-bold">
|
|
105
|
+
{{ selectedTag.tagID }}
|
|
106
|
+
</span>
|
|
107
|
+
</v-col>
|
|
108
|
+
|
|
109
|
+
<v-col cols="12" class="mb-2">
|
|
110
|
+
Status:
|
|
111
|
+
<span class="font-weight-bold text-capitalize">
|
|
112
|
+
{{ selectedTag.status }}
|
|
113
|
+
</span>
|
|
114
|
+
</v-col>
|
|
115
|
+
</v-row>
|
|
116
|
+
</v-card-text>
|
|
117
|
+
|
|
118
|
+
<v-toolbar class="pa-0" density="compact">
|
|
119
|
+
<v-row no-gutters>
|
|
120
|
+
<v-col cols="6" class="pa-0">
|
|
121
|
+
<v-btn
|
|
122
|
+
block
|
|
123
|
+
variant="text"
|
|
124
|
+
class="text-none"
|
|
125
|
+
size="large"
|
|
126
|
+
@click="dialogPreview = false"
|
|
127
|
+
height="48"
|
|
128
|
+
>
|
|
129
|
+
Close
|
|
130
|
+
</v-btn>
|
|
131
|
+
</v-col>
|
|
132
|
+
|
|
133
|
+
<v-col cols="6" class="pa-0">
|
|
134
|
+
<v-menu>
|
|
135
|
+
<template #activator="{ props }">
|
|
136
|
+
<v-btn
|
|
137
|
+
block
|
|
138
|
+
variant="flat"
|
|
139
|
+
color="black"
|
|
140
|
+
class="text-none"
|
|
141
|
+
height="48"
|
|
142
|
+
v-bind="props"
|
|
143
|
+
tile
|
|
144
|
+
>
|
|
145
|
+
More actions
|
|
146
|
+
</v-btn>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<v-list class="pa-0">
|
|
150
|
+
<v-list-item @click="openDialogEdit()">
|
|
151
|
+
<v-list-item-title class="text-subtitle-2">
|
|
152
|
+
Edit NFC Tag
|
|
153
|
+
</v-list-item-title>
|
|
154
|
+
</v-list-item>
|
|
155
|
+
|
|
156
|
+
<v-list-item @click="openDialogDelete()" class="text-red">
|
|
157
|
+
<v-list-item-title class="text-subtitle-2">
|
|
158
|
+
Delete NFC Tag
|
|
159
|
+
</v-list-item-title>
|
|
160
|
+
</v-list-item>
|
|
161
|
+
</v-list>
|
|
162
|
+
</v-menu>
|
|
163
|
+
</v-col>
|
|
164
|
+
</v-row>
|
|
165
|
+
</v-toolbar>
|
|
166
|
+
</v-card>
|
|
167
|
+
</v-dialog>
|
|
168
|
+
|
|
169
|
+
<!-- Delete Dialog -->
|
|
170
|
+
<v-dialog v-model="dialogDelete" persistent width="540">
|
|
171
|
+
<v-card width="100%">
|
|
172
|
+
<v-card-text
|
|
173
|
+
style="max-height: 100vh; overflow-y: auto"
|
|
174
|
+
class="pa-5 my-5 px-7 text-center"
|
|
175
|
+
>
|
|
176
|
+
Are you sure you want to delete this NFC Tag?
|
|
177
|
+
</v-card-text>
|
|
178
|
+
|
|
179
|
+
<v-toolbar class="pa-0" density="compact">
|
|
180
|
+
<v-row no-gutters>
|
|
181
|
+
<v-col cols="6" class="pa-0">
|
|
182
|
+
<v-btn
|
|
183
|
+
block
|
|
184
|
+
variant="text"
|
|
185
|
+
class="text-none"
|
|
186
|
+
size="large"
|
|
187
|
+
tile
|
|
188
|
+
@click="dialogDelete = false"
|
|
189
|
+
height="48"
|
|
190
|
+
>
|
|
191
|
+
Cancel
|
|
192
|
+
</v-btn>
|
|
193
|
+
</v-col>
|
|
194
|
+
|
|
195
|
+
<v-col cols="6" class="pa-0">
|
|
196
|
+
<v-btn
|
|
197
|
+
block
|
|
198
|
+
tile
|
|
199
|
+
variant="flat"
|
|
200
|
+
class="text-none"
|
|
201
|
+
size="large"
|
|
202
|
+
height="48"
|
|
203
|
+
color="black"
|
|
204
|
+
@click="submitDelete()"
|
|
205
|
+
>
|
|
206
|
+
Delete
|
|
207
|
+
</v-btn>
|
|
208
|
+
</v-col>
|
|
209
|
+
</v-row>
|
|
210
|
+
</v-toolbar>
|
|
211
|
+
</v-card>
|
|
212
|
+
</v-dialog>
|
|
213
|
+
|
|
214
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
215
|
+
</v-row>
|
|
216
|
+
</template>
|
|
217
|
+
|
|
218
|
+
<script setup lang="ts">
|
|
219
|
+
|
|
220
|
+
import useNFCPatrolTag from "../../composables/useNFCPatrolTag";
|
|
221
|
+
|
|
222
|
+
const events = defineEmits(["update:value", "row-click"]);
|
|
223
|
+
|
|
224
|
+
const props = defineProps({
|
|
225
|
+
site: {
|
|
226
|
+
type: String,
|
|
227
|
+
required: true,
|
|
228
|
+
},
|
|
229
|
+
siteName: {
|
|
230
|
+
type: String,
|
|
231
|
+
default: "",
|
|
232
|
+
},
|
|
233
|
+
orgId: {
|
|
234
|
+
type: String,
|
|
235
|
+
required: true,
|
|
236
|
+
},
|
|
237
|
+
headers: {
|
|
238
|
+
type: Array as PropType<Array<any>>,
|
|
239
|
+
default: () => [
|
|
240
|
+
{ title: "ID", value: "tagID" },
|
|
241
|
+
{ title: "Name", value: "name" },
|
|
242
|
+
{ title: "Status", value: "status" },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const items = ref<Array<any>>([]);
|
|
248
|
+
const page = ref(1);
|
|
249
|
+
const pages = ref(0);
|
|
250
|
+
const pageRange = ref("1-20 of 1");
|
|
251
|
+
|
|
252
|
+
const { getAll: getAllNFCTags, deleteById: deleteNFCTagById } = useNFCPatrolTag();
|
|
253
|
+
|
|
254
|
+
const { data: getNFCTagReq, refresh: getNFCTagRefresh } = await useLazyAsyncData(
|
|
255
|
+
`get-nfc-tags-${props.site}`,
|
|
256
|
+
() => getAllNFCTags({ site: props.site, page: page.value }),
|
|
257
|
+
{
|
|
258
|
+
watch: [page],
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
watchEffect(() => {
|
|
263
|
+
if (getNFCTagReq.value) {
|
|
264
|
+
items.value = getNFCTagReq.value.items;
|
|
265
|
+
pageRange.value = getNFCTagReq.value.pageRange;
|
|
266
|
+
pages.value = getNFCTagReq.value.pages;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const dialogAdd = ref(false);
|
|
271
|
+
const dialogEdit = ref(false);
|
|
272
|
+
const dialogPreview = ref(false);
|
|
273
|
+
const dialogDelete = ref(false);
|
|
274
|
+
|
|
275
|
+
const message = ref("");
|
|
276
|
+
const messageSnackbar = ref(false);
|
|
277
|
+
const messageColor = ref("");
|
|
278
|
+
|
|
279
|
+
function openDialogEdit() {
|
|
280
|
+
dialogEdit.value = true;
|
|
281
|
+
|
|
282
|
+
if (dialogPreview.value) dialogPreview.value = false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function openDialogDelete() {
|
|
286
|
+
dialogDelete.value = true;
|
|
287
|
+
|
|
288
|
+
if (dialogPreview.value) dialogPreview.value = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function handleSuccess() {
|
|
292
|
+
if (dialogAdd.value) {
|
|
293
|
+
dialogAdd.value = false;
|
|
294
|
+
showMessage("NFC Tag created successfully!", "success");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (dialogEdit.value) {
|
|
298
|
+
dialogEdit.value = false;
|
|
299
|
+
showMessage("NFC Tag updated successfully!", "success");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (dialogPreview.value) dialogPreview.value = false;
|
|
303
|
+
|
|
304
|
+
if (dialogDelete.value) dialogDelete.value = false;
|
|
305
|
+
|
|
306
|
+
getNFCTagRefresh();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function handleError(error: any) {
|
|
310
|
+
const errorMessage = error?.data?.message || error?.message || "An error occurred";
|
|
311
|
+
showMessage(errorMessage, "error");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function showMessage(msg: string, color: string) {
|
|
315
|
+
message.value = msg;
|
|
316
|
+
messageColor.value = color;
|
|
317
|
+
messageSnackbar.value = true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const selectedTag = ref<any>({
|
|
321
|
+
name: "",
|
|
322
|
+
tagID: "",
|
|
323
|
+
status: "active",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
function handleRowClick(_: any, data: any) {
|
|
327
|
+
dialogPreview.value = true;
|
|
328
|
+
selectedTag.value = JSON.parse(JSON.stringify(data.item));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function submitDelete() {
|
|
332
|
+
try {
|
|
333
|
+
await deleteNFCTagById(selectedTag.value._id ?? "");
|
|
334
|
+
await getNFCTagRefresh();
|
|
335
|
+
dialogDelete.value = false;
|
|
336
|
+
showMessage("NFC Tag deleted successfully!", "success");
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error("Error deleting NFC tag:", error);
|
|
339
|
+
handleError(error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
</script>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog v-model="overlay" v-if="overlay" width="100%" height="100%" opacity="50">
|
|
3
|
+
<v-row align="center" justify="center" class="fill-height" style="position: relative;">
|
|
4
|
+
<v-carousel hide-delimiters width="100%" height="100%" :show-arrows="files.length > 1 ? 'hover' : false"
|
|
5
|
+
v-model="activeIndex">
|
|
6
|
+
<template v-for="x, index in files" :key="x || index">
|
|
7
|
+
<template v-if="fileTypes?.[x] === 'video'">
|
|
8
|
+
<v-carousel-item>
|
|
9
|
+
<v-row no-gutters class="h-100 w-100" align="center" justify="center">
|
|
10
|
+
<video width="80%" height="80%" controls>
|
|
11
|
+
<source :src="getFileUrl(x)" />
|
|
12
|
+
</video>
|
|
13
|
+
</v-row>
|
|
14
|
+
<template v-slot:placeholder>
|
|
15
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
16
|
+
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
</v-carousel-item>
|
|
20
|
+
</template>
|
|
21
|
+
<template v-else>
|
|
22
|
+
<v-carousel-item>
|
|
23
|
+
<v-row no-gutters class="h-100 w-100" align="center" justify="center">
|
|
24
|
+
<v-icon size="100" color="white">mdi-file</v-icon>
|
|
25
|
+
</v-row>
|
|
26
|
+
</v-carousel-item>
|
|
27
|
+
</template>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<template v-slot:prev="{ props }">
|
|
31
|
+
<v-btn color="white" variant="outlined" class="text-white" icon="mdi-chevron-left"
|
|
32
|
+
@click="props.onClick"></v-btn>
|
|
33
|
+
</template>
|
|
34
|
+
<template v-slot:next="{ props }">
|
|
35
|
+
<v-btn color="white" class="text-white" variant="outlined" icon="mdi-chevron-right"
|
|
36
|
+
@click="props.onClick"></v-btn>
|
|
37
|
+
</template>
|
|
38
|
+
</v-carousel>
|
|
39
|
+
<div style="position: absolute; top: 2%; left: 0%; right: 0%; z-index: 2"
|
|
40
|
+
class="cursor-pointer text-white text-h6 custom-shadow d-flex justify-space-between" @click="overlay = false">
|
|
41
|
+
<v-row no-gutters style="position: relative">
|
|
42
|
+
<v-col cols="8" xs="6" sm="2" md="2" lg="2">
|
|
43
|
+
<v-btn prepend-icon="mdi-close" text="Close" rounded="lg" color="secondary" class="ml-2"
|
|
44
|
+
style="position: absolute;"></v-btn>
|
|
45
|
+
</v-col>
|
|
46
|
+
|
|
47
|
+
</v-row>
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
<span class="text-white text-16px d-flex w-100 mt-2 justify-center"
|
|
51
|
+
style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);">{{ `${activeIndex +
|
|
52
|
+
1}/${files.length}`
|
|
53
|
+
}}</span>
|
|
54
|
+
|
|
55
|
+
</v-row>
|
|
56
|
+
</v-dialog>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
const props = defineProps({
|
|
61
|
+
activeFileId: {
|
|
62
|
+
type: String,
|
|
63
|
+
required: false,
|
|
64
|
+
},
|
|
65
|
+
files: {
|
|
66
|
+
type: Array as PropType<string[]>,
|
|
67
|
+
default: []
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const { getFileUrl, urlToFile } = useFile()
|
|
73
|
+
const overlay = defineModel({ required: true, default: false });
|
|
74
|
+
const activeIndex = ref(0);
|
|
75
|
+
const fileTypes = ref<Record<string, "image" | "video" | "other">>({});
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
const emit = defineEmits(['share', 'like'])
|
|
79
|
+
|
|
80
|
+
watchEffect(() => {
|
|
81
|
+
if (props.activeFileId && props.files.length > 0) {
|
|
82
|
+
const index =
|
|
83
|
+
props.files?.findIndex((x) => x == props.activeFileId) || 0;
|
|
84
|
+
if (index !== -1) {
|
|
85
|
+
activeIndex.value = index;
|
|
86
|
+
} else activeIndex.value = 0;
|
|
87
|
+
} else {
|
|
88
|
+
return (activeIndex.value = 0);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
async function resolveFileTypes() {
|
|
93
|
+
fileTypes.value = {}; // reset
|
|
94
|
+
|
|
95
|
+
for (const x of props.files) {
|
|
96
|
+
try {
|
|
97
|
+
const url = getFileUrl(x);
|
|
98
|
+
const file = await urlToFile(url, x);
|
|
99
|
+
const type = file?.type;
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if (type?.startsWith("video")) fileTypes.value[x] = "video";
|
|
103
|
+
else if (type?.startsWith("image")) fileTypes.value[x] = "image";
|
|
104
|
+
else fileTypes.value[x] = "other";
|
|
105
|
+
|
|
106
|
+
} catch (err) {
|
|
107
|
+
fileTypes.value[x] = "other";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
watch(
|
|
113
|
+
() => props.files,
|
|
114
|
+
() => resolveFileTypes(),
|
|
115
|
+
{ immediate: true }
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<style scoped>
|
|
122
|
+
.custom-shadow {
|
|
123
|
+
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
|
|
124
|
+
}
|
|
125
|
+
</style>
|