@iservice365/layer-common 1.5.7 → 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.
@@ -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>