@7365admin1/layer-common 3.0.24 → 3.0.26

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/AccessCardAddForm.vue +2 -2
  3. package/components/AccessCardAssignToUnitForm.vue +2 -2
  4. package/components/AccessCardDetailsDialog.vue +31 -1
  5. package/components/AccessManagement.vue +16 -6
  6. package/components/AddPassKeyToVisitor.vue +346 -225
  7. package/components/BuildingUnitFormEdit.vue +1 -1
  8. package/components/DashboardMain.vue +124 -60
  9. package/components/DocumentManagement.vue +294 -1
  10. package/components/HidIntercomManagement.vue +186 -0
  11. package/components/HidReaderManagement.vue +21 -1
  12. package/components/Nfc/NFCPatrolReportMain.vue +1 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +2 -1
  14. package/components/Nfc/PatrolReport/ReportFilters.vue +3 -1
  15. package/components/RolePermissionMain.vue +34 -5
  16. package/components/SiteSettings.vue +3 -1
  17. package/components/VisitorForm.vue +174 -398
  18. package/components/VisitorManagement.vue +302 -679
  19. package/components/VisitorSocketPopUp.vue +29 -1
  20. package/composables/useANPRSettings.ts +57 -8
  21. package/composables/useAccessManagement.ts +9 -1
  22. package/composables/useCleaningPermission.ts +2 -2
  23. package/composables/useCommonPermission.ts +2 -39
  24. package/composables/useDocument.ts +21 -0
  25. package/composables/useLocalAuth.ts +3 -1
  26. package/composables/useNFCPatrolLog.ts +35 -0
  27. package/composables/useNFCPatrolReport.ts +37 -4
  28. package/composables/useNFCPatrolReportFilters.ts +28 -5
  29. package/package.json +1 -1
  30. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
  31. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
  32. package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
  33. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
  34. package/services/nfcPatrolReport.ts +5 -1
  35. package/types/nfc-patrol-report.ts +7 -1
  36. package/types/visitor.d.ts +1 -0
@@ -1355,7 +1355,16 @@
1355
1355
  :key="item.status"
1356
1356
  class="facility-booking-summary-row"
1357
1357
  >
1358
- <span class="facility-booking-summary-label">
1358
+ <button
1359
+ v-if="item.status === 'Pending'"
1360
+ class="facility-booking-summary-label facility-booking-summary-label-button"
1361
+ type="button"
1362
+ aria-label="View pending facility bookings"
1363
+ @click="goToFacilityBookingStatus(item.status)"
1364
+ >
1365
+ {{ item.status }}
1366
+ </button>
1367
+ <span v-else class="facility-booking-summary-label">
1359
1368
  {{ item.status }}
1360
1369
  </span>
1361
1370
  <span class="facility-booking-summary-dots" />
@@ -1533,72 +1542,108 @@ const {
1533
1542
  } = useDashboard();
1534
1543
 
1535
1544
  const { userAppRole } = useLocalSetup();
1536
- const { hasPermission } = usePermission();
1537
- const { dashboardPermissions } = useCommonPermissions();
1538
1545
  const { downloadPDF } = usePDFDownload();
1539
1546
  const route = useRoute();
1540
1547
 
1541
- function canViewWidget(key: string) {
1542
- if (!userAppRole.value) return true;
1543
- if (userAppRole.value.permissions?.includes("*")) return true;
1544
-
1545
- const widgetToModulePermissionMap: Record<string, string[]> = {
1546
- workOrders: ["work-orders:see-all-work-orders"],
1547
- incidents: [
1548
- "incident-reports:see-incident-reports",
1549
- "incident-reports:see-all-incident-reports",
1550
- ],
1551
- visitors: ["visitor:see-all-visitor"],
1552
- facilityBookings: ["facility-booking:see-all-facility-booking"],
1553
- patrolCompliance: [
1554
- "virtual-patrol:see-all-virtual-patrol-logs",
1555
- "virtual-patrol:see-all-virtual-patrol-route",
1556
- ],
1557
- staffStatus: ["attendance:see-all-attendance"],
1558
- attendance: ["attendance:see-all-attendance"],
1559
- feedbacks: ["feedbacks:see-all-feedbacks"],
1560
- upcomingEvents: [
1561
- "bulletin-boards:see-all-bulletin-boards",
1562
- "bulletin-board:see-all-bulletin-board",
1563
- ],
1564
- workOrderStatus: ["work-orders:see-all-work-orders"],
1565
- activePatrol: [
1566
- "virtual-patrol:see-all-virtual-patrol-logs",
1567
- "virtual-patrol:see-all-virtual-patrol-route",
1568
- ],
1569
- taskSchedule: ["cleaning-schedule-mgmt:see-all-schedules"],
1570
- };
1548
+ const dashboardWidgetPermissions: Record<string, string> = {
1549
+ activity: "dashboard:view-activity",
1550
+ recentActivity: "dashboard:view-activity",
1551
+ taskSchedule: "dashboard:view-task-schedule",
1552
+ staffStatus: "dashboard:view-attendance-widget",
1553
+ attendance: "dashboard:view-attendance-widget",
1554
+ feedbacks: "dashboard:view-feedbacks-widget",
1555
+ workOrderStatus: "dashboard:view-work-order-status",
1556
+ activePatrol: "dashboard:view-active-patrol",
1557
+ upcomingEvents: "dashboard:view-upcoming-events",
1558
+ todayReminders: "dashboard:view-today-reminders",
1559
+ todayAttentions: "dashboard:view-today-attentions",
1560
+ };
1571
1561
 
1572
- const modulePerms = widgetToModulePermissionMap[key];
1573
- if (
1574
- modulePerms &&
1575
- modulePerms.some((p) => userAppRole.value.permissions?.includes(p))
1576
- ) {
1577
- return true;
1578
- }
1562
+ const propertyWidgetPermissions: Record<string, string[]> = {
1563
+ workOrders: ["workOrder:see-all-work-orders"],
1564
+ incidents: ["incident-reports:see-incident-reports"],
1565
+ visitors: ["visitorManagement:see-all-visitor"],
1566
+ facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
1567
+ activity: ["workOrder:see-all-work-orders"],
1568
+ upcomingEvents: ["event-mgmt:see-all-event"],
1569
+ todayReminders: ["event-mgmt:see-all-event"],
1570
+ todayAttentions: [
1571
+ "incident-reports:see-incident-reports",
1572
+ "facility-booking-mgmt:see-all-facility-booking",
1573
+ ],
1574
+ workOrderStatus: ["workOrder:see-all-work-orders"],
1575
+ feedbacks: ["feedbacks:see-all-feedback"],
1576
+ activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1577
+ };
1579
1578
 
1580
- const actionMap: Record<string, string> = {
1581
- activity: "view-activity",
1582
- taskSchedule: "view-task-schedule",
1583
- staffStatus: "view-attendance-widget",
1584
- attendance: "view-attendance-widget",
1585
- feedbacks: "view-feedbacks-widget",
1586
- upcomingEvents: "view-upcoming-events",
1587
- todayReminders: "view-today-reminders",
1588
- todayAttentions: "view-today-attentions",
1589
- workOrderStatus: "view-work-order-status",
1590
- activePatrol: "view-active-patrol",
1591
- };
1579
+ const moduleWidgetPermissions: Record<string, string[]> = {
1580
+ openWorkOrder: [
1581
+ "work_order:see-all-work-orders",
1582
+ "work-order:see-all-work-orders",
1583
+ "work-orders:see-all-work-orders",
1584
+ ],
1585
+ workOrders: [
1586
+ "work_order:see-all-work-orders",
1587
+ "work-order:see-all-work-orders",
1588
+ "work-orders:see-all-work-orders",
1589
+ ],
1590
+ taskSchedule: [
1591
+ "cleaning-schedule-mgmt:see-all-schedules",
1592
+ "schedule-task-mgmt:see-all-schedule-tasks",
1593
+ ],
1594
+ taskCompleted: [
1595
+ "cleaning-schedule-mgmt:see-all-schedules",
1596
+ "schedule-task-mgmt:see-all-schedule-tasks",
1597
+ ],
1598
+ staffStatus: ["attendance-mgmt:see-all-attendance"],
1599
+ attendance: ["attendance-mgmt:see-all-attendance"],
1600
+ supplyAlert: ["supply-mgmt:see-all-supplies"],
1601
+ feedbacks: [
1602
+ "feedbacks:see-all-feedback",
1603
+ "feedback:see-all-feedback",
1604
+ "feedbacks:see-all-feedbacks",
1605
+ ],
1606
+ incidents: [
1607
+ "incident-reports:see-incident-reports",
1608
+ "incident-reports:see-all-incident-reports",
1609
+ ],
1610
+ visitors: ["visitor:see-all-visitor"],
1611
+ facilityBookings: ["facility-booking:see-all-facility-booking"],
1612
+ patrolCompliance: [
1613
+ "virtual-patrol:see-all-virtual-patrol-logs",
1614
+ "virtual-patrol:see-all-virtual-patrol-route",
1615
+ ],
1616
+ activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1617
+ };
1618
+
1619
+ function canViewWidget(key: string) {
1620
+ const permissions = userAppRole.value?.permissions;
1621
+ const dashboardPermission = dashboardWidgetPermissions[key];
1622
+ const modulePermissions = isPropertyMode.value
1623
+ ? propertyWidgetPermissions[key]
1624
+ : moduleWidgetPermissions[key];
1625
+
1626
+ if (!permissions) return true;
1627
+ if (permissions.includes("*")) return true;
1628
+
1629
+ if (isPropertyMode.value && modulePermissions) {
1630
+ return modulePermissions.some((permission) =>
1631
+ permissions.includes(permission)
1632
+ );
1633
+ }
1592
1634
 
1593
- const action = actionMap[key];
1594
- if (!action) return true;
1635
+ if (dashboardPermission || modulePermissions) {
1636
+ return (
1637
+ (dashboardPermission && permissions.includes(dashboardPermission)) ||
1638
+ Boolean(
1639
+ modulePermissions?.some((permission) =>
1640
+ permissions.includes(permission)
1641
+ )
1642
+ )
1643
+ );
1644
+ }
1595
1645
 
1596
- return hasPermission(
1597
- userAppRole.value,
1598
- { dashboard: dashboardPermissions },
1599
- "dashboard",
1600
- action
1601
- );
1646
+ return true;
1602
1647
  }
1603
1648
 
1604
1649
  // ─── State ────────────────────────────────────────────────────────────────────
@@ -2294,6 +2339,12 @@ function openFacilityBookingDialog() {
2294
2339
  facilityBookingDialog.value = true;
2295
2340
  }
2296
2341
 
2342
+ function goToFacilityBookingStatus(status: string) {
2343
+ if (status !== "Pending" || !props.orgId || !props.site) return;
2344
+ facilityBookingDialog.value = false;
2345
+ router.push(`${dashboardPath("facility/bookings")}?tab=Pending`);
2346
+ }
2347
+
2297
2348
  function normalizeFacilityBookingStatus(status: unknown) {
2298
2349
  const value = String(status || "").trim();
2299
2350
  return value === "For Refund" ? "With Refund" : value;
@@ -4233,6 +4284,19 @@ function formatDashboardTime(value?: string) {
4233
4284
  line-height: 1.25;
4234
4285
  }
4235
4286
 
4287
+ .facility-booking-summary-label-button {
4288
+ appearance: none;
4289
+ background: transparent;
4290
+ border: 0;
4291
+ cursor: pointer;
4292
+ padding: 0;
4293
+ text-align: left;
4294
+ }
4295
+
4296
+ .facility-booking-summary-label-button:hover {
4297
+ text-decoration: underline;
4298
+ }
4299
+
4236
4300
  .facility-booking-summary-dots {
4237
4301
  border-bottom: 2px dotted #9aa3ad;
4238
4302
  display: block;
@@ -163,13 +163,47 @@
163
163
 
164
164
  <template #item.action="{ item }">
165
165
  <v-btn
166
+ v-if="!isFolderItem(item._raw)"
166
167
  variant="flat"
167
168
  class="table-download-btn text-none"
168
169
  @click.stop="downloadDocument(item._raw)"
169
- :disabled="isFolderItem(item._raw)"
170
170
  >
171
171
  Download
172
172
  </v-btn>
173
+
174
+ <v-menu
175
+ v-else
176
+ v-model="folderActionMenu[item._raw._id]"
177
+ location="bottom end"
178
+ offset="6"
179
+ >
180
+ <template #activator="{ props }">
181
+ <v-btn
182
+ v-bind="props"
183
+ icon="mdi-dots-vertical"
184
+ variant="text"
185
+ size="small"
186
+ density="comfortable"
187
+ class="folder-action-btn"
188
+ @click.stop
189
+ />
190
+ </template>
191
+
192
+ <v-list class="folder-action-list pa-0">
193
+ <v-list-item
194
+ class="folder-action-item"
195
+ @click.stop="openEditFolderDialog(item._raw)"
196
+ >
197
+ <v-list-item-title class="text-body-2">Edit</v-list-item-title>
198
+ </v-list-item>
199
+ <v-list-item
200
+ class="folder-action-item"
201
+ @click.stop="openDeleteFolderDialog(item._raw)"
202
+ >
203
+ <v-list-item-title class="text-body-2">Delete</v-list-item-title>
204
+ </v-list-item>
205
+ </v-list>
206
+ </v-menu>
173
207
  </template>
174
208
  </TableV3>
175
209
  </v-col>
@@ -251,6 +285,129 @@
251
285
  </v-card>
252
286
  </v-dialog>
253
287
 
288
+ <!-- Edit Folder Dialog -->
289
+ <v-dialog v-model="editFolderDialog" width="450" persistent>
290
+ <v-card width="100%" class="folder-dialog-card">
291
+ <v-toolbar class="pa-0 folder-dialog-toolbar" density="compact">
292
+ <v-row no-gutters class="w-100">
293
+ <v-col cols="12" class="pa-4">
294
+ <span class="font-weight-medium text-h6">Edit Folder</span>
295
+ </v-col>
296
+ </v-row>
297
+ </v-toolbar>
298
+
299
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
300
+ <v-form v-model="validEditFolderForm">
301
+ <v-col cols="12" class="px-6 pt-5">
302
+ <InputLabel title="Folder Title" />
303
+ <v-text-field
304
+ v-model.trim="editFolderTitle"
305
+ density="comfortable"
306
+ variant="outlined"
307
+ :rules="[(v) => !!v || 'Folder title is required']"
308
+ hide-details="auto"
309
+ />
310
+ </v-col>
311
+
312
+ <v-col cols="12" class="px-6 pb-5">
313
+ <InputLabel title="Remarks (Optional)" />
314
+ <v-textarea
315
+ v-model.trim="editFolderRemarks"
316
+ density="comfortable"
317
+ variant="outlined"
318
+ rows="3"
319
+ placeholder="Enter remarks"
320
+ hide-details
321
+ />
322
+ </v-col>
323
+ </v-form>
324
+ </v-card-text>
325
+
326
+ <v-toolbar class="pa-0" density="compact">
327
+ <v-row no-gutters class="w-100">
328
+ <v-col cols="6" class="pa-0">
329
+ <v-btn
330
+ block
331
+ variant="text"
332
+ class="text-none"
333
+ size="large"
334
+ @click="editFolderDialog = false"
335
+ :disabled="folderUpdateLoading"
336
+ height="48"
337
+ >
338
+ Cancel
339
+ </v-btn>
340
+ </v-col>
341
+ <v-col cols="6" class="pa-0">
342
+ <v-btn
343
+ block
344
+ color="black"
345
+ variant="flat"
346
+ class="text-none"
347
+ size="large"
348
+ @click="handleApplyFolderEdit"
349
+ :loading="folderUpdateLoading"
350
+ height="48"
351
+ >
352
+ Apply
353
+ </v-btn>
354
+ </v-col>
355
+ </v-row>
356
+ </v-toolbar>
357
+ </v-card>
358
+ </v-dialog>
359
+
360
+ <!-- Delete Folder Dialog -->
361
+ <v-dialog v-model="deleteFolderDialog" width="450" persistent>
362
+ <v-card width="100%" class="folder-dialog-card">
363
+ <v-toolbar density="compact" class="pl-4 folder-dialog-toolbar">
364
+ <span class="font-weight-medium text-h6">Delete Folder</span>
365
+ </v-toolbar>
366
+
367
+ <v-card-text class="px-6 py-5">
368
+ <p class="text-subtitle-2 font-weight-medium mb-4">
369
+ Are you sure you want to permanently delete this folder?
370
+ </p>
371
+ <p class="text-body-2 text-grey-darken-1 mb-0">
372
+ This action cannot be undone and the folder will be removed
373
+ permanently from the system.
374
+ </p>
375
+ </v-card-text>
376
+
377
+ <v-toolbar density="compact" class="pa-0">
378
+ <v-row no-gutters class="w-100">
379
+ <v-col cols="6" class="pa-0">
380
+ <v-btn
381
+ tile
382
+ block
383
+ height="48"
384
+ variant="text"
385
+ class="text-none"
386
+ @click="deleteFolderDialog = false"
387
+ :disabled="folderDeleteLoading"
388
+ >
389
+ Cancel
390
+ </v-btn>
391
+ </v-col>
392
+ <v-col cols="6" class="pa-0">
393
+ <v-btn
394
+ tile
395
+ block
396
+ height="48"
397
+ color="black"
398
+ variant="flat"
399
+ class="text-none"
400
+ @click="handleDeleteFolderUiConfirm"
401
+ :loading="folderDeleteLoading"
402
+ >
403
+ Confirm
404
+ </v-btn>
405
+ </v-col>
406
+ </v-row>
407
+ </v-toolbar>
408
+ </v-card>
409
+ </v-dialog>
410
+
254
411
  <!-- Edit Dialog -->
255
412
  <v-dialog v-model="editDialog" width="450" persistent>
256
413
  <DocumentForm
@@ -531,6 +688,8 @@ const documentService = useDocument();
531
688
  const {
532
689
  getAll: _getAllDocuments,
533
690
  deleteById,
691
+ deleteFolderById,
692
+ updateFolderById,
534
693
  createFolder,
535
694
  create,
536
695
  } = documentService;
@@ -689,6 +848,17 @@ const folderTitle = ref("");
689
848
  const folderRemarks = ref("");
690
849
  const folderLoading = ref(false);
691
850
  const validFolderForm = ref(false);
851
+ const folderActionMenu = ref<Record<string, boolean>>({});
852
+ const selectedFolder = ref<(Partial<TDocument> & Record<string, any>) | null>(
853
+ null
854
+ );
855
+ const editFolderDialog = ref(false);
856
+ const deleteFolderDialog = ref(false);
857
+ const folderUpdateLoading = ref(false);
858
+ const folderDeleteLoading = ref(false);
859
+ const editFolderTitle = ref("");
860
+ const editFolderRemarks = ref("");
861
+ const validEditFolderForm = ref(false);
692
862
 
693
863
  // fileTypes and selectedFileType are declared above to allow
694
864
  // the fetch to include the selected filter.
@@ -849,6 +1019,98 @@ function openCreateFolderDialog() {
849
1019
  createFolderDialog.value = true;
850
1020
  }
851
1021
 
1022
+ function closeFolderActionMenu(folderId?: string) {
1023
+ if (!folderId) {
1024
+ return;
1025
+ }
1026
+
1027
+ folderActionMenu.value[folderId] = false;
1028
+ }
1029
+
1030
+ function openEditFolderDialog(
1031
+ folder: Partial<TDocument> & Record<string, any>
1032
+ ) {
1033
+ selectedFolder.value = {
1034
+ ...folder,
1035
+ parentId: folder.parentId || activeParentId.value,
1036
+ };
1037
+ editFolderTitle.value = String(folder.name || "");
1038
+ editFolderRemarks.value = String(folder.remarks || "");
1039
+ editFolderDialog.value = true;
1040
+ closeFolderActionMenu(String(folder._id || ""));
1041
+ }
1042
+
1043
+ function openDeleteFolderDialog(
1044
+ folder: Partial<TDocument> & Record<string, any>
1045
+ ) {
1046
+ selectedFolder.value = folder;
1047
+ deleteFolderDialog.value = true;
1048
+ closeFolderActionMenu(String(folder._id || ""));
1049
+ }
1050
+
1051
+ async function handleApplyFolderEdit() {
1052
+ const folderId = String(selectedFolder.value?._id || "");
1053
+
1054
+ if (!folderId) {
1055
+ showMessage("Failed to update folder", "error");
1056
+ return;
1057
+ }
1058
+
1059
+ if (!editFolderTitle.value) {
1060
+ showMessage("Folder title is required", "error");
1061
+ return;
1062
+ }
1063
+
1064
+ folderUpdateLoading.value = true;
1065
+ try {
1066
+ const response: any = await updateFolderById(folderId, {
1067
+ name: editFolderTitle.value,
1068
+ remarks: editFolderRemarks.value,
1069
+ parentId: selectedFolder.value?.parentId || activeParentId.value,
1070
+ });
1071
+
1072
+ showMessage(
1073
+ response?.message || "Folder updated successfully!",
1074
+ "success"
1075
+ );
1076
+ await getDocuments();
1077
+ selectedFolder.value = null;
1078
+ editFolderDialog.value = false;
1079
+ } catch (error: any) {
1080
+ showMessage(
1081
+ error?.response?._data?.message || "Failed to update folder",
1082
+ "error"
1083
+ );
1084
+ } finally {
1085
+ folderUpdateLoading.value = false;
1086
+ }
1087
+ }
1088
+
1089
+ async function handleDeleteFolderUiConfirm() {
1090
+ const folderId = String(selectedFolder.value?._id || "");
1091
+
1092
+ if (!folderId) {
1093
+ showMessage("Failed to delete folder", "error");
1094
+ return;
1095
+ }
1096
+
1097
+ folderDeleteLoading.value = true;
1098
+ try {
1099
+ const response = await deleteFolderById(folderId);
1100
+ showMessage(response.message || "Folder deleted successfully!", "success");
1101
+ await getDocuments();
1102
+ selectedFolder.value = null;
1103
+ deleteFolderDialog.value = false;
1104
+ } catch (error: any) {
1105
+ showMessage(
1106
+ error?.response?._data?.message || "Failed to delete folder",
1107
+ "error"
1108
+ );
1109
+ } finally {
1110
+ folderDeleteLoading.value = false;
1111
+ }
1112
+ }
1113
+
852
1114
  async function handleCreateFolder() {
853
1115
  if (!folderTitle.value) {
854
1116
  showMessage("Folder title is required", "error");
@@ -977,4 +1239,35 @@ function selectFileType(type: string) {
977
1239
  border-radius: 8px;
978
1240
  background: #fafafa;
979
1241
  }
1242
+
1243
+ .folder-action-btn {
1244
+ color: #38475a;
1245
+ }
1246
+
1247
+ .folder-action-list {
1248
+ min-width: 180px;
1249
+ border-radius: 8px;
1250
+ background: #fff;
1251
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.22);
1252
+ overflow: hidden;
1253
+ }
1254
+
1255
+ .folder-action-item {
1256
+ min-height: 40px;
1257
+ border-bottom: 1px solid #eef0f2;
1258
+ color: #38475a;
1259
+ }
1260
+
1261
+ .folder-action-item:last-child {
1262
+ border-bottom: none;
1263
+ }
1264
+
1265
+ .folder-dialog-card {
1266
+ border-radius: 6px;
1267
+ overflow: hidden;
1268
+ }
1269
+
1270
+ .folder-dialog-toolbar {
1271
+ border-bottom: 1px solid #eef0f2;
1272
+ }
980
1273
  </style>