@7365admin1/layer-common 4.135.0 → 4.137.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -4
  2. package/assets/css/primitives.css +54 -11
  3. package/assets/css/screens.css +3 -3
  4. package/assets/css/tokens.css +17 -12
  5. package/components/AnprCameraBadge.vue +1 -1
  6. package/components/CameraWall.vue +7 -7
  7. package/components/CameraWallTile.vue +8 -8
  8. package/components/Chat/ListCard.vue +1 -1
  9. package/components/CleaningScheduleMain.vue +56 -20
  10. package/components/ClientMain.vue +1 -1
  11. package/components/ClientSubscriptionForm.vue +1 -1
  12. package/components/DashboardMain.vue +13 -13
  13. package/components/HidAccessLogDashboard.vue +3 -3
  14. package/components/HidIntercomManagement.vue +14 -14
  15. package/components/HidQrCodeConfiguration.vue +3 -3
  16. package/components/HidReaderManagement.vue +1 -1
  17. package/components/InventoryPhotoThumbs.vue +1 -1
  18. package/components/Layout/Header.vue +2 -2
  19. package/components/Layout/NavigationDrawer.vue +1 -1
  20. package/components/Nfc/NFCRouteScheduleTimes.vue +1 -1
  21. package/components/PlateNumberDisplay.vue +1 -1
  22. package/components/RolePermissionFormCreate.vue +6 -2
  23. package/components/RolePermissionFormPreviewUpdate.vue +20 -4
  24. package/components/ScheduleAreaMain.vue +6 -9
  25. package/components/ScheduleGuide.vue +101 -0
  26. package/components/ScheduleTaskForm.vue +88 -7
  27. package/components/ScheduleTaskMain.vue +163 -56
  28. package/components/StatusChip.vue +1 -1
  29. package/components/VisitorsReportPreview.vue +2 -2
  30. package/composables/useCleaningPermission.ts +3 -0
  31. package/composables/useCommonPermission.ts +12 -0
  32. package/composables/useLandscapingPermission.ts +2 -0
  33. package/composables/useMechanicalElectricalPermission.ts +3 -0
  34. package/composables/usePestControlPermission.ts +2 -0
  35. package/composables/usePoolMaintenancePermission.ts +2 -0
  36. package/constants/permissions.ts +3 -1
  37. package/package.json +1 -1
  38. package/utils/breadcrumb.ts +7 -0
  39. package/utils/nav-sections.ts +2 -0
  40. package/utils/org-modules.ts +3 -0
  41. package/utils/permission-labels.ts +6 -0
  42. package/utils/schedule-task.ts +106 -23
  43. package/utils/security-catalogue.ts +1 -1
  44. package/utils/trade-catalogue.ts +43 -0
@@ -5,10 +5,52 @@
5
5
  :org-id="orgId"
6
6
  :site="site"
7
7
  :service-type="serviceType"
8
- text="An extra visit adds one more round to your daily schedule at the time you choose, once or on a repeat. Staff on this site get a push notification when it is added."
9
- />
8
+ text="An extra visit adds one more round to the Daily Checklist at a time you choose - once, or on a repeat."
9
+ how-title="How extra visits work"
10
+ how-key="extra-visits"
11
+ >
12
+ <ol class="sg-steps">
13
+ <li>
14
+ Every night the app makes the <strong>Daily Checklist</strong>
15
+ for every area on this site. You do not need to do anything for
16
+ that.
17
+ </li>
18
+ <li>
19
+ An <strong>extra visit</strong> adds one more round to that
20
+ checklist, in the areas you pick, at the time you set - once, or
21
+ repeating (every day, weekdays, chosen days, or monthly).
22
+ </li>
23
+ <li>
24
+ At that time, staff see the extra round in their mobile app's
25
+ daily checklist (with a push notification) and tick each unit as
26
+ they finish it.
27
+ </li>
28
+ <li>
29
+ Here and in the Daily Checklist you see how it went:
30
+ <strong>Done</strong>, <strong>In progress</strong>,
31
+ <strong>Needs redo</strong> or <strong>Missed</strong>.
32
+ </li>
33
+ </ol>
34
+ <div class="sg-example">
35
+ <strong>Example:</strong> the lobby toilets get busy at lunch. Add
36
+ an extra visit called "Lunch toilet check", pick the area "Lobby
37
+ Toilets", set 2:00 pm, every weekday. From the next weekday, staff
38
+ get one more toilet round at 2:00 pm, on top of their normal ones.
39
+ </div>
40
+ <div class="sg-h">What the words mean</div>
41
+ <dl class="sg-legend">
42
+ <template v-for="h in STATUS_HELP" :key="h.word">
43
+ <dt><StatusChip :status="h.word" :label="h.word" /></dt>
44
+ <dd>{{ h.meaning }}</dd>
45
+ </template>
46
+ <template v-for="h in RESULT_HELP" :key="'r' + h.word">
47
+ <dt><StatusChip :status="h.word" :label="h.word" /></dt>
48
+ <dd>Last visit: {{ h.meaning }}</dd>
49
+ </template>
50
+ </dl>
51
+ </ScheduleGuide>
10
52
  <TableMain
11
- :title="'Extra Visits (Scheduled)'"
53
+ :title="'Extra Visits'"
12
54
  :items="items"
13
55
  :headers="headers"
14
56
  :loading="loading"
@@ -26,9 +68,14 @@
26
68
  <div class="st-empty">
27
69
  <strong>No extra visits yet.</strong>
28
70
  <span>
29
- An extra visit adds one more round to your daily
30
- {{ scheduleName }} at a time you choose. Set up your Areas and
31
- Units first, then add one.
71
+ Your Daily Checklist already covers the normal rounds. Add an
72
+ extra visit when an area needs one more round at a set time -
73
+ for example "Lunch toilet check, Lobby Toilets, 2:00 pm every
74
+ weekday".
75
+ </span>
76
+ <span v-if="canCreateScheduleTask">
77
+ Press <strong>Add Extra Visit</strong> above to start. Your
78
+ Areas and Units must be set up first.
32
79
  </span>
33
80
  </div>
34
81
  </template>
@@ -61,27 +108,6 @@
61
108
  </v-row>
62
109
  </template>
63
110
 
64
- <template #item.areas="{ value }">
65
- <div v-if="value && value.length > 0">
66
- <StatusChip
67
- v-for="(area, idx) in value.slice(0, 2)"
68
- :key="idx"
69
- class="mr-1 mb-1"
70
- :dot="false"
71
- tone="neutral"
72
- :label="area.name"
73
- />
74
- <StatusChip
75
- v-if="value.length > 2"
76
- class="mb-1"
77
- :dot="false"
78
- tone="neutral"
79
- :label="`+${value.length - 2} more`"
80
- />
81
- </div>
82
- <span v-else>N/A</span>
83
- </template>
84
-
85
111
  <template #item.when="{ item }">
86
112
  <span class="st-when">{{ item.whenText }}</span>
87
113
  </template>
@@ -91,24 +117,34 @@
91
117
  </template>
92
118
 
93
119
  <template #item.lastRun="{ item }">
94
- <StatusChip
95
- v-if="item.lastRun && item.lastRunStatus === 'done'"
96
- status="Done"
97
- :label="item.lastRunText"
98
- />
99
- <StatusChip
100
- v-else-if="item.lastRun && item.lastRunStatus === 'missed'"
101
- status="Missed"
102
- :label="item.lastRunText"
103
- />
104
- <span v-else>{{ item.lastRunText }}</span>
120
+ <div class="st-cell">
121
+ <StatusChip
122
+ v-if="item.lastRun && item.lastRunWord"
123
+ :status="item.lastRunWord"
124
+ :label="item.lastRunText"
125
+ />
126
+ <span v-else>{{ item.lastRunText }}</span>
127
+ <a
128
+ v-if="item.lastRun"
129
+ href="#"
130
+ class="st-link"
131
+ @click.prevent.stop="openChecklist(item)"
132
+ >
133
+ See that day's checklist
134
+ </a>
135
+ </div>
105
136
  </template>
106
137
 
107
138
  <template #item.status="{ item }">
108
- <StatusChip
109
- :status="item.stateLabel"
110
- :label="item.stateLabel"
111
- />
139
+ <div class="st-cell">
140
+ <StatusChip
141
+ :status="item.stateLabel"
142
+ :label="item.stateLabel"
143
+ />
144
+ <span v-if="item.stateKey === 'ended'" class="st-note">
145
+ {{ item.finishedText }}
146
+ </span>
147
+ </div>
112
148
  </template>
113
149
 
114
150
  <template #item.actions="{ item }">
@@ -123,6 +159,17 @@
123
159
  >
124
160
  Edit
125
161
  </v-btn>
162
+ <v-btn
163
+ v-if="canUpdateScheduleTask && item.stateKey === 'ended'"
164
+ size="small"
165
+ variant="tonal"
166
+ color="primary"
167
+ class="text-none"
168
+ prepend-icon="mdi-calendar-refresh"
169
+ @click.stop="onExtendRow(item)"
170
+ >
171
+ Extend or repeat
172
+ </v-btn>
126
173
  <v-btn
127
174
  v-if="canUpdateScheduleTask && item.stateKey !== 'ended'"
128
175
  size="small"
@@ -155,6 +202,7 @@
155
202
  v-model:show="showForm"
156
203
  :mode="formMode"
157
204
  :taskData="selectedTask"
205
+ :start-step="formStartStep"
158
206
  :site="props.site"
159
207
  :serviceType="props.serviceType"
160
208
  @refresh="getTaskRefresh"
@@ -194,7 +242,7 @@
194
242
 
195
243
  <v-row no-gutters class="mb-2">
196
244
  <v-col cols="5" class="text-subtitle-2 font-weight-bold">
197
- Next run:
245
+ Next visit:
198
246
  </v-col>
199
247
  <v-col cols="7" class="text-subtitle-2">
200
248
  {{ selectedNext || "-" }}
@@ -203,10 +251,18 @@
203
251
 
204
252
  <v-row no-gutters class="mb-2">
205
253
  <v-col cols="5" class="text-subtitle-2 font-weight-bold">
206
- Last run:
254
+ Last visit:
207
255
  </v-col>
208
256
  <v-col cols="7" class="text-subtitle-2">
209
257
  {{ lastRunLabel(selectedTask) }}
258
+ <a
259
+ v-if="selectedTask.lastRun"
260
+ href="#"
261
+ class="st-link d-block"
262
+ @click.prevent="openChecklist(selectedTask)"
263
+ >
264
+ See that day's checklist
265
+ </a>
210
266
  </v-col>
211
267
  </v-row>
212
268
 
@@ -319,7 +375,7 @@
319
375
  </div>
320
376
  <span v-else class="text-caption text-disabled"
321
377
  >No units in this area, so it is skipped when
322
- this visit runs.</span
378
+ this visit is added.</span
323
379
  >
324
380
  </v-card-text>
325
381
  </div>
@@ -348,6 +404,12 @@
348
404
  :status="stateOf(selectedTask).label"
349
405
  :label="stateOf(selectedTask).label"
350
406
  />
407
+ <div
408
+ v-if="stateOf(selectedTask).key === 'ended'"
409
+ class="st-note mt-1"
410
+ >
411
+ {{ finishedReason(selectedTask as any) }}
412
+ </div>
351
413
  </v-col>
352
414
  </v-row>
353
415
 
@@ -383,8 +445,8 @@
383
445
  </v-card-title>
384
446
 
385
447
  <v-card-text class="screen-modal__body">
386
- This stops all future runs of this extra visit. Visits that already
387
- happened are kept. Delete it?
448
+ This stops all future extra visits from it. Visits that already
449
+ happened stay on their Daily Checklists. Delete it?
388
450
  </v-card-text>
389
451
 
390
452
  <div class="screen-modal__footer">
@@ -422,8 +484,12 @@ import useScheduleTask from "../composables/useScheduleTask";
422
484
  import useUtils from "../composables/useUtils";
423
485
  import useAreas from "../composables/useAreas";
424
486
  import {
487
+ RESULT_HELP,
488
+ STATUS_HELP,
425
489
  STATUS_LABELS,
490
+ describeRow,
426
491
  describeWhen,
492
+ finishedReason,
427
493
  fmtMoment,
428
494
  lastRunLabel,
429
495
  scheduleNameFor,
@@ -458,7 +524,16 @@ const canViewScheduleTaskDetails = computed(
458
524
  () => props.canViewScheduleTaskDetails
459
525
  );
460
526
 
461
- const scheduleName = scheduleNameFor(props.serviceType).title;
527
+ const checklistRoute = scheduleNameFor(props.serviceType).route;
528
+
529
+ /** Opens the Daily Checklist filtered to the day this extra visit was last added. */
530
+ function openChecklist(row: Record<string, any>) {
531
+ const day = String(row?.lastRun || "").slice(0, 10);
532
+ navigateTo({
533
+ path: `/${props.orgId}/${props.site}/${checklistRoute}`,
534
+ query: /^\d{4}-\d{2}-\d{2}$/.test(day) ? { date: day } : {},
535
+ });
536
+ }
462
537
 
463
538
  const page = ref(1);
464
539
  const pages = ref(0);
@@ -468,6 +543,7 @@ const searchInput = ref("");
468
543
  const showForm = ref(false);
469
544
  const selectedTask = ref<Record<string, any>>({});
470
545
  const formMode = ref<"add" | "edit">("add");
546
+ const formStartStep = ref(1);
471
547
  const submitting = ref(false);
472
548
  const dialogShowMoreActions = ref(false);
473
549
  const dialogDeleteTask = ref(false);
@@ -517,11 +593,10 @@ watch(dialogShowMoreActions, async (isOpen) => {
517
593
  const items = ref<any[]>([]);
518
594
 
519
595
  const headers = [
520
- { title: "Title", value: "title" },
521
- { title: "Areas", value: "areas" },
522
- { title: "When", value: "when", sortable: false },
523
- { title: "Next run", value: "nextRun", sortable: false },
524
- { title: "Last run", value: "lastRun", sortable: false },
596
+ { title: "Extra visit", value: "title" },
597
+ { title: "What will happen", value: "when", sortable: false },
598
+ { title: "Next visit", value: "nextRun", sortable: false },
599
+ { title: "Last visit", value: "lastRun", sortable: false },
525
600
  { title: "Status", value: "status", sortable: false },
526
601
  { title: "", value: "actions", sortable: false, align: "end" as const },
527
602
  ];
@@ -566,7 +641,9 @@ watchEffect(() => {
566
641
  const st = stateOf(row);
567
642
  return {
568
643
  ...row,
569
- whenText: describeWhen(row),
644
+ whenText: describeRow(row),
645
+ finishedText: finishedReason(row),
646
+ lastRunWord: ({ done: "Done", missed: "Missed", pending: "In progress" } as Record<string, string>)[row.lastRunStatus] || "",
570
647
  nextRunText: fmtMoment(row.nextRun),
571
648
  lastRunText: lastRunLabel(row),
572
649
  stateKey: st.key,
@@ -590,6 +667,7 @@ watch(
590
667
 
591
668
  function openFormDialog() {
592
669
  formMode.value = "add";
670
+ formStartStep.value = 1;
593
671
  selectedTask.value = {};
594
672
  showForm.value = true;
595
673
  }
@@ -597,6 +675,15 @@ function openFormDialog() {
597
675
  function onEditRow(item: Record<string, any>) {
598
676
  selectedTask.value = item;
599
677
  formMode.value = "edit";
678
+ formStartStep.value = 1;
679
+ showForm.value = true;
680
+ }
681
+
682
+ // A finished extra visit: open its "When" step so a new end date or repeat is one change away.
683
+ function onExtendRow(item: Record<string, any>) {
684
+ selectedTask.value = item;
685
+ formMode.value = "edit";
686
+ formStartStep.value = 3;
600
687
  showForm.value = true;
601
688
  }
602
689
 
@@ -612,8 +699,8 @@ async function togglePause(item: Record<string, any>) {
612
699
  await setScheduleTaskPaused(item._id, !resuming);
613
700
  showMessage(
614
701
  resuming
615
- ? "Resumed. It will run again from its next date."
616
- : "Paused. It will not run until you resume it.",
702
+ ? "Resumed. Extra visits are added again from its next date."
703
+ : "Paused. No extra visits are added until you resume it.",
617
704
  "success",
618
705
  );
619
706
  await getTaskRefresh();
@@ -649,6 +736,7 @@ async function onRowClick(data: any) {
649
736
  const onEditFromMoreAction = async () => {
650
737
  dialogShowMoreActions.value = false;
651
738
  formMode.value = "edit";
739
+ formStartStep.value = stateOf(selectedTask.value).key === "ended" ? 3 : 1;
652
740
  showForm.value = true;
653
741
  };
654
742
 
@@ -693,8 +781,27 @@ async function _deleteScheduleTask() {
693
781
  color: var(--text2);
694
782
  }
695
783
  .st-when {
784
+ display: inline-block;
785
+ min-width: 220px;
696
786
  white-space: normal;
697
787
  }
788
+ .st-cell {
789
+ display: flex;
790
+ flex-direction: column;
791
+ align-items: flex-start;
792
+ gap: 4px;
793
+ }
794
+ .st-note {
795
+ font-size: var(--fs-chip-lg);
796
+ color: var(--text2);
797
+ white-space: normal;
798
+ }
799
+ .st-link {
800
+ font-size: var(--fs-chip-lg);
801
+ font-weight: 600;
802
+ color: rgb(var(--v-theme-primary));
803
+ text-decoration: underline;
804
+ }
698
805
  .st-actions {
699
806
  display: flex;
700
807
  justify-content: flex-end;
@@ -104,7 +104,7 @@ const toneClass = computed(() =>
104
104
  font-size: var(--fs-chip);
105
105
  }
106
106
 
107
- /* The role tag: `padding:3px 10px;border-radius:999px;font-size:11.5px`. */
107
+ /* The role tag: `padding:3px 10px;border-radius:999px;font-size:12.5px`. */
108
108
  .status-chip--pill {
109
109
  padding: 3px 10px;
110
110
  font-size: var(--fs-chip);
@@ -397,12 +397,12 @@ onMounted(() => {
397
397
 
398
398
  :deep(.v-data-table thead tr th) {
399
399
  padding: 6px !important;
400
- font-size: 11px !important;
400
+ font-size: 12px !important;
401
401
  }
402
402
 
403
403
  :deep(.v-data-table tbody tr td) {
404
404
  padding: 4px !important;
405
- font-size: 10px !important;
405
+ font-size: 12px !important;
406
406
  }
407
407
 
408
408
  :deep(.v-img) {
@@ -13,6 +13,7 @@ export default function useCleaningPermission() {
13
13
  bulletinBoardPermissions,
14
14
 
15
15
  inventoryPermissions,
16
+ taskCalendarPermissions,
16
17
  robotPermissions,
17
18
  } = useCommonPermissions();
18
19
  const permissions: TPermissions = {
@@ -298,6 +299,8 @@ export default function useCleaningPermission() {
298
299
  },
299
300
  },
300
301
  "robot-mgmt": robotPermissions,
302
+ // Task Calendar -> See calendar (trade catalogue v2, 2026-10-21).
303
+ "event-mgmt": taskCalendarPermissions,
301
304
  };
302
305
 
303
306
  return {
@@ -391,7 +391,19 @@ export function useCommonPermissions() {
391
391
  },
392
392
  };
393
393
 
394
+ // Trade catalogue v2 (owner 2026-10-21): Task Calendar per role. Grants
395
+ // nothing alone; a role without `catalogue:trade-v2` keeps the calendar
396
+ // (utils/trade-catalogue.ts).
397
+ const taskCalendarPermissions: Record<string, TPermission> = {
398
+ "see-task-calendar": {
399
+ check: true,
400
+ label: "See calendar",
401
+ description: "Allows the user to open the Task Calendar.",
402
+ },
403
+ };
404
+
394
405
  return {
406
+ taskCalendarPermissions,
395
407
  robotPermissions,
396
408
  inventoryPermissions,
397
409
  invitationPermissions,
@@ -31,9 +31,11 @@ export default function useLandscapingPermission() {
31
31
  serviceProviderPermissions,
32
32
 
33
33
  inventoryPermissions,
34
+ taskCalendarPermissions,
34
35
  } = useCommonPermissions();
35
36
  const permissions: TPermissions = {
36
37
  inventory: inventoryPermissions,
38
+ "event-mgmt": taskCalendarPermissions,
37
39
  members: memberPermissions,
38
40
  roles: rolePermissions,
39
41
  invitations: invitationPermissions,
@@ -31,6 +31,7 @@ export default function useMechanicalElectricalPermission() {
31
31
  bulletinBoardPermissions,
32
32
 
33
33
  inventoryPermissions,
34
+ taskCalendarPermissions,
34
35
  } = useCommonPermissions();
35
36
  const { permissions: schedulePermissions } = useCleaningPermission();
36
37
 
@@ -61,6 +62,8 @@ export default function useMechanicalElectricalPermission() {
61
62
  // same seven actions Cleaning offers (decision 3A) -- taken from it, like
62
63
  // the keys above, so the two cannot drift. Catalogue-only: grants nothing.
63
64
  "emergency-contact": schedulePermissions["emergency-contact"],
65
+ // Task Calendar -> See calendar (trade catalogue v2, 2026-10-21).
66
+ "event-mgmt": taskCalendarPermissions,
64
67
 
65
68
  };
66
69
 
@@ -31,9 +31,11 @@ export default function usePestControlPermission() {
31
31
  serviceProviderPermissions,
32
32
 
33
33
  inventoryPermissions,
34
+ taskCalendarPermissions,
34
35
  } = useCommonPermissions();
35
36
  const permissions: TPermissions = {
36
37
  inventory: inventoryPermissions,
38
+ "event-mgmt": taskCalendarPermissions,
37
39
  members: memberPermissions,
38
40
  roles: rolePermissions,
39
41
  invitations: invitationPermissions,
@@ -31,9 +31,11 @@ export default function usePoolMaintenancePermission() {
31
31
  serviceProviderPermissions,
32
32
 
33
33
  inventoryPermissions,
34
+ taskCalendarPermissions,
34
35
  } = useCommonPermissions();
35
36
  const permissions: TPermissions = {
36
37
  inventory: inventoryPermissions,
38
+ "event-mgmt": taskCalendarPermissions,
37
39
  members: memberPermissions,
38
40
  roles: rolePermissions,
39
41
  invitations: invitationPermissions,
@@ -304,6 +304,8 @@ export const PERMISSIONS = {
304
304
  "delete-emergency-contact-category",
305
305
  ],
306
306
  "event-mgmt": [
307
+ // Trade catalogue v2 (2026-10-21): Task Calendar -> See calendar.
308
+ "see-task-calendar",
307
309
  "see-all-event",
308
310
  "add-event",
309
311
  "update-event",
@@ -394,7 +396,7 @@ export const PERMISSIONS = {
394
396
  // Security catalogue v2 (2026-10-20). `catalogue:security-v2` is the marker the
395
397
  // role editor stamps on save; it grants nothing (see utils/security-catalogue.ts).
396
398
  "offline-data": ["see-offline-data"],
397
- catalogue: ["security-v2"],
399
+ catalogue: ["security-v2", "trade-v2"],
398
400
  "visitor-mgmt": [
399
401
  "invite-visitor",
400
402
  "see-all-visitor",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.135.0",
5
+ "version": "4.137.0",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
@@ -25,6 +25,13 @@ const ACRONYMS: Record<string, string> = {
25
25
  const RENAMED: [string, string][] = [
26
26
  ["Building Mgmt", "Occupancy Mgmt"],
27
27
  ["Event Mgmt", "Task Calendar"],
28
+ // Schedule screens (owner 21 Oct): the same names the menu and the mobile apps use.
29
+ ["Schedule Task", "Extra Visits"],
30
+ ...["Cleaning", "Technician", "Pest", "Pool", "Landscape"].flatMap((t): [string, string][] => [
31
+ [`${t} Schedule Area`, "Area"],
32
+ [`${t} Schedule Unit`, "Unit"],
33
+ [`${t} Schedule`, "Daily Checklist"],
34
+ ]),
28
35
  ];
29
36
 
30
37
  export function moduleNameFromRoute(route: {
@@ -50,6 +50,8 @@ export const NAV_SECTIONS: Record<string, string> = {
50
50
  Area: "SERVICE DELIVERY",
51
51
  Unit: "SERVICE DELIVERY",
52
52
  "Schedule Task": "SERVICE DELIVERY",
53
+ "Daily Checklist": "SERVICE DELIVERY",
54
+ "Extra Visits": "SERVICE DELIVERY",
53
55
 
54
56
  // EQUIPMENT
55
57
  "Equipment Management": "EQUIPMENT",
@@ -259,7 +259,10 @@ const GRANT_COVERS: Readonly<Record<string, readonly string[]>> = {
259
259
  "visitor:create": ["visitor-mgmt:invite-visitor"],
260
260
  };
261
261
 
262
+ // Trade v2 See calendar: open to every trade role before the box, so no owner
263
+ // grant names it (core `boundToOwnerGrant` never refuses it either).
262
264
  const grantHolds = (grant: readonly string[], key: string) =>
265
+ key === "event-mgmt:see-task-calendar" ||
263
266
  grant.includes(key) || grant.some((g) => GRANT_COVERS[g]?.includes(key));
264
267
 
265
268
  /**
@@ -68,6 +68,12 @@ export const RESOURCE_LABELS: Record<string, string> = {
68
68
  export const RESOURCE_LABELS_BY_TYPE: Record<string, Record<string, string>> = {
69
69
  property_management_agency: { "virtual-patrol": "CCTV" },
70
70
  real_estate_developer: { "virtual-patrol": "CCTV" },
71
+ // Trade catalogue v2: the trade apps call `event-mgmt` the Task Calendar.
72
+ cleaning_services: { "event-mgmt": "Task Calendar" },
73
+ mechanical_electrical_services: { "event-mgmt": "Task Calendar" },
74
+ pest_control_services: { "event-mgmt": "Task Calendar" },
75
+ pool_maintenance_services: { "event-mgmt": "Task Calendar" },
76
+ landscaping_services: { "event-mgmt": "Task Calendar" },
71
77
  };
72
78
 
73
79
  /**