@7365admin1/layer-common 4.97.1-staging.578 → 4.97.1-staging.579
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/components/AreaChecklistHistoryLogs.vue +5 -4
- package/components/CleaningScheduleMain.vue +109 -14
- package/components/ManageChecklistMain.vue +16 -12
- 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/composables/useCleaningSchedules.ts +9 -0
- package/composables/useScheduleTask.ts +32 -2
- package/package.json +1 -1
- package/types/cleaner-schedule.d.ts +1 -1
- package/utils/schedule-task.ts +281 -0
- package/utils/status.ts +6 -0
- package/composables/useScheduleTaskArea.ts +0 -85
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<StatusChip
|
|
50
50
|
class="text-capitalize"
|
|
51
51
|
:status="status"
|
|
52
|
-
:label="status
|
|
52
|
+
:label="status === 'N/A' ? status : dailyStatusLabel(status)"
|
|
53
53
|
/>
|
|
54
54
|
</v-col>
|
|
55
55
|
</v-row>
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
pill
|
|
126
126
|
class="ml-2"
|
|
127
127
|
>
|
|
128
|
-
|
|
128
|
+
Extra visit
|
|
129
129
|
</StatusChip>
|
|
130
130
|
</v-list-subheader>
|
|
131
131
|
|
|
@@ -165,8 +165,8 @@
|
|
|
165
165
|
green as on every other screen. -->
|
|
166
166
|
<StatusChip
|
|
167
167
|
class="text-capitalize"
|
|
168
|
-
:status="unit.status
|
|
169
|
-
:label="unit.status
|
|
168
|
+
:status="historyUnitLabel(unit.status)"
|
|
169
|
+
:label="historyUnitLabel(unit.status)"
|
|
170
170
|
/>
|
|
171
171
|
</v-col>
|
|
172
172
|
</v-row>
|
|
@@ -262,6 +262,7 @@
|
|
|
262
262
|
</template>
|
|
263
263
|
|
|
264
264
|
<script setup lang="ts">
|
|
265
|
+
import { dailyStatusLabel, historyUnitLabel } from "../utils/schedule-task";
|
|
265
266
|
const props = defineProps({
|
|
266
267
|
orgId: { type: String, required: true, default: "" },
|
|
267
268
|
site: { type: String, required: true, default: "" },
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12" lg="12">
|
|
4
|
+
<ScheduleGuide
|
|
5
|
+
v-if="canViewSchedules"
|
|
6
|
+
:org-id="orgId"
|
|
7
|
+
:site="site"
|
|
8
|
+
:service-type="serviceType"
|
|
9
|
+
:schedule-route="scheduleRoute"
|
|
10
|
+
text="A new checklist is created for every area automatically each midnight. Staff tick off each unit on the mobile app."
|
|
11
|
+
/>
|
|
4
12
|
<TableMain
|
|
5
13
|
v-if="canViewSchedules"
|
|
6
|
-
:title="
|
|
14
|
+
:title="pageTitle"
|
|
7
15
|
:items="items"
|
|
8
16
|
:headers="headers"
|
|
9
17
|
:loading="loading"
|
|
@@ -15,6 +23,28 @@
|
|
|
15
23
|
@refresh="onRefresh"
|
|
16
24
|
@row-click="onRowClick"
|
|
17
25
|
>
|
|
26
|
+
<template #no-data>
|
|
27
|
+
<div class="cs-empty">
|
|
28
|
+
<strong>No daily lists yet.</strong>
|
|
29
|
+
<span>
|
|
30
|
+
A list is created automatically every midnight, once your Areas
|
|
31
|
+
and Units are set up.
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<template #actions>
|
|
37
|
+
<v-btn
|
|
38
|
+
v-if="showCreateToday"
|
|
39
|
+
class="text-none screen-btn-primary"
|
|
40
|
+
variant="flat"
|
|
41
|
+
:loading="creatingToday"
|
|
42
|
+
@click="createTodaysList"
|
|
43
|
+
>
|
|
44
|
+
Create today's list now
|
|
45
|
+
</v-btn>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
18
48
|
<template #extension>
|
|
19
49
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
20
50
|
<div class="filter-bar">
|
|
@@ -73,7 +103,7 @@
|
|
|
73
103
|
<StatusChip
|
|
74
104
|
class="text-capitalize"
|
|
75
105
|
:status="value"
|
|
76
|
-
:label="value
|
|
106
|
+
:label="dailyStatusLabel(value)"
|
|
77
107
|
/>
|
|
78
108
|
</template>
|
|
79
109
|
<template #item.completedAt="{ value }">
|
|
@@ -110,16 +140,22 @@
|
|
|
110
140
|
</div>
|
|
111
141
|
</AppCard>
|
|
112
142
|
</div>
|
|
143
|
+
<Snackbar
|
|
144
|
+
v-model="messageSnackbar"
|
|
145
|
+
:text="message"
|
|
146
|
+
:color="messageColor"
|
|
147
|
+
/>
|
|
113
148
|
</v-col>
|
|
114
149
|
</v-row>
|
|
115
150
|
</template>
|
|
116
151
|
|
|
117
152
|
<script lang="ts" setup>
|
|
118
153
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
119
|
-
import
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
154
|
+
import {
|
|
155
|
+
dailyStatusLabel,
|
|
156
|
+
hasListForToday,
|
|
157
|
+
scheduleNameFor,
|
|
158
|
+
} from "../utils/schedule-task";
|
|
123
159
|
import useUtils from "../composables/useUtils";
|
|
124
160
|
|
|
125
161
|
const props = defineProps({
|
|
@@ -132,16 +168,24 @@ const props = defineProps({
|
|
|
132
168
|
canViewScheduleDetails: { type: Boolean, default: false },
|
|
133
169
|
canDownloadSchedule: { type: Boolean, default: false },
|
|
134
170
|
canManageScheduleTasks: { type: Boolean, default: false },
|
|
171
|
+
canGenerateChecklist: { type: Boolean, default: false },
|
|
135
172
|
});
|
|
136
173
|
|
|
174
|
+
// "Daily Schedule - Cleaning" / "- Landscape" / "- Pest" / "- Pool" / "- Technician"
|
|
175
|
+
const pageTitle = `Daily Schedule – ${scheduleNameFor(props.serviceType).title.replace(
|
|
176
|
+
/ Schedule$/,
|
|
177
|
+
"",
|
|
178
|
+
)}`;
|
|
179
|
+
|
|
137
180
|
const startDate = ref("");
|
|
138
181
|
const endDate = ref("");
|
|
139
182
|
const status = ref<TScheduleAreaStatus>("all");
|
|
140
183
|
const statusOptions = [
|
|
141
184
|
{ title: "All", value: "all" },
|
|
142
185
|
{ title: "Open", value: "open" },
|
|
143
|
-
{ title: "
|
|
186
|
+
{ title: "In progress", value: "ongoing" },
|
|
144
187
|
{ title: "Completed", value: "completed" },
|
|
188
|
+
{ title: "Closed", value: "closed" },
|
|
145
189
|
] as const;
|
|
146
190
|
|
|
147
191
|
const items = ref<Array<Record<string, any>>>([]);
|
|
@@ -159,8 +203,18 @@ const headers = [
|
|
|
159
203
|
const remainingTime = ref({} as Record<string, string>);
|
|
160
204
|
const remainingSeconds = ref({} as Record<string, number>);
|
|
161
205
|
const downloadingId = ref<string | null>(null);
|
|
206
|
+
const creatingToday = ref(false);
|
|
162
207
|
|
|
163
|
-
const
|
|
208
|
+
const message = ref("");
|
|
209
|
+
const messageSnackbar = ref(false);
|
|
210
|
+
const messageColor = ref("error");
|
|
211
|
+
|
|
212
|
+
const {
|
|
213
|
+
getCleaningSchedules,
|
|
214
|
+
downloadChecklistPdf,
|
|
215
|
+
createTodaysParent,
|
|
216
|
+
generateChecklist,
|
|
217
|
+
} = useCleaningSchedules();
|
|
164
218
|
const { formatDate } = useUtils();
|
|
165
219
|
|
|
166
220
|
/**
|
|
@@ -280,6 +334,36 @@ const updateRemainingTime = () => {
|
|
|
280
334
|
});
|
|
281
335
|
};
|
|
282
336
|
|
|
337
|
+
// Only on the unfiltered first page, where today's row would be if it exists.
|
|
338
|
+
const showCreateToday = computed(
|
|
339
|
+
() =>
|
|
340
|
+
props.canGenerateChecklist &&
|
|
341
|
+
!loading.value &&
|
|
342
|
+
!!getCleanerChecklistReq.value &&
|
|
343
|
+
page.value === 1 &&
|
|
344
|
+
!startDate.value &&
|
|
345
|
+
!endDate.value &&
|
|
346
|
+
status.value === "all" &&
|
|
347
|
+
!hasListForToday(items.value),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
async function createTodaysList() {
|
|
351
|
+
try {
|
|
352
|
+
creatingToday.value = true;
|
|
353
|
+
const parent = await createTodaysParent(props.site, props.serviceType);
|
|
354
|
+
await generateChecklist(props.site, parent.id);
|
|
355
|
+
showMessage("Today's list is ready.", "success");
|
|
356
|
+
await getCleanerChecklistRefresh();
|
|
357
|
+
} catch (error: any) {
|
|
358
|
+
showMessage(
|
|
359
|
+
error?.data?.message || error?.message || "Could not create today's list.",
|
|
360
|
+
"error",
|
|
361
|
+
);
|
|
362
|
+
} finally {
|
|
363
|
+
creatingToday.value = false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
283
367
|
watchEffect(() => {
|
|
284
368
|
if (getCleanerChecklistReq.value) {
|
|
285
369
|
items.value = getCleanerChecklistReq.value.items;
|
|
@@ -333,7 +417,11 @@ async function downloadItem(item: any) {
|
|
|
333
417
|
downloadingId.value = id;
|
|
334
418
|
await downloadChecklistPdf(id);
|
|
335
419
|
} catch (err) {
|
|
336
|
-
|
|
420
|
+
showMessage(
|
|
421
|
+
(err as any)?.data?.message ||
|
|
422
|
+
"Could not download the PDF. Please try again.",
|
|
423
|
+
"error",
|
|
424
|
+
);
|
|
337
425
|
} finally {
|
|
338
426
|
downloadingId.value = null;
|
|
339
427
|
}
|
|
@@ -375,15 +463,22 @@ function onRowClick(data: any) {
|
|
|
375
463
|
}
|
|
376
464
|
|
|
377
465
|
function showMessage(msg: string, color: string = "error") {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
alert(msg);
|
|
382
|
-
}
|
|
466
|
+
message.value = msg;
|
|
467
|
+
messageColor.value = color;
|
|
468
|
+
messageSnackbar.value = true;
|
|
383
469
|
}
|
|
384
470
|
</script>
|
|
385
471
|
|
|
386
472
|
<style scoped>
|
|
473
|
+
.cs-empty {
|
|
474
|
+
display: flex;
|
|
475
|
+
flex-direction: column;
|
|
476
|
+
gap: 4px;
|
|
477
|
+
padding: 24px 16px;
|
|
478
|
+
font-family: var(--font-sans);
|
|
479
|
+
font-size: var(--fs-cell);
|
|
480
|
+
color: var(--text2);
|
|
481
|
+
}
|
|
387
482
|
.cs-no-permission {
|
|
388
483
|
font-family: var(--font-sans);
|
|
389
484
|
font-size: var(--fs-cell);
|
|
@@ -109,9 +109,9 @@
|
|
|
109
109
|
<div class="d-flex align-center ga-2 mb-2">
|
|
110
110
|
<v-icon color="error" size="18">mdi-close-circle</v-icon>
|
|
111
111
|
<span class="text-caption font-weight-bold">
|
|
112
|
-
|
|
112
|
+
Needs redo
|
|
113
113
|
</span>
|
|
114
|
-
<StatusChip status="
|
|
114
|
+
<StatusChip status="Needs redo" :dot="false" />
|
|
115
115
|
</div>
|
|
116
116
|
<div
|
|
117
117
|
v-if="item.remarks"
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
</v-row>
|
|
147
147
|
</template>
|
|
148
148
|
|
|
149
|
-
<!--
|
|
149
|
+
<!-- Done / Needs redo action buttons (labels only; the API still says approve / reject) -->
|
|
150
150
|
<template #item-append="{ item, group }">
|
|
151
151
|
<v-row
|
|
152
152
|
v-if="!isScheduleClosed && canManageScheduleTasks"
|
|
@@ -165,6 +165,8 @@
|
|
|
165
165
|
color="error"
|
|
166
166
|
:loading="!!loadingActions[getKey(item, group.set)]"
|
|
167
167
|
:disabled="!!loadingActions[getKey(item, group.set)]"
|
|
168
|
+
aria-label="Needs redo"
|
|
169
|
+
title="Needs redo"
|
|
168
170
|
@click.stop="handleItemActionClick(item, group.set, 'reject')"
|
|
169
171
|
/>
|
|
170
172
|
</v-col>
|
|
@@ -180,6 +182,8 @@
|
|
|
180
182
|
color="success"
|
|
181
183
|
:loading="!!loadingActions[getKey(item, group.set)]"
|
|
182
184
|
:disabled="!!loadingActions[getKey(item, group.set)]"
|
|
185
|
+
aria-label="Done"
|
|
186
|
+
title="Done"
|
|
183
187
|
@click.stop="handleItemActionClick(item, group.set, 'approve')"
|
|
184
188
|
/>
|
|
185
189
|
</v-col>
|
|
@@ -190,7 +194,7 @@
|
|
|
190
194
|
<v-row no-gutters align="center" class="ga-2">
|
|
191
195
|
<v-col v-if="isGroupAnyApproved(group)" cols="auto">
|
|
192
196
|
<StatusChip
|
|
193
|
-
:status="isGroupComplete(group) ? 'Completed' : '
|
|
197
|
+
:status="isGroupComplete(group) ? 'Completed' : 'In progress'"
|
|
194
198
|
:tone="isGroupComplete(group) ? 'ok' : 'warn'"
|
|
195
199
|
:dot="false"
|
|
196
200
|
class="text-none"
|
|
@@ -202,7 +206,7 @@
|
|
|
202
206
|
: "mdi-progress-clock"
|
|
203
207
|
}}
|
|
204
208
|
</v-icon>
|
|
205
|
-
{{ isGroupComplete(group) ? "Completed" : "
|
|
209
|
+
{{ isGroupComplete(group) ? "Completed" : "In progress" }}
|
|
206
210
|
<template v-if="getGroupCompletedByName(group)">
|
|
207
211
|
· {{ getGroupCompletedByName(group) }}
|
|
208
212
|
</template>
|
|
@@ -316,7 +320,7 @@
|
|
|
316
320
|
-->
|
|
317
321
|
<v-dialog v-model="showRejectModal" max-width="600" persistent>
|
|
318
322
|
<v-card class="screen-modal">
|
|
319
|
-
<v-card-title>
|
|
323
|
+
<v-card-title>Needs redo</v-card-title>
|
|
320
324
|
|
|
321
325
|
<v-divider />
|
|
322
326
|
|
|
@@ -901,14 +905,14 @@ async function submitRejectDialog(): Promise<void> {
|
|
|
901
905
|
rejectModal.remarks.trim(),
|
|
902
906
|
rejectModal.photoIds
|
|
903
907
|
);
|
|
904
|
-
showMessage(response?.message || "
|
|
908
|
+
showMessage(response?.message || "Marked as needs redo", "success");
|
|
905
909
|
showRejectModal.value = false;
|
|
906
910
|
resetRejectModal();
|
|
907
911
|
await getUnitCleanerChecklistRefresh();
|
|
908
912
|
} catch (error: any) {
|
|
909
913
|
console.error("Error rejecting unit checklist:", error);
|
|
910
914
|
showMessage(
|
|
911
|
-
error?.data?.message || error?.message || "
|
|
915
|
+
error?.data?.message || error?.message || "Could not mark this unit as needs redo",
|
|
912
916
|
"error"
|
|
913
917
|
);
|
|
914
918
|
} finally {
|
|
@@ -985,7 +989,7 @@ async function _callUpdateUnitChecklist(
|
|
|
985
989
|
(s) => s.unit === item.unit && s.set === set
|
|
986
990
|
);
|
|
987
991
|
if (isApproved && action === "approve") {
|
|
988
|
-
showMessage("
|
|
992
|
+
showMessage("This unit is already done", "info");
|
|
989
993
|
return;
|
|
990
994
|
}
|
|
991
995
|
|
|
@@ -1000,7 +1004,7 @@ async function _callUpdateUnitChecklist(
|
|
|
1000
1004
|
);
|
|
1001
1005
|
showMessage(
|
|
1002
1006
|
response?.message ||
|
|
1003
|
-
`Unit ${action === "approve" ? "
|
|
1007
|
+
`Unit marked as ${action === "approve" ? "done" : "needs redo"}`,
|
|
1004
1008
|
"success"
|
|
1005
1009
|
);
|
|
1006
1010
|
await getUnitCleanerChecklistRefresh();
|
|
@@ -1071,13 +1075,13 @@ async function _updateUnitChecklist({
|
|
|
1071
1075
|
);
|
|
1072
1076
|
}
|
|
1073
1077
|
|
|
1074
|
-
showMessage(`Set ${setNumber}
|
|
1078
|
+
showMessage(`Set ${setNumber} done`, "success");
|
|
1075
1079
|
|
|
1076
1080
|
// Refresh data
|
|
1077
1081
|
await getUnitCleanerChecklistRefresh();
|
|
1078
1082
|
} catch (error: any) {
|
|
1079
1083
|
console.error("Error approving set:", error);
|
|
1080
|
-
showMessage(error?.message || "
|
|
1084
|
+
showMessage(error?.message || "Could not finish this set", "error");
|
|
1081
1085
|
} finally {
|
|
1082
1086
|
submitting.value = false;
|
|
1083
1087
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12" lg="12">
|
|
4
|
+
<ScheduleGuide
|
|
5
|
+
:org-id="orgId"
|
|
6
|
+
:site="site"
|
|
7
|
+
:service-type="serviceType"
|
|
8
|
+
:schedule-route="scheduleRoute"
|
|
9
|
+
text="These are the areas on this day's checklist. Open an area to see its units and visits."
|
|
10
|
+
/>
|
|
4
11
|
<TableMain
|
|
5
|
-
:title="
|
|
12
|
+
:title="pageTitle"
|
|
6
13
|
:headers="headers"
|
|
7
14
|
:items="items"
|
|
8
15
|
:loading="loading"
|
|
@@ -14,6 +21,16 @@
|
|
|
14
21
|
@row-click="onRowClick"
|
|
15
22
|
@refresh="getScheduleAreasRefresh"
|
|
16
23
|
>
|
|
24
|
+
<template #no-data>
|
|
25
|
+
<div class="sa-empty">
|
|
26
|
+
<strong>No areas on this list yet.</strong>
|
|
27
|
+
<span>
|
|
28
|
+
Areas appear here once they are set up with Units and the list is
|
|
29
|
+
created. Use "Create today's list now" if the midnight list is
|
|
30
|
+
missing.
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
17
34
|
<template #item.type="{ item }">
|
|
18
35
|
<span class="text-capitalize">{{ item.type }}</span>
|
|
19
36
|
</template>
|
|
@@ -87,53 +104,18 @@
|
|
|
87
104
|
History
|
|
88
105
|
</v-btn>
|
|
89
106
|
<v-btn
|
|
90
|
-
v-if="
|
|
107
|
+
v-if="showCreateList"
|
|
91
108
|
class="text-none screen-btn-primary"
|
|
92
109
|
variant="flat"
|
|
93
|
-
aria-label="
|
|
110
|
+
aria-label="Create today's list now"
|
|
94
111
|
@click="_generateChecklist"
|
|
95
112
|
:loading="submitting"
|
|
96
|
-
:disabled="isScheduleClosed"
|
|
97
113
|
>
|
|
98
|
-
|
|
114
|
+
Create today's list now
|
|
99
115
|
</v-btn>
|
|
100
116
|
</v-col>
|
|
101
117
|
</v-row>
|
|
102
118
|
</template>
|
|
103
|
-
<template #item.acceptedBy="{ item }">
|
|
104
|
-
<div
|
|
105
|
-
v-if="
|
|
106
|
-
item.acceptedByName ||
|
|
107
|
-
(item.status && String(item.status).toLowerCase() === 'accepted')
|
|
108
|
-
"
|
|
109
|
-
>
|
|
110
|
-
<!--
|
|
111
|
-
`color`/`variant` on a plain `<span>` do nothing - Vuetify only
|
|
112
|
-
reads them on its own components, so this drew as bare cell text
|
|
113
|
-
while reading as a tonal green chip. It is the accepted chip the
|
|
114
|
-
sibling branches already use.
|
|
115
|
-
-->
|
|
116
|
-
<StatusChip
|
|
117
|
-
status="Accepted"
|
|
118
|
-
class="text-capitalize"
|
|
119
|
-
:label="item.acceptedByName || item.acceptedBy || 'Accepted'"
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
122
|
-
<div v-else-if="isScheduleClosed">
|
|
123
|
-
<StatusChip status="Closed" class="text-capitalize" />
|
|
124
|
-
</div>
|
|
125
|
-
<div v-else>
|
|
126
|
-
<v-btn
|
|
127
|
-
v-if="canManageScheduleTasks"
|
|
128
|
-
variant="outlined"
|
|
129
|
-
class="text-capitalize screen-btn-ghost"
|
|
130
|
-
@click.stop="() => _acceptAreaChecklist(item._id)"
|
|
131
|
-
>
|
|
132
|
-
Accept
|
|
133
|
-
</v-btn>
|
|
134
|
-
</div>
|
|
135
|
-
</template>
|
|
136
|
-
|
|
137
119
|
<template #item.createdAt="{ value }">
|
|
138
120
|
{{
|
|
139
121
|
formatDate
|
|
@@ -148,7 +130,7 @@
|
|
|
148
130
|
<StatusChip
|
|
149
131
|
class="text-capitalize"
|
|
150
132
|
:status="value"
|
|
151
|
-
:label="value
|
|
133
|
+
:label="dailyStatusLabel(value)"
|
|
152
134
|
/>
|
|
153
135
|
</template>
|
|
154
136
|
</TableMain></v-col
|
|
@@ -160,10 +142,7 @@
|
|
|
160
142
|
|
|
161
143
|
<script setup lang="ts">
|
|
162
144
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
163
|
-
import
|
|
164
|
-
import { workerTitle } from "../utils/worker-terms";
|
|
165
|
-
// "Cleaner Checklist" in Hygiene, "Gardener Checklist" in Landscape, ...
|
|
166
|
-
const checklistTitle = workerTitle(`${useWorkerTerm().one} checklist`);
|
|
145
|
+
import { dailyStatusLabel, scheduleNameFor } from "../utils/schedule-task";
|
|
167
146
|
import useUtils from "../composables/useUtils";
|
|
168
147
|
|
|
169
148
|
const props = defineProps({
|
|
@@ -178,6 +157,12 @@ const props = defineProps({
|
|
|
178
157
|
canManageScheduleTasks: { type: Boolean, default: false },
|
|
179
158
|
});
|
|
180
159
|
|
|
160
|
+
// "Daily Schedule - Cleaning" / "- Landscape" / ... (same title as the day list)
|
|
161
|
+
const pageTitle = `Daily Schedule – ${scheduleNameFor(props.serviceType).title.replace(
|
|
162
|
+
/ Schedule$/,
|
|
163
|
+
"",
|
|
164
|
+
)}`;
|
|
165
|
+
|
|
181
166
|
const submitting = ref(false);
|
|
182
167
|
const message = ref("");
|
|
183
168
|
const messageSnackbar = ref(false);
|
|
@@ -191,7 +176,7 @@ const headers = [
|
|
|
191
176
|
{ title: "Area", value: "name" },
|
|
192
177
|
{ title: "Status", value: "status" },
|
|
193
178
|
{ title: "Type", value: "type" },
|
|
194
|
-
{ title: "
|
|
179
|
+
{ title: "Visits per day", value: "set" },
|
|
195
180
|
];
|
|
196
181
|
|
|
197
182
|
const { formatDate, back } = useUtils();
|
|
@@ -214,7 +199,7 @@ const statusFilter = ref<TScheduleAreaStatus>("all");
|
|
|
214
199
|
const statusOptions = [
|
|
215
200
|
{ title: "All", value: "all" },
|
|
216
201
|
{ title: "Open", value: "open" },
|
|
217
|
-
{ title: "
|
|
202
|
+
{ title: "In progress", value: "ongoing" },
|
|
218
203
|
{ title: "Completed", value: "completed" },
|
|
219
204
|
];
|
|
220
205
|
const areaTypeFilter = ref<TAreaType>("all");
|
|
@@ -224,8 +209,7 @@ const typeOptions = [
|
|
|
224
209
|
{ title: "Toilet", value: "toilet" },
|
|
225
210
|
];
|
|
226
211
|
|
|
227
|
-
const { getScheduleAreas,
|
|
228
|
-
useCleaningSchedules();
|
|
212
|
+
const { getScheduleAreas, generateChecklist } = useCleaningSchedules();
|
|
229
213
|
|
|
230
214
|
const {
|
|
231
215
|
data: getScheduleAreasReq,
|
|
@@ -262,6 +246,16 @@ watchEffect(() => {
|
|
|
262
246
|
}
|
|
263
247
|
});
|
|
264
248
|
|
|
249
|
+
// Only when the day has no areas at all: that is the missed-midnight case.
|
|
250
|
+
const showCreateList = computed(
|
|
251
|
+
() =>
|
|
252
|
+
canGenerateChecklist.value &&
|
|
253
|
+
!isScheduleClosed.value &&
|
|
254
|
+
!loading.value &&
|
|
255
|
+
!!getScheduleAreasReq.value &&
|
|
256
|
+
items.value.length === 0,
|
|
257
|
+
);
|
|
258
|
+
|
|
265
259
|
function showMessage(msg: string, color: string = "error") {
|
|
266
260
|
message.value = msg;
|
|
267
261
|
messageColor.value = color;
|
|
@@ -288,38 +282,17 @@ function openHistory() {
|
|
|
288
282
|
navigateTo(path);
|
|
289
283
|
}
|
|
290
284
|
|
|
291
|
-
async function _acceptAreaChecklist(id?: string) {
|
|
292
|
-
if (!id) {
|
|
293
|
-
showMessage("No checklist id provided to accept.", "error");
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
submitting.value = true;
|
|
299
|
-
await acceptAreaChecklist(id);
|
|
300
|
-
showMessage("Area checklist accepted successfully.", "success");
|
|
301
|
-
|
|
302
|
-
if (typeof getScheduleAreasRefresh === "function") {
|
|
303
|
-
getScheduleAreasRefresh();
|
|
304
|
-
}
|
|
305
|
-
} catch (error: any) {
|
|
306
|
-
showMessage(error?.message || "Failed to accept area checklist.", "error");
|
|
307
|
-
} finally {
|
|
308
|
-
submitting.value = false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
285
|
async function _generateChecklist() {
|
|
313
286
|
try {
|
|
314
287
|
submitting.value = true;
|
|
315
288
|
await generateChecklist(props.site, props.scheduleAreaId);
|
|
316
|
-
showMessage("
|
|
289
|
+
showMessage("Today's list is ready.", "success");
|
|
317
290
|
|
|
318
291
|
if (typeof getScheduleAreasRefresh === "function") {
|
|
319
292
|
getScheduleAreasRefresh();
|
|
320
293
|
}
|
|
321
294
|
} catch (error: any) {
|
|
322
|
-
showMessage(error?.message || "
|
|
295
|
+
showMessage(error?.data?.message || error?.message || "Could not create today's list.", "error");
|
|
323
296
|
} finally {
|
|
324
297
|
submitting.value = false;
|
|
325
298
|
}
|
|
@@ -354,3 +327,15 @@ onUnmounted(() => {
|
|
|
354
327
|
} catch (_) {}
|
|
355
328
|
});
|
|
356
329
|
</script>
|
|
330
|
+
|
|
331
|
+
<style scoped>
|
|
332
|
+
.sa-empty {
|
|
333
|
+
display: flex;
|
|
334
|
+
flex-direction: column;
|
|
335
|
+
gap: 4px;
|
|
336
|
+
padding: 24px 16px;
|
|
337
|
+
font-family: var(--font-sans);
|
|
338
|
+
font-size: var(--fs-cell);
|
|
339
|
+
color: var(--text2);
|
|
340
|
+
}
|
|
341
|
+
</style>
|