@7365admin1/layer-common 4.132.0 → 4.134.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.
- package/CHANGELOG.md +17 -4
- package/components/AreaChecklistHistoryLogs.vue +5 -4
- package/components/CleaningScheduleMain.vue +109 -15
- package/components/FeedbackMain.vue +17 -1
- package/components/ManageChecklistMain.vue +16 -12
- package/components/RolePermissionFormCreate.vue +5 -1
- package/components/RolePermissionFormPreviewUpdate.vue +34 -4
- package/components/RolePermissionMain.vue +3 -1
- package/components/ScheduleAreaMain.vue +56 -71
- package/components/ScheduleGuide.vue +132 -0
- package/components/ScheduleTaskForm.vue +520 -353
- package/components/ScheduleTaskMain.vue +211 -70
- package/components/TableHygiene.vue +2 -2
- package/components/VehicleAddSelection.vue +5 -1
- package/components/VehicleManagement.vue +10 -4
- package/composables/useCleaningSchedules.ts +9 -0
- package/composables/useFeedback.ts +4 -0
- package/composables/useScheduleTask.ts +32 -2
- package/composables/useSecurityPermission.ts +19 -0
- package/constants/permissions.ts +7 -0
- package/package.json +1 -1
- package/types/cleaner-schedule.d.ts +1 -1
- package/utils/org-modules.ts +17 -1
- package/utils/schedule-task.ts +281 -0
- package/utils/security-catalogue.ts +59 -0
- package/utils/status.ts +6 -0
- package/utils/vehicle-actions.ts +10 -2
- package/composables/useScheduleTaskArea.ts +0 -85
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12">
|
|
4
|
+
<ScheduleGuide
|
|
5
|
+
:org-id="orgId"
|
|
6
|
+
:site="site"
|
|
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
|
+
/>
|
|
4
10
|
<TableMain
|
|
5
|
-
:title="'
|
|
11
|
+
:title="'Extra Visits (Scheduled)'"
|
|
6
12
|
:items="items"
|
|
7
13
|
:headers="headers"
|
|
8
14
|
:loading="loading"
|
|
@@ -10,13 +16,23 @@
|
|
|
10
16
|
v-model:page="page"
|
|
11
17
|
:pages="pages"
|
|
12
18
|
:pageRange="pageRange"
|
|
13
|
-
:no-data-text="'No schedule tasks found'"
|
|
14
19
|
@refresh="getTaskRefresh"
|
|
15
20
|
:canCreate="canCreateScheduleTask"
|
|
16
|
-
createLabel="
|
|
21
|
+
createLabel="Add Extra Visit"
|
|
17
22
|
@create="openFormDialog"
|
|
18
23
|
@row-click="onRowClick"
|
|
19
24
|
>
|
|
25
|
+
<template #no-data>
|
|
26
|
+
<div class="st-empty">
|
|
27
|
+
<strong>No extra visits yet.</strong>
|
|
28
|
+
<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.
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
20
36
|
<template #actions>
|
|
21
37
|
<v-row no-gutters align="center" class="w-100">
|
|
22
38
|
<v-col cols="auto" v-if="canCreateScheduleTask">
|
|
@@ -25,7 +41,7 @@
|
|
|
25
41
|
variant="flat"
|
|
26
42
|
@click="openFormDialog"
|
|
27
43
|
>
|
|
28
|
-
|
|
44
|
+
Add Extra Visit
|
|
29
45
|
</v-btn>
|
|
30
46
|
</v-col>
|
|
31
47
|
|
|
@@ -66,13 +82,73 @@
|
|
|
66
82
|
<span v-else>N/A</span>
|
|
67
83
|
</template>
|
|
68
84
|
|
|
69
|
-
<template #item.
|
|
85
|
+
<template #item.when="{ item }">
|
|
86
|
+
<span class="st-when">{{ item.whenText }}</span>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<template #item.nextRun="{ item }">
|
|
90
|
+
{{ item.nextRunText || "-" }}
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<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>
|
|
105
|
+
</template>
|
|
106
|
+
|
|
107
|
+
<template #item.status="{ item }">
|
|
70
108
|
<StatusChip
|
|
71
|
-
|
|
72
|
-
:
|
|
73
|
-
:label="value || 'N/A'"
|
|
109
|
+
:status="item.stateLabel"
|
|
110
|
+
:label="item.stateLabel"
|
|
74
111
|
/>
|
|
75
112
|
</template>
|
|
113
|
+
|
|
114
|
+
<template #item.actions="{ item }">
|
|
115
|
+
<div class="st-actions">
|
|
116
|
+
<v-btn
|
|
117
|
+
v-if="canUpdateScheduleTask"
|
|
118
|
+
size="small"
|
|
119
|
+
variant="text"
|
|
120
|
+
class="text-none"
|
|
121
|
+
prepend-icon="mdi-pencil-outline"
|
|
122
|
+
@click.stop="onEditRow(item)"
|
|
123
|
+
>
|
|
124
|
+
Edit
|
|
125
|
+
</v-btn>
|
|
126
|
+
<v-btn
|
|
127
|
+
v-if="canUpdateScheduleTask && item.stateKey !== 'ended'"
|
|
128
|
+
size="small"
|
|
129
|
+
variant="text"
|
|
130
|
+
class="text-none"
|
|
131
|
+
:prepend-icon="
|
|
132
|
+
item.stateKey === 'paused' ? 'mdi-play-outline' : 'mdi-pause'
|
|
133
|
+
"
|
|
134
|
+
:loading="pausingId === item._id"
|
|
135
|
+
@click.stop="togglePause(item)"
|
|
136
|
+
>
|
|
137
|
+
{{ item.stateKey === "paused" ? "Resume" : "Pause" }}
|
|
138
|
+
</v-btn>
|
|
139
|
+
<v-btn
|
|
140
|
+
v-if="canDeleteScheduleTask"
|
|
141
|
+
size="small"
|
|
142
|
+
variant="text"
|
|
143
|
+
color="error"
|
|
144
|
+
class="text-none"
|
|
145
|
+
prepend-icon="mdi-delete-outline"
|
|
146
|
+
@click.stop="onDeleteRow(item)"
|
|
147
|
+
>
|
|
148
|
+
Delete
|
|
149
|
+
</v-btn>
|
|
150
|
+
</div>
|
|
151
|
+
</template>
|
|
76
152
|
</TableMain>
|
|
77
153
|
|
|
78
154
|
<ScheduleTaskForm
|
|
@@ -86,14 +162,14 @@
|
|
|
86
162
|
|
|
87
163
|
<v-dialog v-model="dialogShowMoreActions" width="400" persistent>
|
|
88
164
|
<HygieneUpdateMoreAction
|
|
89
|
-
:title="selectedTask?.title || '
|
|
165
|
+
:title="selectedTask?.title || 'Extra Visit'"
|
|
90
166
|
:canUpdate="canUpdateScheduleTask"
|
|
91
167
|
@close="dialogShowMoreActions = false"
|
|
92
168
|
:canDelete="canDeleteScheduleTask"
|
|
93
|
-
:deleteButtonLabel="'Delete
|
|
169
|
+
:deleteButtonLabel="'Delete Extra Visit'"
|
|
94
170
|
@edit="onEditFromMoreAction"
|
|
95
171
|
@delete="onDeleteFromMoreAction"
|
|
96
|
-
:editButtonLabel="'Edit
|
|
172
|
+
:editButtonLabel="'Edit Extra Visit'"
|
|
97
173
|
>
|
|
98
174
|
<template #content>
|
|
99
175
|
<v-row no-gutters>
|
|
@@ -109,32 +185,28 @@
|
|
|
109
185
|
|
|
110
186
|
<v-row no-gutters class="mb-2">
|
|
111
187
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
112
|
-
|
|
188
|
+
When:
|
|
113
189
|
</v-col>
|
|
114
190
|
<v-col cols="7" class="text-subtitle-2">
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{{ formatDateChip(date) }}
|
|
126
|
-
</v-chip>
|
|
127
|
-
</div>
|
|
128
|
-
<div v-else>N/A</div>
|
|
191
|
+
{{ selectedWhen }}
|
|
192
|
+
</v-col>
|
|
193
|
+
</v-row>
|
|
194
|
+
|
|
195
|
+
<v-row no-gutters class="mb-2">
|
|
196
|
+
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
197
|
+
Next run:
|
|
198
|
+
</v-col>
|
|
199
|
+
<v-col cols="7" class="text-subtitle-2">
|
|
200
|
+
{{ selectedNext || "-" }}
|
|
129
201
|
</v-col>
|
|
130
202
|
</v-row>
|
|
131
203
|
|
|
132
|
-
<v-row
|
|
204
|
+
<v-row no-gutters class="mb-2">
|
|
133
205
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
134
|
-
|
|
206
|
+
Last run:
|
|
135
207
|
</v-col>
|
|
136
208
|
<v-col cols="7" class="text-subtitle-2">
|
|
137
|
-
{{ selectedTask
|
|
209
|
+
{{ lastRunLabel(selectedTask) }}
|
|
138
210
|
</v-col>
|
|
139
211
|
</v-row>
|
|
140
212
|
|
|
@@ -192,8 +264,8 @@
|
|
|
192
264
|
v-if="
|
|
193
265
|
loadingViewUnits &&
|
|
194
266
|
!(
|
|
195
|
-
area.value ||
|
|
196
|
-
|
|
267
|
+
(area.value || area._id) in
|
|
268
|
+
fetchedViewAreaUnits
|
|
197
269
|
)
|
|
198
270
|
"
|
|
199
271
|
size="14"
|
|
@@ -246,7 +318,8 @@
|
|
|
246
318
|
</v-chip>
|
|
247
319
|
</div>
|
|
248
320
|
<span v-else class="text-caption text-disabled"
|
|
249
|
-
>No units
|
|
321
|
+
>No units in this area, so it is skipped when
|
|
322
|
+
this visit runs.</span
|
|
250
323
|
>
|
|
251
324
|
</v-card-text>
|
|
252
325
|
</div>
|
|
@@ -266,15 +339,14 @@
|
|
|
266
339
|
</v-col>
|
|
267
340
|
</v-row>
|
|
268
341
|
|
|
269
|
-
<v-row
|
|
342
|
+
<v-row no-gutters class="mb-2">
|
|
270
343
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
271
344
|
Status:
|
|
272
345
|
</v-col>
|
|
273
346
|
<v-col cols="7" class="text-subtitle-2">
|
|
274
347
|
<StatusChip
|
|
275
|
-
|
|
276
|
-
:
|
|
277
|
-
:label="selectedTask.status || 'N/A'"
|
|
348
|
+
:status="stateOf(selectedTask).label"
|
|
349
|
+
:label="stateOf(selectedTask).label"
|
|
278
350
|
/>
|
|
279
351
|
</v-col>
|
|
280
352
|
</v-row>
|
|
@@ -307,11 +379,12 @@
|
|
|
307
379
|
<v-dialog v-model="dialogDeleteTask" max-width="520">
|
|
308
380
|
<v-card class="screen-modal">
|
|
309
381
|
<v-card-title class="text-capitalize">
|
|
310
|
-
Delete {{ selectedTask?.title || "
|
|
382
|
+
Delete {{ selectedTask?.title || "Extra Visit" }}
|
|
311
383
|
</v-card-title>
|
|
312
384
|
|
|
313
385
|
<v-card-text class="screen-modal__body">
|
|
314
|
-
|
|
386
|
+
This stops all future runs of this extra visit. Visits that already
|
|
387
|
+
happened are kept. Delete it?
|
|
315
388
|
</v-card-text>
|
|
316
389
|
|
|
317
390
|
<div class="screen-modal__footer">
|
|
@@ -348,6 +421,13 @@
|
|
|
348
421
|
import useScheduleTask from "../composables/useScheduleTask";
|
|
349
422
|
import useUtils from "../composables/useUtils";
|
|
350
423
|
import useAreas from "../composables/useAreas";
|
|
424
|
+
import {
|
|
425
|
+
STATUS_LABELS,
|
|
426
|
+
describeWhen,
|
|
427
|
+
fmtMoment,
|
|
428
|
+
lastRunLabel,
|
|
429
|
+
scheduleNameFor,
|
|
430
|
+
} from "../utils/schedule-task";
|
|
351
431
|
|
|
352
432
|
const props = defineProps({
|
|
353
433
|
orgId: { type: String, required: true },
|
|
@@ -362,8 +442,12 @@ const props = defineProps({
|
|
|
362
442
|
});
|
|
363
443
|
|
|
364
444
|
const { formatDate, debounce } = useUtils();
|
|
365
|
-
const {
|
|
366
|
-
|
|
445
|
+
const {
|
|
446
|
+
getScheduleTasks,
|
|
447
|
+
getScheduleTaskById,
|
|
448
|
+
deleteScheduleTask,
|
|
449
|
+
setScheduleTaskPaused,
|
|
450
|
+
} = useScheduleTask();
|
|
367
451
|
const { getAreaById } = useAreas();
|
|
368
452
|
|
|
369
453
|
const canViewScheduleTasks = computed(() => props.canViewScheduleTasks);
|
|
@@ -374,6 +458,8 @@ const canViewScheduleTaskDetails = computed(
|
|
|
374
458
|
() => props.canViewScheduleTaskDetails
|
|
375
459
|
);
|
|
376
460
|
|
|
461
|
+
const scheduleName = scheduleNameFor(props.serviceType).title;
|
|
462
|
+
|
|
377
463
|
const page = ref(1);
|
|
378
464
|
const pages = ref(0);
|
|
379
465
|
const pageRange = ref("-- - -- of --");
|
|
@@ -433,41 +519,28 @@ const items = ref<any[]>([]);
|
|
|
433
519
|
const headers = [
|
|
434
520
|
{ title: "Title", value: "title" },
|
|
435
521
|
{ title: "Areas", value: "areas" },
|
|
436
|
-
{ title: "
|
|
522
|
+
{ title: "When", value: "when", sortable: false },
|
|
523
|
+
{ title: "Next run", value: "nextRun", sortable: false },
|
|
524
|
+
{ title: "Last run", value: "lastRun", sortable: false },
|
|
525
|
+
{ title: "Status", value: "status", sortable: false },
|
|
526
|
+
{ title: "", value: "actions", sortable: false, align: "end" as const },
|
|
437
527
|
];
|
|
438
528
|
|
|
529
|
+
// Active / Paused / Ended. An older server without `scheduleStatus` shows Active.
|
|
530
|
+
function stateOf(row: Record<string, any>) {
|
|
531
|
+
const key = row?.scheduleStatus || (row?.paused ? "paused" : "active");
|
|
532
|
+
return { key: key as string, label: STATUS_LABELS[key] || "Active" };
|
|
533
|
+
}
|
|
534
|
+
const selectedWhen = computed(() => describeWhen(selectedTask.value as any));
|
|
535
|
+
const selectedNext = computed(() => fmtMoment(selectedTask.value?.nextRun));
|
|
536
|
+
const pausingId = ref("");
|
|
537
|
+
|
|
439
538
|
const showMessage = (text: string, color: string = "success") => {
|
|
440
539
|
message.value = text;
|
|
441
540
|
messageColor.value = color;
|
|
442
541
|
messageSnackbar.value = true;
|
|
443
542
|
};
|
|
444
543
|
|
|
445
|
-
const formatDateChip = (dateStr: string) => {
|
|
446
|
-
const parts = dateStr?.split("-");
|
|
447
|
-
if (parts?.length === 3) return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
|
448
|
-
return dateStr;
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const mapShortToDay = (short: string) => {
|
|
452
|
-
const mapping: Record<string, string> = {
|
|
453
|
-
Mon: "Monday",
|
|
454
|
-
Tue: "Tuesday",
|
|
455
|
-
Wed: "Wednesday",
|
|
456
|
-
Thu: "Thursday",
|
|
457
|
-
Fri: "Friday",
|
|
458
|
-
Sat: "Saturday",
|
|
459
|
-
Sun: "Sunday",
|
|
460
|
-
};
|
|
461
|
-
return mapping[short] || short;
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
const formatTaskDays = (dayField: any) => {
|
|
465
|
-
if (!dayField) return "N/A";
|
|
466
|
-
if (Array.isArray(dayField))
|
|
467
|
-
return dayField.map((d) => mapShortToDay(d)).join(", ");
|
|
468
|
-
return String(dayField);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
544
|
const {
|
|
472
545
|
data: getTaskReq,
|
|
473
546
|
refresh: getTaskRefresh,
|
|
@@ -488,7 +561,18 @@ const {
|
|
|
488
561
|
|
|
489
562
|
watchEffect(() => {
|
|
490
563
|
if (getTaskReq.value) {
|
|
491
|
-
|
|
564
|
+
const rows: any[] = getTaskReq.value.items || getTaskReq.value || [];
|
|
565
|
+
items.value = rows.map((row) => {
|
|
566
|
+
const st = stateOf(row);
|
|
567
|
+
return {
|
|
568
|
+
...row,
|
|
569
|
+
whenText: describeWhen(row),
|
|
570
|
+
nextRunText: fmtMoment(row.nextRun),
|
|
571
|
+
lastRunText: lastRunLabel(row),
|
|
572
|
+
stateKey: st.key,
|
|
573
|
+
stateLabel: st.label,
|
|
574
|
+
};
|
|
575
|
+
});
|
|
492
576
|
pages.value = getTaskReq.value.pages || 0;
|
|
493
577
|
pageRange.value = getTaskReq.value.pageRange || "-- - -- of --";
|
|
494
578
|
}
|
|
@@ -510,7 +594,41 @@ function openFormDialog() {
|
|
|
510
594
|
showForm.value = true;
|
|
511
595
|
}
|
|
512
596
|
|
|
597
|
+
function onEditRow(item: Record<string, any>) {
|
|
598
|
+
selectedTask.value = item;
|
|
599
|
+
formMode.value = "edit";
|
|
600
|
+
showForm.value = true;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function onDeleteRow(item: Record<string, any>) {
|
|
604
|
+
selectedTask.value = item;
|
|
605
|
+
dialogDeleteTask.value = true;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function togglePause(item: Record<string, any>) {
|
|
609
|
+
const resuming = item.stateKey === "paused";
|
|
610
|
+
try {
|
|
611
|
+
pausingId.value = item._id;
|
|
612
|
+
await setScheduleTaskPaused(item._id, !resuming);
|
|
613
|
+
showMessage(
|
|
614
|
+
resuming
|
|
615
|
+
? "Resumed. It will run again from its next date."
|
|
616
|
+
: "Paused. It will not run until you resume it.",
|
|
617
|
+
"success",
|
|
618
|
+
);
|
|
619
|
+
await getTaskRefresh();
|
|
620
|
+
} catch (error: any) {
|
|
621
|
+
showMessage(
|
|
622
|
+
error?.data?.message || "Could not change this extra visit.",
|
|
623
|
+
"error",
|
|
624
|
+
);
|
|
625
|
+
} finally {
|
|
626
|
+
pausingId.value = "";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
513
630
|
async function onRowClick(data: any) {
|
|
631
|
+
if (!canViewScheduleTaskDetails.value) return;
|
|
514
632
|
const taskId = data?.item?._id || data?._id || data?.item?.id || data?.id;
|
|
515
633
|
|
|
516
634
|
if (taskId) {
|
|
@@ -550,13 +668,36 @@ async function _deleteScheduleTask() {
|
|
|
550
668
|
|
|
551
669
|
const response = await deleteScheduleTask(id);
|
|
552
670
|
dialogDeleteTask.value = false;
|
|
553
|
-
showMessage("
|
|
671
|
+
showMessage("Extra visit deleted. Past visits are kept.", "success");
|
|
554
672
|
|
|
555
673
|
await getTaskRefresh();
|
|
556
674
|
} catch (error: any) {
|
|
557
|
-
showMessage(
|
|
675
|
+
showMessage(
|
|
676
|
+
error?.data?.message || "Could not delete this extra visit.",
|
|
677
|
+
"error",
|
|
678
|
+
);
|
|
558
679
|
} finally {
|
|
559
680
|
submitting.value = false;
|
|
560
681
|
}
|
|
561
682
|
}
|
|
562
683
|
</script>
|
|
684
|
+
|
|
685
|
+
<style scoped>
|
|
686
|
+
.st-empty {
|
|
687
|
+
display: flex;
|
|
688
|
+
flex-direction: column;
|
|
689
|
+
gap: 4px;
|
|
690
|
+
padding: 24px 16px;
|
|
691
|
+
font-family: var(--font-sans);
|
|
692
|
+
font-size: var(--fs-cell);
|
|
693
|
+
color: var(--text2);
|
|
694
|
+
}
|
|
695
|
+
.st-when {
|
|
696
|
+
white-space: normal;
|
|
697
|
+
}
|
|
698
|
+
.st-actions {
|
|
699
|
+
display: flex;
|
|
700
|
+
justify-content: flex-end;
|
|
701
|
+
flex-wrap: wrap;
|
|
702
|
+
}
|
|
703
|
+
</style>
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
class="text-none"
|
|
93
93
|
>
|
|
94
94
|
<v-icon size="12">mdi-calendar-clock</v-icon>
|
|
95
|
-
|
|
95
|
+
Extra visit
|
|
96
96
|
</StatusChip>
|
|
97
97
|
<slot name="group-header-chips" :group="group">
|
|
98
98
|
<StatusChip
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
class="text-none"
|
|
115
115
|
>
|
|
116
116
|
<v-icon size="12">mdi-progress-clock</v-icon>
|
|
117
|
-
|
|
117
|
+
In progress · {{ group.completedByName }}
|
|
118
118
|
</StatusChip>
|
|
119
119
|
</slot>
|
|
120
120
|
</v-col>
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
type TVehicleAddChoice = TVehicleType | "seasonpass" | "whitelist-bulk";
|
|
28
28
|
|
|
29
29
|
const prop = defineProps({
|
|
30
|
+
// Security catalogue v2: false hides the Blocklist choice. Default = today.
|
|
31
|
+
canManageBlocklist: { type: Boolean, default: true },
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
const emit = defineEmits(['cancel', 'select']);
|
|
@@ -37,7 +39,9 @@ const selection = computed<{label: string, value: TVehicleAddChoice}[]>(() => {
|
|
|
37
39
|
// Many whitelist vehicles at once, typed or pasted into a sheet.
|
|
38
40
|
{ label: "Whitelist (Bulk)", value: "whitelist-bulk" },
|
|
39
41
|
{ label: "Season Pass", value: "seasonpass" },
|
|
40
|
-
|
|
42
|
+
...(prop.canManageBlocklist
|
|
43
|
+
? [{ label: "Blocklist", value: "blocklist" as TVehicleAddChoice }]
|
|
44
|
+
: []),
|
|
41
45
|
];
|
|
42
46
|
})
|
|
43
47
|
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
<!-- <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" /> -->
|
|
138
138
|
|
|
139
139
|
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
140
|
-
<VehicleAddSelection @cancel="dialog.showSelection = false" @select="handleSelectVehicleStatus" />
|
|
140
|
+
<VehicleAddSelection :can-manage-blocklist="canManageBlocklist" @cancel="dialog.showSelection = false" @select="handleSelectVehicleStatus" />
|
|
141
141
|
</v-dialog>
|
|
142
142
|
|
|
143
143
|
<!-- Many whitelist vehicles at once. Wider than the one-vehicle form: it
|
|
@@ -495,6 +495,9 @@ const props = defineProps({
|
|
|
495
495
|
canDeleteVehicle: { type: Boolean, default: false },
|
|
496
496
|
canViewVehicleDetails: { type: Boolean, default: false },
|
|
497
497
|
canApproveVehicle: { type: Boolean, default: false },
|
|
498
|
+
// Security catalogue v2. Defaults keep today's behaviour for every other app.
|
|
499
|
+
canSeeBlocklist: { type: Boolean, default: true },
|
|
500
|
+
canManageBlocklist: { type: Boolean, default: undefined },
|
|
498
501
|
site: { type: String, required: true },
|
|
499
502
|
org: { type: String, required: true },
|
|
500
503
|
app: { type: String, required: true },
|
|
@@ -578,11 +581,13 @@ const historyHeaders = [
|
|
|
578
581
|
* resident / season pass that was blocked
|
|
579
582
|
*/
|
|
580
583
|
type TVehicleTab = "residents" | "season-passes" | "block-listed";
|
|
581
|
-
const vehicleTabs
|
|
584
|
+
const vehicleTabs = computed<{ name: string; value: TVehicleTab }[]>(() => [
|
|
582
585
|
{ name: "Residents", value: "residents" },
|
|
583
586
|
{ name: "Season Passes", value: "season-passes" },
|
|
584
|
-
|
|
585
|
-
]
|
|
587
|
+
...(props.canSeeBlocklist
|
|
588
|
+
? [{ name: "Block Listed", value: "block-listed" as TVehicleTab }]
|
|
589
|
+
: []),
|
|
590
|
+
])
|
|
586
591
|
const activeTab = ref<TVehicleTab>("residents");
|
|
587
592
|
|
|
588
593
|
|
|
@@ -1134,6 +1139,7 @@ const vehiclePermissions = computed(() => ({
|
|
|
1134
1139
|
canUpdateVehicle: props.canUpdateVehicle && !revoked.update,
|
|
1135
1140
|
canDeleteVehicle: props.canDeleteVehicle && !revoked.delete,
|
|
1136
1141
|
canApproveVehicle: props.canApproveVehicle && !revoked.approve,
|
|
1142
|
+
canManageBlocklist: props.canManageBlocklist,
|
|
1137
1143
|
}));
|
|
1138
1144
|
|
|
1139
1145
|
/**
|
|
@@ -140,6 +140,14 @@ export default function useCleaningSchedules() {
|
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/** Today's day-row, created if missing (safe to repeat). Follow with generateChecklist(site, id). */
|
|
144
|
+
function createTodaysParent(site: string, serviceType: string) {
|
|
145
|
+
return useNuxtApp().$api<{ id: string }>(`/api/hygiene-parent-checklist`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: { site, serviceType },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
async function getAreaChecklistHistory({
|
|
144
152
|
parentChecklistId = "",
|
|
145
153
|
page = 1,
|
|
@@ -231,6 +239,7 @@ export default function useCleaningSchedules() {
|
|
|
231
239
|
updateAreaChecklistStatus,
|
|
232
240
|
uploadAreaChecklistAttachment,
|
|
233
241
|
generateChecklist,
|
|
242
|
+
createTodaysParent,
|
|
234
243
|
getAreaChecklistHistory,
|
|
235
244
|
getAreaChecklistHistoryById,
|
|
236
245
|
exportAreas,
|
|
@@ -73,6 +73,7 @@ export default function useFeedback() {
|
|
|
73
73
|
provider = "",
|
|
74
74
|
service = "",
|
|
75
75
|
userId = "",
|
|
76
|
+
category = "",
|
|
76
77
|
}: {
|
|
77
78
|
page?: number;
|
|
78
79
|
limit?: number;
|
|
@@ -85,6 +86,8 @@ export default function useFeedback() {
|
|
|
85
86
|
provider?: string | any;
|
|
86
87
|
service?: string;
|
|
87
88
|
userId?: string;
|
|
89
|
+
/** Optional: one category (the feedback subject) or a comma list. Empty = all. */
|
|
90
|
+
category?: string;
|
|
88
91
|
} = {}) {
|
|
89
92
|
try {
|
|
90
93
|
return useNuxtApp().$api<Record<string, any>>("/api/feedbacks2", {
|
|
@@ -100,6 +103,7 @@ export default function useFeedback() {
|
|
|
100
103
|
status,
|
|
101
104
|
provider,
|
|
102
105
|
service,
|
|
106
|
+
...(category && { category }),
|
|
103
107
|
...(currentUser.value?.type != "site" && {
|
|
104
108
|
userId,
|
|
105
109
|
}),
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// Repeat fields (all optional: a payload without them behaves as a one-off on `dates`).
|
|
2
|
+
// "" / null clears an optional field on the server.
|
|
3
|
+
type TScheduleRepeatFields = {
|
|
4
|
+
repeat?: "none" | "daily" | "weekdays" | "weekly" | "monthly";
|
|
5
|
+
daysOfWeek?: number[] | null;
|
|
6
|
+
startDate?: string | null;
|
|
7
|
+
endDate?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const repeatBody = (p: TScheduleRepeatFields) => ({
|
|
11
|
+
repeat: p.repeat,
|
|
12
|
+
daysOfWeek: p.daysOfWeek,
|
|
13
|
+
startDate: p.startDate,
|
|
14
|
+
endDate: p.endDate,
|
|
15
|
+
});
|
|
16
|
+
|
|
1
17
|
export default function useScheduleTask() {
|
|
2
18
|
async function getScheduleTasks({
|
|
3
19
|
site = "",
|
|
@@ -38,7 +54,7 @@ export default function useScheduleTask() {
|
|
|
38
54
|
areas: { name: string; value: string }[];
|
|
39
55
|
site: string;
|
|
40
56
|
serviceType: string;
|
|
41
|
-
}) {
|
|
57
|
+
} & TScheduleRepeatFields) {
|
|
42
58
|
return useNuxtApp().$api<Record<string, any>>(
|
|
43
59
|
`/api/hygiene-schedule-tasks/site/${payload.site}`,
|
|
44
60
|
{
|
|
@@ -50,6 +66,7 @@ export default function useScheduleTask() {
|
|
|
50
66
|
description: payload.description,
|
|
51
67
|
areas: payload.areas,
|
|
52
68
|
serviceType: payload.serviceType,
|
|
69
|
+
...repeatBody(payload),
|
|
53
70
|
},
|
|
54
71
|
},
|
|
55
72
|
);
|
|
@@ -63,7 +80,7 @@ export default function useScheduleTask() {
|
|
|
63
80
|
dates: string[];
|
|
64
81
|
description?: string;
|
|
65
82
|
areas: { name: string; value: string }[];
|
|
66
|
-
},
|
|
83
|
+
} & TScheduleRepeatFields,
|
|
67
84
|
) {
|
|
68
85
|
return useNuxtApp().$api<Record<string, any>>(
|
|
69
86
|
`/api/hygiene-schedule-tasks/id/${id}`,
|
|
@@ -75,11 +92,23 @@ export default function useScheduleTask() {
|
|
|
75
92
|
dates: payload.dates,
|
|
76
93
|
description: payload.description,
|
|
77
94
|
areas: payload.areas,
|
|
95
|
+
...repeatBody(payload),
|
|
78
96
|
},
|
|
79
97
|
},
|
|
80
98
|
);
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
/** Pause / resume. The server stops the next runs while paused; nothing is deleted. */
|
|
102
|
+
function setScheduleTaskPaused(id: string, paused: boolean) {
|
|
103
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
104
|
+
`/api/hygiene-schedule-tasks/id/${id}`,
|
|
105
|
+
{
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
body: { paused },
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
83
112
|
function deleteScheduleTask(id: string) {
|
|
84
113
|
return useNuxtApp().$api<Record<string, any>>(
|
|
85
114
|
`/api/hygiene-schedule-tasks/id/${id}`,
|
|
@@ -95,5 +124,6 @@ export default function useScheduleTask() {
|
|
|
95
124
|
updateScheduleTask,
|
|
96
125
|
getScheduleTaskById,
|
|
97
126
|
deleteScheduleTask,
|
|
127
|
+
setScheduleTaskPaused,
|
|
98
128
|
};
|
|
99
129
|
}
|
|
@@ -115,6 +115,14 @@ export default function useSecurityPermission() {
|
|
|
115
115
|
},
|
|
116
116
|
|
|
117
117
|
"vehicle-mgmt": {
|
|
118
|
+
"see-blocklist": {
|
|
119
|
+
check: true,
|
|
120
|
+
description: "See Blocklist",
|
|
121
|
+
},
|
|
122
|
+
"manage-blocklist": {
|
|
123
|
+
check: true,
|
|
124
|
+
description: "Block / Unblock Vehicles",
|
|
125
|
+
},
|
|
118
126
|
"add-vehicle": {
|
|
119
127
|
check: true,
|
|
120
128
|
description: "Allows the user to add a new vehicle to the system.",
|
|
@@ -291,6 +299,10 @@ export default function useSecurityPermission() {
|
|
|
291
299
|
},
|
|
292
300
|
|
|
293
301
|
"visitor-mgmt": {
|
|
302
|
+
"invite-visitor": {
|
|
303
|
+
check: true,
|
|
304
|
+
description: "Invite Visitor / Contractor",
|
|
305
|
+
},
|
|
294
306
|
"add-visitor": {
|
|
295
307
|
check: true,
|
|
296
308
|
description: "Allows the user to add a new visitor to the system.",
|
|
@@ -814,6 +826,13 @@ export default function useSecurityPermission() {
|
|
|
814
826
|
|
|
815
827
|
},
|
|
816
828
|
|
|
829
|
+
"offline-data": {
|
|
830
|
+
"see-offline-data": {
|
|
831
|
+
check: true,
|
|
832
|
+
description: "See Offline Data",
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
|
|
817
836
|
};
|
|
818
837
|
|
|
819
838
|
return {
|