@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.
@@ -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 && canCreateDocument"
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 || 'Filter by File Type' }}
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 size="18" color="grey-darken-2">
140
+ <v-icon
141
+ size="18"
142
+ :color="
143
+ isFolderItem(item._raw) ? 'amber-darken-2' : 'grey-darken-2'
144
+ "
145
+ >
95
146
  {{
96
- item.file_type?.toLowerCase() === "folder"
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 @cancel="createDialog = false" @success="successCreate()" />
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
- <strong>Name:</strong> {{ selectedDocument?.name ?? "N/A" }}
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
- <v-col cols="12">
216
- <strong>Type:</strong> {{ selectedDocument?.type ?? "N/A" }}
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
- <strong>Document: </strong>
220
- <NuxtLink
221
- :to="getFileUrl(selectedDocument.attachment)"
222
- external
223
- target="_blank"
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 { getAll: _getAllDocuments, deleteById, createFolder, create } = documentService;
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: doc.file_size || doc.size || "0mb",
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
- const fileTypes = ref(["All", "pdf", "csv", "doc"]);
496
- const selectedFileType = ref("All");
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
- selectedDocument.value = (data?._raw || data) as TDocument;
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
- previewDialog.value = dialog;
536
- selectedDocument.value = data;
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
- if (documentService.createFolder) {
567
- const resp = await (documentService as any).createFolder({
568
- name: folderTitle.value,
569
- remarks: folderRemarks.value,
570
- site: siteId,
571
- });
572
- showMessage(resp?.message || "Folder created successfully!", "success");
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
- const resp = await (documentService as any).create({
575
- name: folderTitle.value,
576
- remarks: folderRemarks.value,
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
- // fallback: no API available in this service
583
- showMessage("Folder created (local)", "success");
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(error?.response?._data?.message || "Failed to create folder", "error");
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
- // Gom selected + enabled vào 1 shallowRef duy nhất
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 duy nhất = 1 render cycle, không crash
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>
@@ -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>