@7365admin1/layer-common 3.0.30 → 3.0.31-staging.11

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 (27) hide show
  1. package/components/AddPassKeyToVisitor.vue +115 -53
  2. package/components/DashboardMain.vue +29 -33
  3. package/components/InvitationMain.vue +4 -3
  4. package/components/ManageChecklistMain.vue +32 -25
  5. package/components/PhotoUpload.vue +19 -4
  6. package/components/VisitorForm.vue +385 -59
  7. package/components/VisitorManagement.vue +109 -30
  8. package/composables/useCleaningSchedulePermission.ts +16 -51
  9. package/package.json +1 -1
  10. package/types/customer.d.ts +2 -1
  11. package/types/people.d.ts +2 -0
  12. /package/components/{NFC → Nfc}/NFCPatrolReportMain.vue +0 -0
  13. /package/components/{NFC → Nfc}/NFCPatrolRouteForm.vue +0 -0
  14. /package/components/{NFC → Nfc}/NFCPatrolRouteMain.vue +0 -0
  15. /package/components/{NFC → Nfc}/NFCPatrolSettings.vue +0 -0
  16. /package/components/{NFC → Nfc}/NFCTagForm.vue +0 -0
  17. /package/components/{NFC → Nfc}/NFCTagMain.vue +0 -0
  18. /package/components/{NFC → Nfc}/PatrolReport/DailyReportTab.vue +0 -0
  19. /package/components/{NFC → Nfc}/PatrolReport/MonthlyReportTab.vue +0 -0
  20. /package/components/{NFC → Nfc}/PatrolReport/PatrolActivityTable.vue +0 -0
  21. /package/components/{NFC → Nfc}/PatrolReport/PatrolReportTab.vue +0 -0
  22. /package/components/{NFC → Nfc}/PatrolReport/PatrolRouteSummary.vue +0 -0
  23. /package/components/{NFC → Nfc}/PatrolReport/RemarksDialog.vue +0 -0
  24. /package/components/{NFC → Nfc}/PatrolReport/ReportEmptyState.vue +0 -0
  25. /package/components/{NFC → Nfc}/PatrolReport/ReportFilters.vue +0 -0
  26. /package/components/{NFC → Nfc}/PatrolReport/ReportLocationHeader.vue +0 -0
  27. /package/components/{NFC → Nfc}/PatrolReport/SummaryReportTable.vue +0 -0
@@ -32,11 +32,6 @@
32
32
  color="blue"
33
33
  variant="tonal"
34
34
  size="small"
35
- closable
36
- @click:close="
37
- existingPassToBeReplaced = existingPass;
38
- existingPass = null;
39
- "
40
35
  >
41
36
  {{ existingPass.prefixAndName }}
42
37
  </v-chip>
@@ -45,9 +40,9 @@
45
40
 
46
41
  <!-- New pass selector -->
47
42
  <v-autocomplete
48
- v-if="!existingPass"
49
43
  v-model="selectedPass"
50
44
  v-model:search="passInput"
45
+ :label="existingPass?.keyId ? 'Change pass' : undefined"
51
46
  :hide-no-data="false"
52
47
  :items="passItems"
53
48
  item-title="prefixAndName"
@@ -85,20 +80,20 @@
85
80
  class="d-flex flex-wrap ga-1 mb-2"
86
81
  >
87
82
  <v-chip
88
- v-for="key in existingKeys"
83
+ v-for="key in existingKeys.filter((k) => k.status != 'Removed')"
89
84
  :key="key.keyId"
90
85
  prepend-icon="mdi-key"
91
86
  color="orange"
92
87
  variant="tonal"
93
88
  size="small"
94
89
  closable
95
- @click:close="removeExistingKey(key.keyId)"
90
+ @click:close="removeExistingKey(key)"
96
91
  >
97
92
  {{ key.prefixAndName }}
98
93
  </v-chip>
99
- <span class="text-caption text-medium-emphasis align-self-center"
100
- >(current)</span
101
- >
94
+ <span class="text-caption text-medium-emphasis align-self-center">
95
+ (current)
96
+ </span>
102
97
  </div>
103
98
 
104
99
  <!-- New keys selector -->
@@ -116,6 +111,8 @@
116
111
  density="compact"
117
112
  small-chips
118
113
  :loading="fetchKeysPending"
114
+ clearable
115
+ @update:model-value="selectKeys"
119
116
  >
120
117
  <template v-slot:chip="{ props: chipProps, item }">
121
118
  <v-chip
@@ -205,7 +202,7 @@ const processing = ref(false);
205
202
  const errorMessage = ref("");
206
203
 
207
204
  // New selections
208
- const selectedPass = ref<string>("");
205
+ const selectedPass = ref<string | null>(null);
209
206
  const selectedKeys = ref<string[]>([]);
210
207
  const passInput = ref("");
211
208
  const keyInput = ref("");
@@ -213,12 +210,15 @@ const passItems = ref<TPassKey[]>([]);
213
210
  const keyItems = ref<TPassKey[]>([]);
214
211
 
215
212
  // Existing pass/keys (editable)
216
- const existingPass = ref<{ keyId: string; prefixAndName: string } | null>(null);
217
- const existingPassToBeReplaced = ref<{
213
+ const existingPass = ref<{
218
214
  keyId: string;
219
215
  prefixAndName: string;
216
+ status?: string;
220
217
  } | null>(null);
221
- const existingKeys = ref<{ keyId: string; prefixAndName: string }[]>([]);
218
+
219
+ const existingKeys = ref<
220
+ { keyId: string; prefixAndName: string; status?: string }[]
221
+ >([]);
222
222
 
223
223
  const isEditMode = computed(() => prop.mode === "edit");
224
224
  const showKeys = computed(() => prop.visitor.type === "contractor");
@@ -237,28 +237,82 @@ const passTypesComputed = computed(() => {
237
237
  // Initialise existing values in edit mode
238
238
  onMounted(() => {
239
239
  if (isEditMode.value) {
240
- const vPass = prop.visitor.visitorPass;
240
+ const vPass = prop.visitor.visitorPass?.filter(
241
+ (i) => i.status != "Removed"
242
+ );
241
243
  if (Array.isArray(vPass) && vPass.length > 0) {
242
244
  const p = vPass[0] as any;
243
- existingPass.value = { keyId: p.keyId, prefixAndName: p.prefixAndName };
245
+ existingPass.value = {
246
+ keyId: p.keyId,
247
+ prefixAndName: p.prefixAndName,
248
+ status: p.status,
249
+ };
244
250
  }
245
- const vKeys = prop.visitor.passKeys;
251
+ const vKeys = prop.visitor.passKeys?.filter((i) => i.status != "Removed");
246
252
  if (Array.isArray(vKeys) && vKeys.length > 0) {
247
253
  existingKeys.value = (vKeys as any[]).map((k) => ({
248
254
  keyId: k.keyId,
249
255
  prefixAndName: k.prefixAndName,
256
+ status: k.status,
250
257
  }));
251
258
  }
252
259
  }
253
260
  });
254
261
 
255
- function removeExistingKey(keyId: string) {
256
- existingKeys.value = existingKeys.value.filter((k) => k.keyId !== keyId);
262
+ const previouslyRemovedKeys = ref<any>([]);
263
+
264
+ function removeExistingKey(key: Record<string, any>) {
265
+ const originalKey = existingKeys.value.find((k) => k.keyId === key.keyId);
266
+
267
+ if (
268
+ originalKey &&
269
+ !previouslyRemovedKeys.value.some((k: any) => k.keyId === key.keyId)
270
+ ) {
271
+ previouslyRemovedKeys.value.push({ ...originalKey });
272
+ }
273
+
274
+ existingKeys.value = existingKeys.value.map((i) => {
275
+ if (i.keyId === key.keyId) {
276
+ return {
277
+ ...i,
278
+ status: "Removed",
279
+ };
280
+ }
281
+ return i;
282
+ });
283
+ }
284
+
285
+ function selectKeys() {
286
+ const currentSelections = [...selectedKeys.value];
287
+
288
+ currentSelections.forEach((selectedId) => {
289
+ const removedMatch = previouslyRemovedKeys.value.find(
290
+ (k: any) => k.keyId === selectedId
291
+ );
292
+
293
+ if (removedMatch) {
294
+ existingKeys.value = existingKeys.value.map((i) => {
295
+ if (i.keyId === selectedId) {
296
+ return {
297
+ ...i,
298
+ status: removedMatch.status,
299
+ };
300
+ }
301
+ return i;
302
+ });
303
+
304
+ previouslyRemovedKeys.value = previouslyRemovedKeys.value.filter(
305
+ (k: any) => k.keyId !== selectedId
306
+ );
307
+
308
+ selectedKeys.value = selectedKeys.value.filter((id) => id !== selectedId);
309
+ }
310
+ });
257
311
  }
258
312
 
259
313
  // Exclude already-held pass/keys from selectable lists
260
314
  const existingPassId = computed(() => existingPass.value?.keyId);
261
- const existingKeyIds = computed(() => existingKeys.value.map((k) => k.keyId));
315
+ // const existingKeyIds = computed(() => existingKeys.value.map((k) => k.keyId));
262
316
 
263
317
  const {
264
318
  data: passesData,
@@ -294,43 +348,50 @@ const {
294
348
  })
295
349
  );
296
350
 
297
- watch(passesData, (data: any) => {
298
- const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
299
- passItems.value = all.filter((p) => p._id !== existingPassId.value);
300
- });
351
+ // watch(passesData, (data: any) => {
352
+ // const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
353
+ // passItems.value = all.filter((p) => p._id !== existingPassId.value);
354
+ // });
301
355
 
302
- watch(keysData, (data: any) => {
303
- const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
304
- keyItems.value = all.filter((k) => !existingKeyIds.value.includes(k._id!));
305
- });
356
+ // watch(keysData, (data: any) => {
357
+ // const all: TPassKey[] = Array.isArray(data?.items) ? data.items : [];
358
+ // keyItems.value = all.filter((k) => !existingKeyIds.value.includes(k._id!));
359
+ // });
306
360
 
307
361
  // Re-filter when existing items change
308
362
  watch(
309
- [existingPassId, existingKeyIds],
363
+ [existingPassId, existingKeys, passesData, keysData],
310
364
  () => {
311
365
  const rawPasses = (passesData.value as any)?.items ?? [];
312
366
  passItems.value = rawPasses.filter(
313
367
  (p: TPassKey) => p._id !== existingPassId.value
314
368
  );
369
+
315
370
  const rawKeys = (keysData.value as any)?.items ?? [];
316
- keyItems.value = rawKeys.filter(
317
- (k: TPassKey) => !existingKeyIds.value.includes(k._id!)
371
+ const manuallyRemovedKeys = existingKeys.value
372
+ .filter((k) => k.status === "Removed")
373
+ .map((k) => ({
374
+ _id: k.keyId,
375
+ prefixAndName: k.prefixAndName,
376
+ }));
377
+
378
+ const allExistingKeyIds = existingKeys.value.map((k) => k.keyId);
379
+
380
+ const filteredRawKeys = rawKeys.filter(
381
+ (k: TPassKey) => !allExistingKeyIds.includes(k._id!)
318
382
  );
383
+
384
+ keyItems.value = [...filteredRawKeys, ...manuallyRemovedKeys];
385
+ console.log("keyItems: ", keyItems.value);
319
386
  },
320
- { deep: true }
387
+ { deep: true, immediate: true }
321
388
  );
322
389
 
323
390
  const canSubmit = computed(() => {
324
- if (isEditMode.value) {
325
- // allow save if anything changed: existing items modified OR new items selected
326
- return (
327
- existingPass.value !== null ||
328
- selectedPass.value ||
329
- existingKeys.value.length > 0 ||
330
- selectedKeys.value.length > 0
331
- );
332
- }
333
- return !!selectedPass.value || selectedKeys.value.length > 0;
391
+ const hasRemovedKeys = existingKeys.value.some((k) => k.status === "Removed");
392
+ return (
393
+ !!selectedPass.value || selectedKeys.value.length > 0 || hasRemovedKeys
394
+ );
334
395
  });
335
396
 
336
397
  async function handleSubmit() {
@@ -343,16 +404,13 @@ async function handleSubmit() {
343
404
 
344
405
  // Build final pass list: kept existing + newly selected
345
406
  const finalPasses: { keyId: string; status?: string; add?: boolean }[] = [];
346
- if (existingPass.value) {
347
- finalPasses.push({ keyId: existingPass.value.keyId, status: "Removed" });
348
- }
349
- if (existingPassToBeReplaced.value) {
350
- finalPasses.push({
351
- keyId: existingPassToBeReplaced.value.keyId,
352
- status: "Removed",
353
- });
354
- }
355
407
  if (selectedPass.value) {
408
+ if (existingPass.value) {
409
+ finalPasses.push({
410
+ keyId: existingPass.value.keyId,
411
+ status: "Removed",
412
+ });
413
+ }
356
414
  finalPasses.push({
357
415
  keyId: selectedPass.value,
358
416
  status: "Not Returned",
@@ -364,13 +422,17 @@ async function handleSubmit() {
364
422
  // Build final keys list: kept existing + newly selected
365
423
  if (showKeys.value) {
366
424
  const finalKeys: { keyId: string; status?: string; add?: boolean }[] = [
367
- ...existingKeys.value.map((k) => ({ keyId: k.keyId })),
425
+ ...existingKeys.value.map((k) => ({
426
+ keyId: k.keyId,
427
+ status: k.status,
428
+ })),
368
429
  ...selectedKeys.value.map((keyId) => ({
369
430
  keyId,
370
431
  status: "Not Returned",
371
432
  add: true,
372
433
  })),
373
434
  ];
435
+
374
436
  payload.passKeys = finalKeys;
375
437
  }
376
438
 
@@ -1545,30 +1545,16 @@ const { userAppRole } = useLocalSetup();
1545
1545
  const { downloadPDF } = usePDFDownload();
1546
1546
  const route = useRoute();
1547
1547
 
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
- };
1561
-
1562
1548
  const propertyWidgetPermissions: Record<string, string[]> = {
1563
1549
  workOrders: [
1564
- "workOrder:see-all-work-orders",
1550
+ "work_orders:see-all-work-orders",
1565
1551
  "work-order:see-all-work-orders",
1566
1552
  ],
1567
1553
  incidents: ["incident-reports:see-incident-reports"],
1568
1554
  visitors: ["visitorManagement:see-all-visitor"],
1569
1555
  facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
1570
1556
  activity: [
1571
- "workOrder:see-all-work-orders",
1557
+ "work_orders:see-all-work-orders",
1572
1558
  "work-order:see-all-work-orders",
1573
1559
  ],
1574
1560
  upcomingEvents: ["event-mgmt:see-all-event"],
@@ -1578,7 +1564,7 @@ const propertyWidgetPermissions: Record<string, string[]> = {
1578
1564
  "facility-booking-mgmt:see-all-facility-booking",
1579
1565
  ],
1580
1566
  workOrderStatus: [
1581
- "workOrder:see-all-work-orders",
1567
+ "work_orders:see-all-work-orders",
1582
1568
  "work-order:see-all-work-orders",
1583
1569
  ],
1584
1570
  feedbacks: ["feedbacks:see-all-feedback"],
@@ -1587,27 +1573,30 @@ const propertyWidgetPermissions: Record<string, string[]> = {
1587
1573
 
1588
1574
  const moduleWidgetPermissions: Record<string, string[]> = {
1589
1575
  openWorkOrder: [
1576
+ "work_orders:see-all-work-orders",
1590
1577
  "work_order:see-all-work-orders",
1591
1578
  "work-order:see-all-work-orders",
1592
1579
  "work-orders:see-all-work-orders",
1593
1580
  ],
1594
1581
  workOrders: [
1582
+ "work_orders:see-all-work-orders",
1595
1583
  "work_order:see-all-work-orders",
1596
1584
  "work-order:see-all-work-orders",
1597
1585
  "work-orders:see-all-work-orders",
1598
1586
  ],
1599
1587
  activity: [
1588
+ "work_orders:see-all-work-orders",
1600
1589
  "work_order:see-all-work-orders",
1601
1590
  "work-order:see-all-work-orders",
1602
1591
  "work-orders:see-all-work-orders",
1592
+ "work_orders:see-all-work-orders",
1603
1593
  ],
1604
- taskSchedule: [
1605
- "cleaning-schedule-mgmt:see-all-schedules",
1606
- "schedule-task-mgmt:see-all-schedule-tasks",
1607
- ],
1608
- taskCompleted: [
1609
- "cleaning-schedule-mgmt:see-all-schedules",
1610
- "schedule-task-mgmt:see-all-schedule-tasks",
1594
+ recentActivity: [
1595
+ "work_orders:see-all-work-orders",
1596
+ "work_order:see-all-work-orders",
1597
+ "work-order:see-all-work-orders",
1598
+ "work-orders:see-all-work-orders",
1599
+ "work_orders:see-all-work-orders",
1611
1600
  ],
1612
1601
  staffStatus: ["attendance-mgmt:see-all-attendance"],
1613
1602
  attendance: ["attendance-mgmt:see-all-attendance"],
@@ -1630,9 +1619,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
1630
1619
  activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1631
1620
  };
1632
1621
 
1622
+ function hasSchedulePermission(permissions: string[], action: string) {
1623
+ return permissions.some((permission) =>
1624
+ permission.endsWith(`-schedule-mgmt:${action}`)
1625
+ );
1626
+ }
1627
+
1633
1628
  function canViewWidget(key: string) {
1634
1629
  const permissions = userAppRole.value?.permissions;
1635
- const dashboardPermission = dashboardWidgetPermissions[key];
1636
1630
  const modulePermissions = isPropertyMode.value
1637
1631
  ? propertyWidgetPermissions[key]
1638
1632
  : moduleWidgetPermissions[key];
@@ -1640,20 +1634,22 @@ function canViewWidget(key: string) {
1640
1634
  if (!permissions) return true;
1641
1635
  if (permissions.includes("*")) return true;
1642
1636
 
1637
+ if (
1638
+ !isPropertyMode.value &&
1639
+ ["taskSchedule", "taskCompleted"].includes(key)
1640
+ ) {
1641
+ return hasSchedulePermission(permissions, "see-all-schedules");
1642
+ }
1643
+
1643
1644
  if (isPropertyMode.value && modulePermissions) {
1644
1645
  return modulePermissions.some((permission) =>
1645
1646
  permissions.includes(permission)
1646
1647
  );
1647
1648
  }
1648
1649
 
1649
- if (dashboardPermission || modulePermissions) {
1650
- return (
1651
- (dashboardPermission && permissions.includes(dashboardPermission)) ||
1652
- Boolean(
1653
- modulePermissions?.some((permission) =>
1654
- permissions.includes(permission)
1655
- )
1656
- )
1650
+ if (modulePermissions) {
1651
+ return modulePermissions.some((permission) =>
1652
+ permissions.includes(permission)
1657
1653
  );
1658
1654
  }
1659
1655
 
@@ -221,7 +221,7 @@ const headers = computed(() => {
221
221
  title: "Status",
222
222
  value: "status",
223
223
  },
224
-
224
+
225
225
  ];
226
226
 
227
227
  if (props.status === "pending" && props.cancelInvitation) {
@@ -270,10 +270,11 @@ const {
270
270
  page: page.value,
271
271
  status: props.status,
272
272
  search: headerSearch.value,
273
- type: "user-invite,member-invite",
273
+ // type: "user-invite,member-invite",
274
+ type: "member-invite",
274
275
  app: props.app,
275
276
  orgId: props.org,
276
- siteId: props.siteId,
277
+ // siteId: props.siteId,
277
278
  }),
278
279
  {
279
280
  watch: [() => props.status]
@@ -305,31 +305,38 @@
305
305
 
306
306
  <v-divider />
307
307
 
308
- <v-card-actions class="pa-0">
309
- <v-btn
310
- color="grey"
311
- variant="flat"
312
- rounded="0"
313
- class="flex-grow-1 text-none"
314
- height="48"
315
- :disabled="rejectModal.submitting"
316
- @click="closeRejectDialog"
317
- >
318
- Cancel
319
- </v-btn>
320
- <v-btn
321
- color="error"
322
- variant="flat"
323
- rounded="0"
324
- class="flex-grow-1 text-none"
325
- height="48"
326
- :loading="rejectModal.submitting"
327
- :disabled="!isRejectModalValid || rejectModal.submitting"
328
- @click="submitRejectDialog"
329
- >
330
- Confirm
331
- </v-btn>
332
- </v-card-actions>
308
+ <v-toolbar class="pa-0" density="compact">
309
+ <v-row no-gutters>
310
+ <v-col cols="6" class="pa-0">
311
+ <v-btn
312
+ block
313
+ variant="text"
314
+ class="text-none"
315
+ size="large"
316
+ height="48"
317
+ :disabled="rejectModal.submitting"
318
+ @click="closeRejectDialog"
319
+ >
320
+ Cancel
321
+ </v-btn>
322
+ </v-col>
323
+
324
+ <v-col cols="6" class="pa-0">
325
+ <v-btn
326
+ block
327
+ variant="flat"
328
+ color="black"
329
+ class="text-none font-weight-bold rounded-0"
330
+ height="48"
331
+ :loading="rejectModal.submitting"
332
+ :disabled="!isRejectModalValid || rejectModal.submitting"
333
+ @click="submitRejectDialog"
334
+ >
335
+ Confirm
336
+ </v-btn>
337
+ </v-col>
338
+ </v-row>
339
+ </v-toolbar>
333
340
  </v-card>
334
341
  </v-dialog>
335
342
 
@@ -59,6 +59,21 @@
59
59
  </v-col>
60
60
  </v-row>
61
61
 
62
+ <v-dialog v-model="showPhotoPreview" max-width="900">
63
+ <v-card>
64
+ <v-card-actions class="pa-2 justify-end">
65
+ <v-btn
66
+ icon="mdi-close"
67
+ variant="text"
68
+ @click="showPhotoPreview = false"
69
+ />
70
+ </v-card-actions>
71
+ <v-card-text class="pa-2 pt-0">
72
+ <v-img :src="selectedPhoto" contain max-height="80vh" />
73
+ </v-card-text>
74
+ </v-card>
75
+ </v-dialog>
76
+
62
77
  <v-dialog v-model="photoOptionsDialog" max-width="420">
63
78
  <v-card>
64
79
  <v-card-title class="d-flex align-center pa-4">
@@ -196,6 +211,8 @@ const photos = ref<string[]>([...props.modelValue]);
196
211
  const photosIds = ref<string[]>([...props.photoIds]);
197
212
  const photoOptionsDialog = ref(false);
198
213
  const showCameraDialog = ref(false);
214
+ const showPhotoPreview = ref(false);
215
+ const selectedPhoto = ref("");
199
216
  const videoRef = ref<HTMLVideoElement | null>(null);
200
217
  const canvasRef = ref<HTMLCanvasElement | null>(null);
201
218
  const fileInputRef = ref<HTMLInputElement | null>(null);
@@ -205,10 +222,8 @@ const uploading = ref(false);
205
222
  const { addFile } = useFile();
206
223
 
207
224
  function viewPhoto(photoData: string) {
208
- const link = document.createElement("a");
209
- link.href = photoData;
210
- link.target = "_blank";
211
- link.click();
225
+ selectedPhoto.value = photoData;
226
+ showPhotoPreview.value = true;
212
227
  }
213
228
 
214
229
  watch(