@7365admin1/layer-common 1.11.46 → 1.11.48
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 +13 -0
- package/components/AccessCardPreviewDialog.vue +63 -10
- package/components/AccessManagement.vue +10 -0
- package/components/CameraForm.vue +6 -4
- package/components/ClientMain.vue +70 -58
- package/components/DocumentForm.vue +50 -17
- package/components/DocumentManagement.vue +310 -48
- package/components/RolePermissionFormCreate.vue +2 -3
- package/components/SiteSettings.vue +105 -0
- package/components/TableV3.vue +10 -0
- package/components/VehicleManagement.vue +10 -9
- package/components/VisitorManagement.vue +7 -1
- package/composables/useANPRSettings.ts +27 -0
- package/composables/useAccessManagement.ts +8 -0
- package/composables/useDocument.ts +42 -12
- package/composables/useLocal.ts +18 -0
- package/composables/useOrg.ts +60 -26
- package/composables/useUtils.ts +14 -0
- package/package.json +1 -1
- package/types/customer.d.ts +7 -1
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<v-row no-gutters class="pa-0">
|
|
3
3
|
<v-col cols="12" class="mb-2">
|
|
4
4
|
<v-row no-gutters align="center">
|
|
5
|
-
<!-- Left Buttons -->
|
|
6
5
|
<v-col cols="12" md="6" class="d-flex align-center">
|
|
7
6
|
<v-btn
|
|
8
7
|
class="text-none me-2"
|
|
@@ -21,19 +20,17 @@
|
|
|
21
20
|
variant="tonal"
|
|
22
21
|
size="large"
|
|
23
22
|
@click="openCreateFolderDialog()"
|
|
24
|
-
v-if="canCreate &&
|
|
23
|
+
v-if="canCreate && canCreateFolder"
|
|
25
24
|
>
|
|
26
25
|
Create Folder
|
|
27
26
|
</v-btn>
|
|
28
27
|
</v-col>
|
|
29
28
|
|
|
30
|
-
<!-- Right Search + Filter -->
|
|
31
29
|
<v-col
|
|
32
30
|
cols="12"
|
|
33
31
|
md="6"
|
|
34
32
|
class="d-flex justify-end align-center mt-4 mt-md-0"
|
|
35
33
|
>
|
|
36
|
-
<!-- Search -->
|
|
37
34
|
<v-text-field
|
|
38
35
|
v-model="search"
|
|
39
36
|
placeholder="Search"
|
|
@@ -54,7 +51,7 @@
|
|
|
54
51
|
class="filtertype-btn text-none"
|
|
55
52
|
style="max-width: 220px"
|
|
56
53
|
>
|
|
57
|
-
{{ selectedFileType ||
|
|
54
|
+
{{ selectedFileType || "Filter by File Type" }}
|
|
58
55
|
</v-btn>
|
|
59
56
|
</template>
|
|
60
57
|
|
|
@@ -76,6 +73,22 @@
|
|
|
76
73
|
</v-col>
|
|
77
74
|
</v-row>
|
|
78
75
|
</v-col>
|
|
76
|
+
|
|
77
|
+
<v-col cols="12" class="mb-2" v-if="isInsideFolder">
|
|
78
|
+
<div class="folder-path-bar d-flex align-center ga-2 px-3 py-2">
|
|
79
|
+
<v-btn
|
|
80
|
+
icon="mdi-arrow-left"
|
|
81
|
+
size="small"
|
|
82
|
+
variant="text"
|
|
83
|
+
@click="goBackFolder"
|
|
84
|
+
/>
|
|
85
|
+
<v-icon size="18" color="grey-darken-2">mdi-folder</v-icon>
|
|
86
|
+
<span class="text-body-2 font-weight-medium">{{
|
|
87
|
+
currentFolderPath
|
|
88
|
+
}}</span>
|
|
89
|
+
</div>
|
|
90
|
+
</v-col>
|
|
91
|
+
|
|
79
92
|
<v-col cols="12">
|
|
80
93
|
<TableV3
|
|
81
94
|
:headers="headers"
|
|
@@ -89,11 +102,49 @@
|
|
|
89
102
|
@update:page="page = $event"
|
|
90
103
|
@row-click="tableRowClickHandler"
|
|
91
104
|
>
|
|
105
|
+
<template v-if="isInsideFolder" #breadcrumb>
|
|
106
|
+
<div class="d-flex align-center flex-wrap ga-1">
|
|
107
|
+
<v-btn
|
|
108
|
+
icon="mdi-chevron-left"
|
|
109
|
+
size="x-small"
|
|
110
|
+
variant="text"
|
|
111
|
+
density="comfortable"
|
|
112
|
+
class="me-1"
|
|
113
|
+
@click="goBackFolder"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<span
|
|
117
|
+
class="breadcrumb-segment text-body-2 text-grey-darken-1 cursor-pointer"
|
|
118
|
+
@click="navigateToRoot"
|
|
119
|
+
>Home</span
|
|
120
|
+
>
|
|
121
|
+
<template v-for="(folder, idx) in folderStack" :key="folder.id">
|
|
122
|
+
<v-icon size="14" color="grey-darken-1">mdi-chevron-right</v-icon>
|
|
123
|
+
<span
|
|
124
|
+
:class="[
|
|
125
|
+
'breadcrumb-segment text-body-2 font-weight-medium',
|
|
126
|
+
idx === folderStack.length - 1
|
|
127
|
+
? 'text-black'
|
|
128
|
+
: 'text-grey-darken-1 cursor-pointer',
|
|
129
|
+
]"
|
|
130
|
+
@click="
|
|
131
|
+
idx < folderStack.length - 1 && navigateToStackIndex(idx)
|
|
132
|
+
"
|
|
133
|
+
>{{ folder.name }}</span
|
|
134
|
+
>
|
|
135
|
+
</template>
|
|
136
|
+
</div>
|
|
137
|
+
</template>
|
|
92
138
|
<template #item.name="{ item }">
|
|
93
139
|
<div class="d-flex align-center ga-2">
|
|
94
|
-
<v-icon
|
|
140
|
+
<v-icon
|
|
141
|
+
size="18"
|
|
142
|
+
:color="
|
|
143
|
+
isFolderItem(item._raw) ? 'amber-darken-2' : 'grey-darken-2'
|
|
144
|
+
"
|
|
145
|
+
>
|
|
95
146
|
{{
|
|
96
|
-
item.
|
|
147
|
+
isFolderItem(item._raw)
|
|
97
148
|
? "mdi-folder"
|
|
98
149
|
: "mdi-file-document-outline"
|
|
99
150
|
}}
|
|
@@ -115,6 +166,7 @@
|
|
|
115
166
|
variant="flat"
|
|
116
167
|
class="table-download-btn text-none"
|
|
117
168
|
@click.stop="downloadDocument(item._raw)"
|
|
169
|
+
:disabled="isFolderItem(item._raw)"
|
|
118
170
|
>
|
|
119
171
|
Download
|
|
120
172
|
</v-btn>
|
|
@@ -124,7 +176,11 @@
|
|
|
124
176
|
|
|
125
177
|
<!-- Create Dialog -->
|
|
126
178
|
<v-dialog v-model="createDialog" width="450" persistent>
|
|
127
|
-
<DocumentForm
|
|
179
|
+
<DocumentForm
|
|
180
|
+
:parent-id="activeParentId"
|
|
181
|
+
@cancel="createDialog = false"
|
|
182
|
+
@success="successCreate()"
|
|
183
|
+
/>
|
|
128
184
|
</v-dialog>
|
|
129
185
|
|
|
130
186
|
<!-- Create Folder Dialog -->
|
|
@@ -145,7 +201,7 @@
|
|
|
145
201
|
<v-text-field
|
|
146
202
|
v-model.trim="folderTitle"
|
|
147
203
|
density="comfortable"
|
|
148
|
-
:rules="[v => !!v || 'Folder title is required']"
|
|
204
|
+
:rules="[(v) => !!v || 'Folder title is required']"
|
|
149
205
|
/>
|
|
150
206
|
</v-col>
|
|
151
207
|
|
|
@@ -209,21 +265,72 @@
|
|
|
209
265
|
<v-card width="100%">
|
|
210
266
|
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
|
|
211
267
|
<v-row no-gutters class="mb-4">
|
|
212
|
-
<v-col cols="12">
|
|
213
|
-
<
|
|
268
|
+
<v-col cols="12" class="mb-3">
|
|
269
|
+
<div class="text-h6 font-weight-bold">
|
|
270
|
+
{{ selectedDocument?.name || "N/A" }}
|
|
271
|
+
</div>
|
|
214
272
|
</v-col>
|
|
215
|
-
|
|
216
|
-
|
|
273
|
+
|
|
274
|
+
<v-col cols="12" class="mb-2">
|
|
275
|
+
<v-row no-gutters>
|
|
276
|
+
<v-col cols="6" class="text-body-1">Document</v-col>
|
|
277
|
+
<v-col cols="6" class="text-right text-body-1">
|
|
278
|
+
<NuxtLink
|
|
279
|
+
v-if="selectedDocument?.attachment"
|
|
280
|
+
:to="getFileUrl(selectedDocument.attachment)"
|
|
281
|
+
external
|
|
282
|
+
target="_blank"
|
|
283
|
+
>
|
|
284
|
+
View Document
|
|
285
|
+
</NuxtLink>
|
|
286
|
+
<span v-else>N/A</span>
|
|
287
|
+
</v-col>
|
|
288
|
+
</v-row>
|
|
289
|
+
</v-col>
|
|
290
|
+
|
|
291
|
+
<v-col cols="12" class="mb-2">
|
|
292
|
+
<v-row no-gutters>
|
|
293
|
+
<v-col cols="6" class="text-body-1">File Type</v-col>
|
|
294
|
+
<v-col cols="6" class="text-right text-body-1">{{
|
|
295
|
+
previewDetails.fileType
|
|
296
|
+
}}</v-col>
|
|
297
|
+
</v-row>
|
|
298
|
+
</v-col>
|
|
299
|
+
|
|
300
|
+
<v-col cols="12" class="mb-2">
|
|
301
|
+
<v-row no-gutters>
|
|
302
|
+
<v-col cols="6" class="text-body-1">File Size</v-col>
|
|
303
|
+
<v-col cols="6" class="text-right text-body-1">{{
|
|
304
|
+
previewDetails.fileSize
|
|
305
|
+
}}</v-col>
|
|
306
|
+
</v-row>
|
|
307
|
+
</v-col>
|
|
308
|
+
|
|
309
|
+
<v-col cols="12" class="mb-2">
|
|
310
|
+
<v-row no-gutters>
|
|
311
|
+
<v-col cols="6" class="text-body-1">Date Created</v-col>
|
|
312
|
+
<v-col cols="6" class="text-right text-body-1">{{
|
|
313
|
+
previewDetails.dateCreated
|
|
314
|
+
}}</v-col>
|
|
315
|
+
</v-row>
|
|
316
|
+
</v-col>
|
|
317
|
+
|
|
318
|
+
<v-col cols="12" class="mb-2">
|
|
319
|
+
<v-row no-gutters>
|
|
320
|
+
<v-col cols="6" class="text-body-1">Date Modified</v-col>
|
|
321
|
+
<v-col cols="6" class="text-right text-body-1">{{
|
|
322
|
+
previewDetails.dateModified
|
|
323
|
+
}}</v-col>
|
|
324
|
+
</v-row>
|
|
217
325
|
</v-col>
|
|
326
|
+
|
|
218
327
|
<v-col cols="12">
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
>
|
|
225
|
-
View Document
|
|
226
|
-
</NuxtLink>
|
|
328
|
+
<v-row no-gutters>
|
|
329
|
+
<v-col cols="6" class="text-body-1">Remarks</v-col>
|
|
330
|
+
<v-col cols="6" class="text-right text-body-1">{{
|
|
331
|
+
previewDetails.remarks
|
|
332
|
+
}}</v-col>
|
|
333
|
+
</v-row>
|
|
227
334
|
</v-col>
|
|
228
335
|
</v-row>
|
|
229
336
|
</v-card-text>
|
|
@@ -394,6 +501,10 @@ const props = defineProps({
|
|
|
394
501
|
type: Boolean,
|
|
395
502
|
default: true,
|
|
396
503
|
},
|
|
504
|
+
canCreateFolder: {
|
|
505
|
+
type: Boolean,
|
|
506
|
+
default: true,
|
|
507
|
+
},
|
|
397
508
|
canUpdateDocument: {
|
|
398
509
|
type: Boolean,
|
|
399
510
|
default: true,
|
|
@@ -406,9 +517,17 @@ const props = defineProps({
|
|
|
406
517
|
|
|
407
518
|
const { headerSearch } = useLocal();
|
|
408
519
|
const documentService = useDocument();
|
|
409
|
-
const {
|
|
520
|
+
const {
|
|
521
|
+
getAll: _getAllDocuments,
|
|
522
|
+
deleteById,
|
|
523
|
+
createFolder,
|
|
524
|
+
create,
|
|
525
|
+
} = documentService;
|
|
410
526
|
const { getFileUrl } = useFile();
|
|
411
527
|
|
|
528
|
+
const router = useRouter();
|
|
529
|
+
const route = useRoute();
|
|
530
|
+
|
|
412
531
|
const page = ref(1);
|
|
413
532
|
const pages = ref(0);
|
|
414
533
|
const pageRange = ref("-- - -- of --");
|
|
@@ -419,8 +538,32 @@ const messageColor = ref("");
|
|
|
419
538
|
|
|
420
539
|
const items = ref<Array<Record<string, any>>>([]);
|
|
421
540
|
|
|
422
|
-
const route = useRoute();
|
|
423
541
|
const siteId = route.params.site as string;
|
|
542
|
+
const orgId = route.params.org as string;
|
|
543
|
+
const routeParentId = computed(() =>
|
|
544
|
+
typeof route.query.parentId === "string" ? route.query.parentId : ""
|
|
545
|
+
);
|
|
546
|
+
const activeParentId = ref(routeParentId.value);
|
|
547
|
+
const folderStack = ref<Array<{ id: string; name: string }>>([]);
|
|
548
|
+
|
|
549
|
+
const search = computed({
|
|
550
|
+
get: () => headerSearch.value,
|
|
551
|
+
set: (value: string) => {
|
|
552
|
+
headerSearch.value = value;
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const isInsideFolder = computed(() => activeParentId.value !== "");
|
|
557
|
+
const currentFolderPath = computed(() => {
|
|
558
|
+
if (folderStack.value.length === 0 && activeParentId.value) {
|
|
559
|
+
return "Current Folder";
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return folderStack.value.map((folder) => folder.name).join(" / ");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const fileTypes = ref(["all", "pdf", "csv", "doc"]);
|
|
566
|
+
const selectedFileType = ref("all");
|
|
424
567
|
|
|
425
568
|
const {
|
|
426
569
|
data: getDocumentReq,
|
|
@@ -433,9 +576,12 @@ const {
|
|
|
433
576
|
page: page.value,
|
|
434
577
|
search: headerSearch.value,
|
|
435
578
|
site: siteId,
|
|
579
|
+
parentId: activeParentId.value,
|
|
580
|
+
type:
|
|
581
|
+
selectedFileType.value === "all" ? undefined : selectedFileType.value,
|
|
436
582
|
}),
|
|
437
583
|
{
|
|
438
|
-
watch: [page, headerSearch],
|
|
584
|
+
watch: [page, headerSearch, activeParentId, selectedFileType],
|
|
439
585
|
}
|
|
440
586
|
);
|
|
441
587
|
|
|
@@ -469,12 +615,24 @@ function formatDate(value?: string | null) {
|
|
|
469
615
|
});
|
|
470
616
|
}
|
|
471
617
|
|
|
618
|
+
function formatBytes(bytes = 0) {
|
|
619
|
+
const b = Number(bytes) || 0;
|
|
620
|
+
if (b === 0) return "0 B";
|
|
621
|
+
const k = 1024;
|
|
622
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
623
|
+
const i = Math.floor(Math.log(b) / Math.log(k));
|
|
624
|
+
return `${parseFloat((b / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
625
|
+
}
|
|
626
|
+
|
|
472
627
|
const tableItems = computed(() => {
|
|
473
628
|
return items.value.map((doc: any) => ({
|
|
474
629
|
...doc,
|
|
475
630
|
_raw: doc,
|
|
476
|
-
file_type: doc.type || "N/A",
|
|
477
|
-
file_size:
|
|
631
|
+
file_type: doc.type === "folder" ? "Folder" : doc.type || "N/A",
|
|
632
|
+
file_size:
|
|
633
|
+
doc.type === "folder"
|
|
634
|
+
? "—"
|
|
635
|
+
: formatBytes(Number(doc.file_size ?? doc.size ?? 0)),
|
|
478
636
|
date_created: formatDate(doc.created_at || doc.createdAt),
|
|
479
637
|
date_modified: formatDate(doc.updated_at || doc.updatedAt),
|
|
480
638
|
remarks: doc.remarks || "N/A",
|
|
@@ -492,8 +650,8 @@ const folderRemarks = ref("");
|
|
|
492
650
|
const folderLoading = ref(false);
|
|
493
651
|
const validFolderForm = ref(false);
|
|
494
652
|
|
|
495
|
-
|
|
496
|
-
|
|
653
|
+
// fileTypes and selectedFileType are declared above to allow
|
|
654
|
+
// the fetch to include the selected filter.
|
|
497
655
|
const filterMenu = ref(false);
|
|
498
656
|
const selectedDocument = ref<TDocument>({
|
|
499
657
|
_id: "",
|
|
@@ -501,13 +659,101 @@ const selectedDocument = ref<TDocument>({
|
|
|
501
659
|
attachment: "",
|
|
502
660
|
type: "",
|
|
503
661
|
});
|
|
662
|
+
|
|
663
|
+
const previewDetails = computed(() => {
|
|
664
|
+
const document = selectedDocument.value as Record<string, any>;
|
|
665
|
+
const fileType =
|
|
666
|
+
document?.type === "folder"
|
|
667
|
+
? "Folder"
|
|
668
|
+
: document?.type || document?.file_type || "N/A";
|
|
669
|
+
const fileSize =
|
|
670
|
+
document?.type === "folder"
|
|
671
|
+
? "N/A"
|
|
672
|
+
: formatBytes(Number(document?.file_size ?? document?.size ?? 0));
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
fileType,
|
|
676
|
+
fileSize,
|
|
677
|
+
dateCreated: formatDate(document?.created_at || document?.createdAt),
|
|
678
|
+
dateModified: formatDate(document?.updated_at || document?.updatedAt),
|
|
679
|
+
remarks: document?.remarks || "N/A",
|
|
680
|
+
};
|
|
681
|
+
});
|
|
504
682
|
const deleteLoading = ref(false);
|
|
505
683
|
const confirmDialog = ref(false);
|
|
506
684
|
const selectedDocumentId = ref<string | null>(null);
|
|
507
685
|
|
|
508
686
|
function tableRowClickHandler(data: any) {
|
|
509
|
-
|
|
687
|
+
const rowData = (data?._raw || data) as TDocument;
|
|
688
|
+
|
|
689
|
+
if (isFolderItem(rowData)) {
|
|
690
|
+
openFolder(rowData);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
openPreviewDialog(rowData);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function openPreviewDialog(document: TDocument) {
|
|
698
|
+
selectedDocument.value = document;
|
|
510
699
|
previewDialog.value = true;
|
|
700
|
+
|
|
701
|
+
const documentId = String(document?._id || "");
|
|
702
|
+
if (!documentId) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
const response = await documentService.getById(documentId);
|
|
708
|
+
const fetchedDocument =
|
|
709
|
+
(response as any)?.item ||
|
|
710
|
+
(response as any)?.data ||
|
|
711
|
+
(response as any)?.document ||
|
|
712
|
+
response;
|
|
713
|
+
|
|
714
|
+
if (fetchedDocument && typeof fetchedDocument === "object") {
|
|
715
|
+
selectedDocument.value = {
|
|
716
|
+
...document,
|
|
717
|
+
...(fetchedDocument as Record<string, any>),
|
|
718
|
+
} as TDocument;
|
|
719
|
+
}
|
|
720
|
+
} catch (_error) {
|
|
721
|
+
// Keep table data in modal if detail API request fails.
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function isFolderItem(document?: Partial<TDocument> & Record<string, any>) {
|
|
726
|
+
if (!document) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const docType = String(
|
|
731
|
+
document.type || document.file_type || ""
|
|
732
|
+
).toLowerCase();
|
|
733
|
+
return docType === "folder";
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function openFolder(document: Partial<TDocument> & Record<string, any>) {
|
|
737
|
+
const folderId = String(document._id || "");
|
|
738
|
+
const folderName = String(document.name || "Folder");
|
|
739
|
+
|
|
740
|
+
if (!folderId) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
activeParentId.value = folderId;
|
|
745
|
+
folderStack.value.push({ id: folderId, name: folderName });
|
|
746
|
+
page.value = 1;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function goBackFolder() {
|
|
750
|
+
if (folderStack.value.length === 0) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
folderStack.value.pop();
|
|
755
|
+
activeParentId.value = folderStack.value.at(-1)?.id || "";
|
|
756
|
+
page.value = 1;
|
|
511
757
|
}
|
|
512
758
|
|
|
513
759
|
function downloadDocument(document?: TDocument) {
|
|
@@ -532,8 +778,11 @@ function setDocument({
|
|
|
532
778
|
editDialog.value = dialog;
|
|
533
779
|
selectedDocument.value = data;
|
|
534
780
|
} else if (mode === "preview") {
|
|
535
|
-
|
|
536
|
-
|
|
781
|
+
if (!dialog) {
|
|
782
|
+
previewDialog.value = false;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
openPreviewDialog(data);
|
|
537
786
|
}
|
|
538
787
|
}
|
|
539
788
|
|
|
@@ -563,24 +812,28 @@ async function handleCreateFolder() {
|
|
|
563
812
|
|
|
564
813
|
folderLoading.value = true;
|
|
565
814
|
try {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
815
|
+
const payload = {
|
|
816
|
+
name: folderTitle.value,
|
|
817
|
+
remarks: folderRemarks.value,
|
|
818
|
+
org: orgId,
|
|
819
|
+
site: siteId,
|
|
820
|
+
parentId: activeParentId.value,
|
|
821
|
+
type: "folder",
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
let resp: any = null;
|
|
825
|
+
if ((documentService as any).createFolder) {
|
|
826
|
+
resp = await (documentService as any).createFolder(payload);
|
|
573
827
|
} else if ((documentService as any).create) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
type: "folder",
|
|
578
|
-
site: siteId,
|
|
579
|
-
});
|
|
580
|
-
showMessage(resp?.message || "Folder created successfully!", "success");
|
|
828
|
+
resp = await (documentService as any).create(payload);
|
|
829
|
+
} else if ((documentService as any).add) {
|
|
830
|
+
resp = await (documentService as any).add(payload);
|
|
581
831
|
} else {
|
|
582
|
-
|
|
583
|
-
|
|
832
|
+
showMessage("Unable to create folder. No API available.", "error");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (resp) {
|
|
836
|
+
showMessage(resp?.message || "Folder created successfully!", "success");
|
|
584
837
|
}
|
|
585
838
|
|
|
586
839
|
createFolderDialog.value = false;
|
|
@@ -588,7 +841,10 @@ async function handleCreateFolder() {
|
|
|
588
841
|
folderRemarks.value = "";
|
|
589
842
|
await getDocuments();
|
|
590
843
|
} catch (error: any) {
|
|
591
|
-
showMessage(
|
|
844
|
+
showMessage(
|
|
845
|
+
error?.response?._data?.message || "Failed to create folder",
|
|
846
|
+
"error"
|
|
847
|
+
);
|
|
592
848
|
} finally {
|
|
593
849
|
folderLoading.value = false;
|
|
594
850
|
}
|
|
@@ -670,4 +926,10 @@ function selectFileType(type: string) {
|
|
|
670
926
|
.filtertype-item:last-child {
|
|
671
927
|
border-bottom: none;
|
|
672
928
|
}
|
|
929
|
+
|
|
930
|
+
.folder-path-bar {
|
|
931
|
+
border: 1px solid #e0e0e0;
|
|
932
|
+
border-radius: 8px;
|
|
933
|
+
background: #fafafa;
|
|
934
|
+
}
|
|
673
935
|
</style>
|
|
@@ -221,8 +221,7 @@ const { requiredRule } = useUtils();
|
|
|
221
221
|
const { createRole } = useRole();
|
|
222
222
|
const { getSiteById } = useSite();
|
|
223
223
|
|
|
224
|
-
//
|
|
225
|
-
// để chỉ trigger 1 render cycle khi update cả 2
|
|
224
|
+
// All selected + enabled 1 shallowRef
|
|
226
225
|
const permState = shallowRef({
|
|
227
226
|
selected: [] as string[],
|
|
228
227
|
enabled: [] as string[],
|
|
@@ -283,7 +282,7 @@ function toggleSelectAll(value: boolean) {
|
|
|
283
282
|
}
|
|
284
283
|
}
|
|
285
284
|
|
|
286
|
-
// 1 assignment
|
|
285
|
+
// 1 assignment to trigger reactivity only once
|
|
287
286
|
permState.value = { selected: allKeys, enabled: categories }
|
|
288
287
|
}
|
|
289
288
|
|
|
@@ -83,6 +83,56 @@
|
|
|
83
83
|
</v-expansion-panel-title>
|
|
84
84
|
|
|
85
85
|
<v-expansion-panel-text>
|
|
86
|
+
<v-container fluid class="px-0">
|
|
87
|
+
<v-row dense>
|
|
88
|
+
<v-col cols="6">
|
|
89
|
+
<v-switch
|
|
90
|
+
v-model="ANPRSwitches.enableUnregistered"
|
|
91
|
+
:disabled="!canManageSiteSettings"
|
|
92
|
+
label="Enable Unregistered Vehicles"
|
|
93
|
+
color="primary"
|
|
94
|
+
hide-details
|
|
95
|
+
></v-switch>
|
|
96
|
+
</v-col>
|
|
97
|
+
|
|
98
|
+
<v-col cols="6">
|
|
99
|
+
<v-switch
|
|
100
|
+
v-model="ANPRSwitches.openBarrierPickUpDropOff"
|
|
101
|
+
:disabled="!canManageSiteSettings"
|
|
102
|
+
label="Open Barrier for Pick-up and Drop-off"
|
|
103
|
+
color="primary"
|
|
104
|
+
hide-details
|
|
105
|
+
></v-switch>
|
|
106
|
+
</v-col>
|
|
107
|
+
|
|
108
|
+
<v-col cols="6">
|
|
109
|
+
<v-switch
|
|
110
|
+
v-model="ANPRSwitches.vehicleSnapshot"
|
|
111
|
+
:disabled="!canManageSiteSettings"
|
|
112
|
+
label="Capture Vehicle Snapshot"
|
|
113
|
+
color="primary"
|
|
114
|
+
hide-details
|
|
115
|
+
></v-switch>
|
|
116
|
+
</v-col>
|
|
117
|
+
</v-row>
|
|
118
|
+
|
|
119
|
+
<v-row v-if="canManageSiteSettings">
|
|
120
|
+
<v-col cols="auto">
|
|
121
|
+
<v-btn
|
|
122
|
+
variant="flat"
|
|
123
|
+
color="primary"
|
|
124
|
+
class="text-none"
|
|
125
|
+
:loading="isSaving"
|
|
126
|
+
@click="saveANPRSettings"
|
|
127
|
+
>
|
|
128
|
+
Save
|
|
129
|
+
</v-btn>
|
|
130
|
+
</v-col>
|
|
131
|
+
</v-row>
|
|
132
|
+
</v-container>
|
|
133
|
+
|
|
134
|
+
<v-divider class="my-4"></v-divider>
|
|
135
|
+
|
|
86
136
|
<CameraMain :site="siteId" :read-only="!canManageSiteSettings"
|
|
87
137
|
:guard-posts="siteData?.metadata?.guardPosts" />
|
|
88
138
|
</v-expansion-panel-text>
|
|
@@ -154,9 +204,11 @@ import useFile from '../composables/useFile';
|
|
|
154
204
|
import useSiteSettings from '../composables/useSiteSettings';
|
|
155
205
|
import useUtils from '../composables/useUtils';
|
|
156
206
|
import useWorkOrder from '../composables/useWorkOrder';
|
|
207
|
+
import useANPRSettings from '../composables/useANPRSettings';
|
|
157
208
|
|
|
158
209
|
const props = defineProps({
|
|
159
210
|
siteId: { type: String, required: true },
|
|
211
|
+
createdBy: { type: String },
|
|
160
212
|
canManageSiteSettings: { type: Boolean, default: false },
|
|
161
213
|
showAnprCamera: { type: Boolean, default: false },
|
|
162
214
|
showCctvCamera: { type: Boolean, default: false },
|
|
@@ -173,6 +225,7 @@ const { requiredRule } = useUtils();
|
|
|
173
225
|
const { getFileUrl } = useFile();
|
|
174
226
|
const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder();
|
|
175
227
|
|
|
228
|
+
|
|
176
229
|
const blocks = ref(0);
|
|
177
230
|
const guardPosts = ref(0);
|
|
178
231
|
const gracePeriod = ref(0);
|
|
@@ -358,4 +411,56 @@ function show(message: string, color: string) {
|
|
|
358
411
|
toast.message = message;
|
|
359
412
|
toast.color = color;
|
|
360
413
|
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
// ANPR Settings
|
|
417
|
+
const {getANPRSettingBySite, createUpdateANPRSettings} = useANPRSettings();
|
|
418
|
+
const isSaving = ref(false)
|
|
419
|
+
|
|
420
|
+
// Reactive object to hold the state of the 3 switches
|
|
421
|
+
const ANPRSwitches = ref({
|
|
422
|
+
enableUnregistered: false,
|
|
423
|
+
openBarrierPickUpDropOff: false,
|
|
424
|
+
vehicleSnapshot: false
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
await useLazyAsyncData(
|
|
428
|
+
`anpr-settings-${props.siteId}`,
|
|
429
|
+
async () => {
|
|
430
|
+
const data = await getANPRSettingBySite({
|
|
431
|
+
site: props.siteId,
|
|
432
|
+
userType: "site",
|
|
433
|
+
});
|
|
434
|
+
console.log('data', data)
|
|
435
|
+
if(typeof data?.ANPRSwitches == "object"){
|
|
436
|
+
ANPRSwitches.value = data?.ANPRSwitches
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
const saveANPRSettings = async () => {
|
|
442
|
+
try {
|
|
443
|
+
isSaving.value = true
|
|
444
|
+
|
|
445
|
+
// Replace this with your actual API endpoint / store action
|
|
446
|
+
// await $fetch(`/api/sites/${siteId.value}/camera-settings`, {
|
|
447
|
+
// method: 'POST',
|
|
448
|
+
// body: cameraSettings.value
|
|
449
|
+
// })
|
|
450
|
+
const data = {
|
|
451
|
+
site: props.siteId,
|
|
452
|
+
ANPRSwitches: ANPRSwitches.value,
|
|
453
|
+
updatedBy: props.createdBy,
|
|
454
|
+
userType: "site",
|
|
455
|
+
};
|
|
456
|
+
console.log('saveANPRSettings data:', data)
|
|
457
|
+
await createUpdateANPRSettings(data);
|
|
458
|
+
show("Saved successfully", "success");
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error('Failed to save settings:', error)
|
|
461
|
+
} finally {
|
|
462
|
+
isSaving.value = false
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
361
466
|
</script>
|
package/components/TableV3.vue
CHANGED
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
</template>
|
|
44
44
|
</v-toolbar>
|
|
45
45
|
|
|
46
|
+
<div v-if="$slots.breadcrumb" class="table-v3__breadcrumb">
|
|
47
|
+
<slot name="breadcrumb" />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
46
50
|
<v-data-table
|
|
47
51
|
:headers="headers"
|
|
48
52
|
:items="items"
|
|
@@ -176,4 +180,10 @@ watch(
|
|
|
176
180
|
width: 38px;
|
|
177
181
|
height: 38px;
|
|
178
182
|
}
|
|
183
|
+
|
|
184
|
+
.table-v3__breadcrumb {
|
|
185
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
186
|
+
background-color: #fff;
|
|
187
|
+
padding: 6px 12px;
|
|
188
|
+
}
|
|
179
189
|
</style>
|