@7365admin1/layer-common 4.97.1-staging.589 → 4.97.1-staging.591
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 +2 -2
- package/components/AreaFormDialog.vue +4 -1
- package/components/AreaMain.vue +10 -3
- package/components/BuildingForm.vue +1 -1
- package/components/BuildingManagement/buildings.vue +3 -3
- package/components/CleaningScheduleMain.vue +2 -2
- package/components/ScheduleAreaMain.vue +5 -1
- package/components/ScheduleTaskForm.vue +16 -17
- package/components/ScheduleTaskMain.vue +43 -39
- package/components/TableHygiene.vue +4 -1
- package/components/UnitMain.vue +3 -1
- package/package.json +1 -1
- package/utils/breadcrumb.ts +2 -1
- package/utils/nav-sections.ts +1 -0
- package/utils/permission-labels.ts +17 -6
- package/utils/schedule-task.ts +51 -9
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
pill
|
|
126
126
|
class="ml-2"
|
|
127
127
|
>
|
|
128
|
-
|
|
128
|
+
{{ extraCheckLabel(checklistSet) }}
|
|
129
129
|
</StatusChip>
|
|
130
130
|
</v-list-subheader>
|
|
131
131
|
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
</template>
|
|
263
263
|
|
|
264
264
|
<script setup lang="ts">
|
|
265
|
-
import { dailyStatusLabel, historyUnitLabel } from "../utils/schedule-task";
|
|
265
|
+
import { dailyStatusLabel, extraCheckLabel, historyUnitLabel } from "../utils/schedule-task";
|
|
266
266
|
const props = defineProps({
|
|
267
267
|
orgId: { type: String, required: true, default: "" },
|
|
268
268
|
site: { type: String, required: true, default: "" },
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<v-card-text class="screen-modal__body">
|
|
38
38
|
<v-form ref="formRef" v-model="valid">
|
|
39
39
|
<v-row no-gutters class="pa-0">
|
|
40
|
-
<v-col v-if="prop.showType" cols="12" class="pa-0 mb-2">
|
|
40
|
+
<v-col v-if="prop.showType && !prop.hideTypeField" cols="12" class="pa-0 mb-2">
|
|
41
41
|
<InputLabel for="type" title="Type" required class="mb-1" />
|
|
42
42
|
<v-select
|
|
43
43
|
v-model="typeModel"
|
|
@@ -169,6 +169,9 @@ const prop = defineProps({
|
|
|
169
169
|
label: { type: String, default: "Name" },
|
|
170
170
|
entityType: { type: String, default: "Area" }, // values: "Area", "Unit"
|
|
171
171
|
showType: { type: Boolean, default: true },
|
|
172
|
+
// Keeps the type in the saved payload (as loaded, or "Common") but does not
|
|
173
|
+
// offer the Common / Toilet choice - for apps where it means nothing.
|
|
174
|
+
hideTypeField: { type: Boolean, default: false },
|
|
172
175
|
showOptional: { type: Boolean, default: true },
|
|
173
176
|
showUnits: { type: Boolean, default: false },
|
|
174
177
|
site: { type: String, default: "" },
|
package/components/AreaMain.vue
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
@create="onCreateItem"
|
|
18
18
|
@row-click="handleRowClick"
|
|
19
19
|
>
|
|
20
|
-
<template #extension>
|
|
20
|
+
<template v-if="props.showAreaType" #extension>
|
|
21
21
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
22
22
|
<!--
|
|
23
23
|
A bare `v-card` here paints Vuetify's own `surface`, a slab a
|
|
@@ -138,6 +138,7 @@
|
|
|
138
138
|
:label="formLabel"
|
|
139
139
|
:entityType="entityTypeLabel"
|
|
140
140
|
:showUnits="true"
|
|
141
|
+
:hideTypeField="!props.showAreaType"
|
|
141
142
|
:site="props.site"
|
|
142
143
|
:serviceType="props.serviceType"
|
|
143
144
|
@saved="_createArea"
|
|
@@ -314,7 +315,9 @@
|
|
|
314
315
|
title="Import Areas from Excel"
|
|
315
316
|
noun="areas"
|
|
316
317
|
name-label="Area"
|
|
317
|
-
fill-hint="
|
|
318
|
+
:fill-hint="props.showAreaType
|
|
319
|
+
? 'One area per row: its name, Type (Common or Toilet, from the dropdown), Set and the units to check, separated by commas.'
|
|
320
|
+
: 'One area per row: its name, Set and the units to check, separated by commas. Leave Type as Common.'"
|
|
318
321
|
duplicate-note="Areas already on the list (same name and type) are skipped and left unchanged."
|
|
319
322
|
template-file-name="areas-import-template.xlsx"
|
|
320
323
|
:download-template="() => downloadTemplate('areas', importQuery)"
|
|
@@ -345,6 +348,10 @@ const props = defineProps({
|
|
|
345
348
|
canUpdateArea: { type: Boolean, default: false },
|
|
346
349
|
canDeleteArea: { type: Boolean, default: false },
|
|
347
350
|
canImportArea: { type: Boolean, default: false },
|
|
351
|
+
// Common / Toilet is a cleaning distinction (Toilet areas get the toilet
|
|
352
|
+
// checklist QR). Apps without it hide the Type filter, column and field;
|
|
353
|
+
// the saved `type` value is untouched (new areas stay "common").
|
|
354
|
+
showAreaType: { type: Boolean, default: true },
|
|
348
355
|
});
|
|
349
356
|
|
|
350
357
|
const isCleanerArea = computed(() => {
|
|
@@ -403,7 +410,7 @@ const isDownloading = ref(false);
|
|
|
403
410
|
|
|
404
411
|
const headers = [
|
|
405
412
|
{ title: "AREA", value: "name" },
|
|
406
|
-
{ title: "TYPE", value: "type" },
|
|
413
|
+
...(props.showAreaType ? [{ title: "TYPE", value: "type" }] : []),
|
|
407
414
|
{ title: "SETS", value: "set" },
|
|
408
415
|
];
|
|
409
416
|
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
<v-col cols="12" class="px-6">
|
|
123
|
-
<InputLabel class="text-capitalize font-weight-bold" title="
|
|
123
|
+
<InputLabel class="text-capitalize font-weight-bold" title="Block floor plan" />
|
|
124
124
|
<InputFileV2 v-model="building.buildingFiles" :multiple="true" accept="*" :max-length="10"
|
|
125
125
|
title="Upload Files" />
|
|
126
126
|
</v-col>
|
|
@@ -97,13 +97,13 @@
|
|
|
97
97
|
<v-list class="pa-0">
|
|
98
98
|
<v-list-item v-if="canUpdateBuilding" @click="openEditDialog()">
|
|
99
99
|
<v-list-item-title class="text-subtitle-2">
|
|
100
|
-
Edit
|
|
100
|
+
Edit Block
|
|
101
101
|
</v-list-item-title>
|
|
102
102
|
</v-list-item>
|
|
103
103
|
|
|
104
104
|
<v-list-item v-if="canDeleteBuilding" @click="openDeleteDialog()" class="text-red">
|
|
105
105
|
<v-list-item-title class="text-subtitle-2">
|
|
106
|
-
Delete
|
|
106
|
+
Delete Block
|
|
107
107
|
</v-list-item-title>
|
|
108
108
|
</v-list-item>
|
|
109
109
|
</v-list>
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
<v-col cols="6">
|
|
145
145
|
<v-btn tile block size="48" color="black" variant="flat" class="text-none" @click="handleDeleteBuilding"
|
|
146
146
|
:loading="deleteLoading">
|
|
147
|
-
Delete
|
|
147
|
+
Delete Block
|
|
148
148
|
</v-btn>
|
|
149
149
|
</v-col>
|
|
150
150
|
</v-row>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<li>
|
|
16
16
|
Every midnight a new row appears here for the day. It holds every
|
|
17
17
|
area, with as many visits as the area's "visits per day", plus
|
|
18
|
-
any <strong>Extra
|
|
18
|
+
any <strong>Extra Checks</strong> set for that day.
|
|
19
19
|
</li>
|
|
20
20
|
<li>
|
|
21
21
|
Staff open the same list in their mobile app and tick each unit
|
|
@@ -211,7 +211,7 @@ const props = defineProps({
|
|
|
211
211
|
// "Daily Checklist – Cleaning" / "– Landscape" / "– Pest" / "– Pool" / "– Technician"
|
|
212
212
|
const pageTitle = dailyChecklistTitle(props.serviceType);
|
|
213
213
|
|
|
214
|
-
// `?date=YYYY-MM-DD` (from an Extra
|
|
214
|
+
// `?date=YYYY-MM-DD` (from an Extra Check's "See that day's checklist") opens on that one day.
|
|
215
215
|
const linkedDay = String(useRoute().query.date ?? "");
|
|
216
216
|
const startDate = ref(/^\d{4}-\d{2}-\d{2}$/.test(linkedDay) ? linkedDay : "");
|
|
217
217
|
const endDate = ref(startDate.value);
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
/>
|
|
57
57
|
|
|
58
58
|
<v-select
|
|
59
|
+
v-if="props.showAreaType"
|
|
59
60
|
v-model="areaTypeFilter"
|
|
60
61
|
:items="typeOptions"
|
|
61
62
|
density="compact"
|
|
@@ -155,6 +156,9 @@ const props = defineProps({
|
|
|
155
156
|
canGenerateChecklist: { type: Boolean, default: false },
|
|
156
157
|
canViewHistory: { type: Boolean, default: false },
|
|
157
158
|
canManageScheduleTasks: { type: Boolean, default: false },
|
|
159
|
+
// Common / Toilet is a cleaning distinction; apps without it hide the Type
|
|
160
|
+
// filter and column (saved `type` values are untouched).
|
|
161
|
+
showAreaType: { type: Boolean, default: true },
|
|
158
162
|
});
|
|
159
163
|
|
|
160
164
|
// "Daily Checklist – Cleaning" / "– Landscape" / ... (same title as the day list)
|
|
@@ -172,7 +176,7 @@ const pageRange = ref("-- - -- of --");
|
|
|
172
176
|
const headers = [
|
|
173
177
|
{ title: "Area", value: "name" },
|
|
174
178
|
{ title: "Status", value: "status" },
|
|
175
|
-
{ title: "Type", value: "type" },
|
|
179
|
+
...(props.showAreaType ? [{ title: "Type", value: "type" }] : []),
|
|
176
180
|
{ title: "Visits per day", value: "set" },
|
|
177
181
|
];
|
|
178
182
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
ADD / EDIT AN EXTRA
|
|
2
|
+
ADD / EDIT AN EXTRA CHECK - four short steps: What, Where, When, Who is told.
|
|
3
3
|
|
|
4
4
|
The last step shows one sentence saying exactly what will be created
|
|
5
|
-
("Adds 1 extra
|
|
5
|
+
("Adds 1 extra check to 3 areas (24 units) at 08:00 every weekday ... next:
|
|
6
6
|
Wed 21 Oct"). The form checks what the server checks (utils/schedule-task.ts)
|
|
7
7
|
so a schedule that would never run is caught before saving; whatever the
|
|
8
8
|
server still refuses is shown in its own words.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<v-dialog v-model="show" max-width="760" persistent scrollable>
|
|
12
12
|
<v-card class="screen-modal">
|
|
13
13
|
<v-card-title class="d-flex align-center">
|
|
14
|
-
{{ props.mode === "edit" ? "Edit Extra
|
|
14
|
+
{{ props.mode === "edit" ? "Edit Extra Check" : "Add Extra Check" }}
|
|
15
15
|
<v-spacer />
|
|
16
16
|
<v-btn
|
|
17
17
|
icon="mdi-close"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<v-stepper-window-item :value="1">
|
|
50
50
|
<div class="st-step">
|
|
51
51
|
<p class="st-step__help">
|
|
52
|
-
Give the extra
|
|
52
|
+
Give the extra check a name staff will recognise, and say
|
|
53
53
|
what they should do.
|
|
54
54
|
</p>
|
|
55
55
|
<InputLabel for="title" title="Title" required />
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
class="filter-field mb-4"
|
|
62
62
|
placeholder="For example: Lunch toilet check"
|
|
63
63
|
:error-messages="fieldError('title')"
|
|
64
|
-
hint="Staff see this name on their phone
|
|
64
|
+
hint="Staff see this name on their phone and on the Daily Checklist."
|
|
65
65
|
persistent-hint
|
|
66
66
|
hide-details="auto"
|
|
67
67
|
/>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
density="comfortable"
|
|
74
74
|
class="filter-field"
|
|
75
75
|
rows="3"
|
|
76
|
-
placeholder="Optional. Anything staff should know for this
|
|
76
|
+
placeholder="Optional. Anything staff should know for this check."
|
|
77
77
|
hint="Optional. For example: refill soap and tissue as well."
|
|
78
78
|
persistent-hint
|
|
79
79
|
hide-details="auto"
|
|
@@ -85,9 +85,8 @@
|
|
|
85
85
|
<v-stepper-window-item :value="2">
|
|
86
86
|
<div class="st-step">
|
|
87
87
|
<p class="st-step__help">
|
|
88
|
-
Pick the areas
|
|
89
|
-
|
|
90
|
-
for this visit.
|
|
88
|
+
Pick the areas to check. Every unit in a picked area gets
|
|
89
|
+
one more tick box on the Daily Checklist for this check.
|
|
91
90
|
</p>
|
|
92
91
|
<InputLabel for="selected-areas" title="Areas" required />
|
|
93
92
|
<v-select
|
|
@@ -170,7 +169,7 @@
|
|
|
170
169
|
<div class="st-step">
|
|
171
170
|
<p class="st-step__help">
|
|
172
171
|
Choose the time, and whether it happens once or repeats.
|
|
173
|
-
Times are Singapore time. The
|
|
172
|
+
Times are Singapore time. The check appears on staff's
|
|
174
173
|
checklist at this time.
|
|
175
174
|
</p>
|
|
176
175
|
<v-row dense>
|
|
@@ -305,8 +304,8 @@
|
|
|
305
304
|
class="mb-4"
|
|
306
305
|
title="Who is told"
|
|
307
306
|
>
|
|
308
|
-
Staff
|
|
309
|
-
|
|
307
|
+
Staff of this site who use the mobile app get a phone
|
|
308
|
+
notification when the check is added to their checklist.
|
|
310
309
|
</v-alert>
|
|
311
310
|
|
|
312
311
|
<div class="st-summary">
|
|
@@ -321,7 +320,7 @@
|
|
|
321
320
|
<div class="st-preview">
|
|
322
321
|
<div class="st-preview__head">
|
|
323
322
|
<v-icon size="18" class="mr-1">mdi-clock-outline</v-icon>
|
|
324
|
-
{{ previewTime }} · Extra
|
|
323
|
+
{{ previewTime }} · Extra check
|
|
325
324
|
</div>
|
|
326
325
|
<div class="st-preview__title">
|
|
327
326
|
{{ subject.trim() || "(your title)" }}
|
|
@@ -391,7 +390,7 @@
|
|
|
391
390
|
:loading="submitting"
|
|
392
391
|
@click="submitTask"
|
|
393
392
|
>
|
|
394
|
-
{{ props.mode === "edit" ? "Save Changes" : "Add Extra
|
|
393
|
+
{{ props.mode === "edit" ? "Save Changes" : "Add Extra Check" }}
|
|
395
394
|
</v-btn>
|
|
396
395
|
</v-card-actions>
|
|
397
396
|
</v-card>
|
|
@@ -667,7 +666,7 @@ const loadTaskData = async () => {
|
|
|
667
666
|
} catch (error: any) {
|
|
668
667
|
if ((props.taskData as any)?.title) setFormFromTask(props.taskData);
|
|
669
668
|
showMessage(
|
|
670
|
-
error?.data?.message || "Could not load this extra
|
|
669
|
+
error?.data?.message || "Could not load this extra check.",
|
|
671
670
|
"error",
|
|
672
671
|
);
|
|
673
672
|
} finally {
|
|
@@ -719,7 +718,7 @@ const submitTask = async () => {
|
|
|
719
718
|
|
|
720
719
|
showMessage(
|
|
721
720
|
response?.message ||
|
|
722
|
-
`Extra
|
|
721
|
+
`Extra check ${props.mode === "edit" ? "saved" : "added"}.`,
|
|
723
722
|
"success",
|
|
724
723
|
);
|
|
725
724
|
|
|
@@ -729,7 +728,7 @@ const submitTask = async () => {
|
|
|
729
728
|
} catch (error: any) {
|
|
730
729
|
// The server's own plain-English message (permission, validation, ...).
|
|
731
730
|
serverError.value =
|
|
732
|
-
error?.data?.message || "Could not save this extra
|
|
731
|
+
error?.data?.message || "Could not save this extra check. Please try again.";
|
|
733
732
|
showMessage(serverError.value, "error");
|
|
734
733
|
} finally {
|
|
735
734
|
submitting.value = false;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
:org-id="orgId"
|
|
6
6
|
:site="site"
|
|
7
7
|
:service-type="serviceType"
|
|
8
|
-
text="
|
|
9
|
-
how-title="How extra
|
|
8
|
+
:text="FEATURE_SUBTITLE"
|
|
9
|
+
how-title="How extra checks work"
|
|
10
10
|
how-key="extra-visits"
|
|
11
11
|
>
|
|
12
12
|
<ol class="sg-steps">
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
that.
|
|
17
17
|
</li>
|
|
18
18
|
<li>
|
|
19
|
-
An <strong>extra
|
|
20
|
-
checklist, in the areas you pick, at the time you set - once,
|
|
21
|
-
repeating (every day, weekdays, chosen days, or monthly).
|
|
19
|
+
An <strong>extra check</strong> adds one more round of ticks to
|
|
20
|
+
that checklist, in the areas you pick, at the time you set - once,
|
|
21
|
+
or repeating (every day, weekdays, chosen days, or monthly).
|
|
22
22
|
</li>
|
|
23
23
|
<li>
|
|
24
|
-
At that time, staff
|
|
25
|
-
|
|
26
|
-
they finish it.
|
|
24
|
+
At that time, staff get a phone notification and see the extra
|
|
25
|
+
check in their app's Daily Checklist, marked "Extra check" with its
|
|
26
|
+
name. They tick each unit as they finish it.
|
|
27
27
|
</li>
|
|
28
28
|
<li>
|
|
29
29
|
Here and in the Daily Checklist you see how it went:
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
</ol>
|
|
34
34
|
<div class="sg-example">
|
|
35
35
|
<strong>Example:</strong> the lobby toilets get busy at lunch. Add
|
|
36
|
-
an extra
|
|
37
|
-
Toilets", set 2:00 pm, every weekday.
|
|
38
|
-
get one more toilet
|
|
36
|
+
an extra check called "Lunch toilet check", pick the area "Lobby
|
|
37
|
+
Toilets", set 2:00 pm, every weekday. Every weekday at 2:00 pm staff
|
|
38
|
+
get one more toilet check, on top of their normal rounds.
|
|
39
39
|
</div>
|
|
40
40
|
<div class="sg-h">What the words mean</div>
|
|
41
41
|
<dl class="sg-legend">
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
</template>
|
|
46
46
|
<template v-for="h in RESULT_HELP" :key="'r' + h.word">
|
|
47
47
|
<dt><StatusChip :status="h.word" :label="h.word" /></dt>
|
|
48
|
-
<dd>Last
|
|
48
|
+
<dd>Last check: {{ h.meaning }}</dd>
|
|
49
49
|
</template>
|
|
50
50
|
</dl>
|
|
51
51
|
</ScheduleGuide>
|
|
52
52
|
<TableMain
|
|
53
|
-
:title="
|
|
53
|
+
:title="FEATURE_NAME"
|
|
54
54
|
:items="items"
|
|
55
55
|
:headers="headers"
|
|
56
56
|
:loading="loading"
|
|
@@ -60,21 +60,21 @@
|
|
|
60
60
|
:pageRange="pageRange"
|
|
61
61
|
@refresh="getTaskRefresh"
|
|
62
62
|
:canCreate="canCreateScheduleTask"
|
|
63
|
-
createLabel="Add Extra
|
|
63
|
+
createLabel="Add Extra Check"
|
|
64
64
|
@create="openFormDialog"
|
|
65
65
|
@row-click="onRowClick"
|
|
66
66
|
>
|
|
67
67
|
<template #no-data>
|
|
68
68
|
<div class="st-empty">
|
|
69
|
-
<strong>No extra
|
|
69
|
+
<strong>No extra checks yet.</strong>
|
|
70
70
|
<span>
|
|
71
71
|
Your Daily Checklist already covers the normal rounds. Add an
|
|
72
|
-
extra
|
|
72
|
+
extra check when an area needs one more check at a set time -
|
|
73
73
|
for example "Lunch toilet check, Lobby Toilets, 2:00 pm every
|
|
74
74
|
weekday".
|
|
75
75
|
</span>
|
|
76
76
|
<span v-if="canCreateScheduleTask">
|
|
77
|
-
Press <strong>Add Extra
|
|
77
|
+
Press <strong>Add Extra Check</strong> above to start. Your
|
|
78
78
|
Areas and Units must be set up first.
|
|
79
79
|
</span>
|
|
80
80
|
</div>
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
variant="flat"
|
|
89
89
|
@click="openFormDialog"
|
|
90
90
|
>
|
|
91
|
-
Add Extra
|
|
91
|
+
Add Extra Check
|
|
92
92
|
</v-btn>
|
|
93
93
|
</v-col>
|
|
94
94
|
|
|
@@ -210,14 +210,14 @@
|
|
|
210
210
|
|
|
211
211
|
<v-dialog v-model="dialogShowMoreActions" width="400" persistent>
|
|
212
212
|
<HygieneUpdateMoreAction
|
|
213
|
-
:title="selectedTask?.title || 'Extra
|
|
213
|
+
:title="selectedTask?.title || 'Extra Check'"
|
|
214
214
|
:canUpdate="canUpdateScheduleTask"
|
|
215
215
|
@close="dialogShowMoreActions = false"
|
|
216
216
|
:canDelete="canDeleteScheduleTask"
|
|
217
|
-
:deleteButtonLabel="'Delete Extra
|
|
217
|
+
:deleteButtonLabel="'Delete Extra Check'"
|
|
218
218
|
@edit="onEditFromMoreAction"
|
|
219
219
|
@delete="onDeleteFromMoreAction"
|
|
220
|
-
:editButtonLabel="'Edit Extra
|
|
220
|
+
:editButtonLabel="'Edit Extra Check'"
|
|
221
221
|
>
|
|
222
222
|
<template #content>
|
|
223
223
|
<v-row no-gutters>
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
|
|
243
243
|
<v-row no-gutters class="mb-2">
|
|
244
244
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
245
|
-
Next
|
|
245
|
+
Next check:
|
|
246
246
|
</v-col>
|
|
247
247
|
<v-col cols="7" class="text-subtitle-2">
|
|
248
248
|
{{ selectedNext || "-" }}
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
|
|
252
252
|
<v-row no-gutters class="mb-2">
|
|
253
253
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
254
|
-
Last
|
|
254
|
+
Last check:
|
|
255
255
|
</v-col>
|
|
256
256
|
<v-col cols="7" class="text-subtitle-2">
|
|
257
257
|
{{ lastRunLabel(selectedTask) }}
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
</div>
|
|
376
376
|
<span v-else class="text-caption text-disabled"
|
|
377
377
|
>No units in this area, so it is skipped when
|
|
378
|
-
this
|
|
378
|
+
this check is added.</span
|
|
379
379
|
>
|
|
380
380
|
</v-card-text>
|
|
381
381
|
</div>
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
|
|
389
389
|
<v-row v-if="selectedTask.description" no-gutters class="mb-2">
|
|
390
390
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
391
|
-
|
|
391
|
+
Instructions:
|
|
392
392
|
</v-col>
|
|
393
393
|
<v-col cols="7" class="text-subtitle-2">
|
|
394
394
|
{{ selectedTask.description || "N/A" }}
|
|
@@ -441,11 +441,11 @@
|
|
|
441
441
|
<v-dialog v-model="dialogDeleteTask" max-width="520">
|
|
442
442
|
<v-card class="screen-modal">
|
|
443
443
|
<v-card-title class="text-capitalize">
|
|
444
|
-
Delete {{ selectedTask?.title || "Extra
|
|
444
|
+
Delete {{ selectedTask?.title || "Extra Check" }}
|
|
445
445
|
</v-card-title>
|
|
446
446
|
|
|
447
447
|
<v-card-text class="screen-modal__body">
|
|
448
|
-
This stops all future extra
|
|
448
|
+
This stops all future extra checks from it. Checks that already
|
|
449
449
|
happened stay on their Daily Checklists. Delete it?
|
|
450
450
|
</v-card-text>
|
|
451
451
|
|
|
@@ -487,6 +487,8 @@ import {
|
|
|
487
487
|
RESULT_HELP,
|
|
488
488
|
STATUS_HELP,
|
|
489
489
|
STATUS_LABELS,
|
|
490
|
+
FEATURE_NAME,
|
|
491
|
+
FEATURE_SUBTITLE,
|
|
490
492
|
describeRow,
|
|
491
493
|
describeWhen,
|
|
492
494
|
finishedReason,
|
|
@@ -526,7 +528,7 @@ const canViewScheduleTaskDetails = computed(
|
|
|
526
528
|
|
|
527
529
|
const checklistRoute = scheduleNameFor(props.serviceType).route;
|
|
528
530
|
|
|
529
|
-
/** Opens the Daily Checklist filtered to the day this extra
|
|
531
|
+
/** Opens the Daily Checklist filtered to the day this extra check was last added. */
|
|
530
532
|
function openChecklist(row: Record<string, any>) {
|
|
531
533
|
const day = String(row?.lastRun || "").slice(0, 10);
|
|
532
534
|
navigateTo({
|
|
@@ -593,10 +595,10 @@ watch(dialogShowMoreActions, async (isOpen) => {
|
|
|
593
595
|
const items = ref<any[]>([]);
|
|
594
596
|
|
|
595
597
|
const headers = [
|
|
596
|
-
{ title: "Extra
|
|
597
|
-
{ title: "
|
|
598
|
-
{ title: "Next
|
|
599
|
-
{ title: "Last
|
|
598
|
+
{ title: "Extra check", value: "title" },
|
|
599
|
+
{ title: "When and where", value: "when", sortable: false },
|
|
600
|
+
{ title: "Next check", value: "nextRun", sortable: false },
|
|
601
|
+
{ title: "Last check", value: "lastRun", sortable: false },
|
|
600
602
|
{ title: "Status", value: "status", sortable: false },
|
|
601
603
|
{ title: "", value: "actions", sortable: false, align: "end" as const },
|
|
602
604
|
];
|
|
@@ -660,7 +662,9 @@ const debounceSearch = debounce(getTaskRefresh, 500);
|
|
|
660
662
|
watch(
|
|
661
663
|
[searchInput],
|
|
662
664
|
() => {
|
|
663
|
-
|
|
665
|
+
// A search on page 2 must not land on an empty page: start from page 1.
|
|
666
|
+
if (page.value !== 1) page.value = 1;
|
|
667
|
+
else debounceSearch();
|
|
664
668
|
},
|
|
665
669
|
{ immediate: false, deep: true }
|
|
666
670
|
);
|
|
@@ -679,7 +683,7 @@ function onEditRow(item: Record<string, any>) {
|
|
|
679
683
|
showForm.value = true;
|
|
680
684
|
}
|
|
681
685
|
|
|
682
|
-
// A finished extra
|
|
686
|
+
// A finished extra check: open its "When" step so a new end date or repeat is one change away.
|
|
683
687
|
function onExtendRow(item: Record<string, any>) {
|
|
684
688
|
selectedTask.value = item;
|
|
685
689
|
formMode.value = "edit";
|
|
@@ -699,14 +703,14 @@ async function togglePause(item: Record<string, any>) {
|
|
|
699
703
|
await setScheduleTaskPaused(item._id, !resuming);
|
|
700
704
|
showMessage(
|
|
701
705
|
resuming
|
|
702
|
-
? "Resumed.
|
|
703
|
-
: "Paused.
|
|
706
|
+
? "Resumed. It is added again from its next date."
|
|
707
|
+
: "Paused. Nothing is added to staff's checklist until you resume it.",
|
|
704
708
|
"success",
|
|
705
709
|
);
|
|
706
710
|
await getTaskRefresh();
|
|
707
711
|
} catch (error: any) {
|
|
708
712
|
showMessage(
|
|
709
|
-
error?.data?.message || "Could not change this extra
|
|
713
|
+
error?.data?.message || "Could not change this extra check.",
|
|
710
714
|
"error",
|
|
711
715
|
);
|
|
712
716
|
} finally {
|
|
@@ -756,12 +760,12 @@ async function _deleteScheduleTask() {
|
|
|
756
760
|
|
|
757
761
|
const response = await deleteScheduleTask(id);
|
|
758
762
|
dialogDeleteTask.value = false;
|
|
759
|
-
showMessage("Extra
|
|
763
|
+
showMessage("Extra check deleted. Past checks are kept.", "success");
|
|
760
764
|
|
|
761
765
|
await getTaskRefresh();
|
|
762
766
|
} catch (error: any) {
|
|
763
767
|
showMessage(
|
|
764
|
-
error?.data?.message || "Could not delete this extra
|
|
768
|
+
error?.data?.message || "Could not delete this extra check.",
|
|
765
769
|
"error",
|
|
766
770
|
);
|
|
767
771
|
} finally {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
class="text-none"
|
|
93
93
|
>
|
|
94
94
|
<v-icon size="12">mdi-calendar-clock</v-icon>
|
|
95
|
-
|
|
95
|
+
{{ extraCheckLabel(group) }}
|
|
96
96
|
</StatusChip>
|
|
97
97
|
<slot name="group-header-chips" :group="group">
|
|
98
98
|
<StatusChip
|
|
@@ -206,6 +206,7 @@
|
|
|
206
206
|
</template>
|
|
207
207
|
|
|
208
208
|
<script lang="ts" setup>
|
|
209
|
+
import { extraCheckLabel } from "../utils/schedule-task";
|
|
209
210
|
defineOptions({ inheritAttrs: false });
|
|
210
211
|
|
|
211
212
|
const props = defineProps({
|
|
@@ -296,6 +297,8 @@ const groupedItems = computed(() => {
|
|
|
296
297
|
return {
|
|
297
298
|
set: item.set,
|
|
298
299
|
isScheduleTask: item.isScheduleTask ?? false,
|
|
300
|
+
scheduleTaskTitle: item.scheduleTaskTitle ?? null,
|
|
301
|
+
scheduleTaskTime: item.scheduleTaskTime ?? null,
|
|
299
302
|
completedByName: item.completedByName ?? null,
|
|
300
303
|
attachments:
|
|
301
304
|
(item.attachment as string[] | undefined) ??
|
package/components/UnitMain.vue
CHANGED
|
@@ -187,7 +187,9 @@
|
|
|
187
187
|
title="Import Units from Excel"
|
|
188
188
|
noun="units"
|
|
189
189
|
name-label="Unit"
|
|
190
|
-
fill-hint="
|
|
190
|
+
:fill-hint="/mechanical/i.test(props.serviceType)
|
|
191
|
+
? 'One unit per row, e.g. Air-con Unit, Water Pump.'
|
|
192
|
+
: 'One unit per row, e.g. Toilet Bowl, Sofa.'"
|
|
191
193
|
duplicate-note="Units already on the list are skipped and left unchanged."
|
|
192
194
|
template-file-name="units-import-template.xlsx"
|
|
193
195
|
:download-template="() => downloadTemplate('units', importQuery)"
|
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.97.1-staging.
|
|
5
|
+
"version": "4.97.1-staging.591",
|
|
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.",
|
package/utils/breadcrumb.ts
CHANGED
|
@@ -26,7 +26,8 @@ const RENAMED: [string, string][] = [
|
|
|
26
26
|
["Building Mgmt", "Occupancy Mgmt"],
|
|
27
27
|
["Event Mgmt", "Task Calendar"],
|
|
28
28
|
// Schedule screens (owner 21 Oct): the same names the menu and the mobile apps use.
|
|
29
|
-
["Schedule Task", "Extra
|
|
29
|
+
["Schedule Task", "Extra Checks"],
|
|
30
|
+
["Extra Visits", "Extra Checks"],
|
|
30
31
|
...["Cleaning", "Technician", "Pest", "Pool", "Landscape"].flatMap((t): [string, string][] => [
|
|
31
32
|
[`${t} Schedule Area`, "Area"],
|
|
32
33
|
[`${t} Schedule Unit`, "Unit"],
|
package/utils/nav-sections.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const NAV_SECTIONS: Record<string, string> = {
|
|
|
52
52
|
"Schedule Task": "SERVICE DELIVERY",
|
|
53
53
|
"Daily Checklist": "SERVICE DELIVERY",
|
|
54
54
|
"Extra Visits": "SERVICE DELIVERY",
|
|
55
|
+
"Extra Checks": "SERVICE DELIVERY",
|
|
55
56
|
|
|
56
57
|
// EQUIPMENT
|
|
57
58
|
"Equipment Management": "EQUIPMENT",
|
|
@@ -68,12 +68,13 @@ 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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Trade catalogue v2: the trade apps call `event-mgmt` the Task Calendar and
|
|
72
|
+
// `schedule-task-mgmt` Extra Checks (owner 22 Oct; the key is unchanged).
|
|
73
|
+
cleaning_services: { "event-mgmt": "Task Calendar", "schedule-task-mgmt": "Extra checks" },
|
|
74
|
+
mechanical_electrical_services: { "event-mgmt": "Task Calendar", "schedule-task-mgmt": "Extra checks" },
|
|
75
|
+
pest_control_services: { "event-mgmt": "Task Calendar", "schedule-task-mgmt": "Extra checks" },
|
|
76
|
+
pool_maintenance_services: { "event-mgmt": "Task Calendar", "schedule-task-mgmt": "Extra checks" },
|
|
77
|
+
landscaping_services: { "event-mgmt": "Task Calendar", "schedule-task-mgmt": "Extra checks" },
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
/**
|
|
@@ -113,8 +114,18 @@ export function formatResourceLabel(key: string, type?: string): string {
|
|
|
113
114
|
* `.replace(/-/g, " ")` inline; this keeps them from drifting apart, and keeps
|
|
114
115
|
* an acronym from being lower-cased into nonsense.
|
|
115
116
|
*/
|
|
117
|
+
/** Action keys whose words name a screen by its old name. Display only. */
|
|
118
|
+
const ACTION_LABELS: Record<string, string> = {
|
|
119
|
+
"see-all-schedule-tasks": "see all extra checks",
|
|
120
|
+
"add-schedule-task": "add extra check",
|
|
121
|
+
"update-schedule-task": "edit, pause or resume extra check",
|
|
122
|
+
"delete-schedule-task": "delete extra check",
|
|
123
|
+
"see-schedule-task-details": "see extra check details",
|
|
124
|
+
};
|
|
125
|
+
|
|
116
126
|
export function formatActionLabel(action: string): string {
|
|
117
127
|
if (!action) return "";
|
|
128
|
+
if (ACTION_LABELS[action]) return ACTION_LABELS[action];
|
|
118
129
|
return action
|
|
119
130
|
.split(/[-_\s]+/)
|
|
120
131
|
.filter(Boolean)
|
package/utils/schedule-task.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EXTRA
|
|
2
|
+
* EXTRA CHECKS (stored as "schedule tasks") - the rules and the wording, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Named "Extra Checks" (owner 22 Oct: "Extra Visits (Scheduled)" was not
|
|
5
|
+
* understood). It is one more check of an area at a set time, added to staff's
|
|
6
|
+
* Daily Checklist - "Lunch toilet check", "Pump room check", "Pool water check".
|
|
7
|
+
* "Check" is the word every trade already uses and it matches "Daily Checklist".
|
|
8
|
+
* Saved data, API paths and permission keys keep their old names.
|
|
3
9
|
*
|
|
4
10
|
* Mirrors the server (module-hygiene `schedule-task-rules.util.ts`) so the form
|
|
5
11
|
* can say "this will never run" BEFORE the user saves, and the list and the
|
|
@@ -9,6 +15,18 @@
|
|
|
9
15
|
* Pure: no Vue, no imports, so it is unit-tested with `node --test`.
|
|
10
16
|
*/
|
|
11
17
|
|
|
18
|
+
export const FEATURE_NAME = "Extra Checks";
|
|
19
|
+
export const FEATURE_SUBTITLE =
|
|
20
|
+
"Add a check at a set time - once, or on a repeat. At that time it appears on staff's Daily Checklist on their phone.";
|
|
21
|
+
|
|
22
|
+
/** The chip on an extra round of the Daily Checklist: "Extra check: Lunch toilet check · 2:00 pm". */
|
|
23
|
+
export function extraCheckLabel(set: { scheduleTaskTitle?: string | null; scheduleTaskTime?: string | null } | null | undefined): string {
|
|
24
|
+
const title = (set?.scheduleTaskTitle || "").trim();
|
|
25
|
+
const time = fmtTime(set?.scheduleTaskTime);
|
|
26
|
+
if (!title) return "Extra check";
|
|
27
|
+
return `Extra check: ${title}${time ? ` · ${time}` : ""}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
export type TRepeat = "none" | "daily" | "weekdays" | "weekly" | "monthly";
|
|
13
31
|
|
|
14
32
|
export const REPEAT_OPTIONS: { title: string; value: TRepeat }[] = [
|
|
@@ -119,6 +137,29 @@ function occursOn(r: TRule, date: string): boolean {
|
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Does a STORED extra check fall on `date` (YYYY-MM-DD)? For calendars (the
|
|
142
|
+
* Property Management Task Calendar overlay). Paused ones do not; fields a stored
|
|
143
|
+
* row may lack are treated as empty.
|
|
144
|
+
*/
|
|
145
|
+
export function scheduleOccursOn(
|
|
146
|
+
row: { repeat?: string | null; dates?: string[] | null; daysOfWeek?: number[] | null; startDate?: string | null; endDate?: string | null; paused?: boolean | null },
|
|
147
|
+
date: string,
|
|
148
|
+
): boolean {
|
|
149
|
+
if (row.paused) return false;
|
|
150
|
+
return occursOn(
|
|
151
|
+
{
|
|
152
|
+
repeat: (row.repeat || "none") as TRepeat,
|
|
153
|
+
dates: row.dates ?? [],
|
|
154
|
+
daysOfWeek: row.daysOfWeek ?? [],
|
|
155
|
+
startDate: row.startDate ?? "",
|
|
156
|
+
endDate: row.endDate ?? "",
|
|
157
|
+
time: "",
|
|
158
|
+
},
|
|
159
|
+
date,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
/**
|
|
123
164
|
* The next day this would run if saved right now ("YYYY-MM-DD"), or null when it never would.
|
|
124
165
|
* A day whose time has already passed does not count: a task saved after its time waits for its next day.
|
|
@@ -231,36 +272,37 @@ export function buildSummary(
|
|
|
231
272
|
const units = `${unitCount} unit${unitCount === 1 ? "" : "s"}`;
|
|
232
273
|
const next = nextRunDate(f, now);
|
|
233
274
|
const nextText = next ? `${DAYS[weekdayOf(next)]} ${fmtDate(next, year)}` : "never";
|
|
234
|
-
return `Adds 1 extra
|
|
275
|
+
return `Adds 1 extra check to ${areas} (${units} to tick) ${describeWhen(f, year)} — next: ${nextText}.`;
|
|
235
276
|
}
|
|
236
277
|
|
|
237
|
-
/** The state of an extra
|
|
278
|
+
/** The state of an extra check as a whole. "ended" reads "Finished": nothing is left to run. */
|
|
238
279
|
export const STATUS_LABELS: Record<string, string> = { active: "Active", paused: "Paused", ended: "Finished" };
|
|
239
280
|
|
|
240
281
|
/** What each word means, shown on the page so nobody has to guess. Same words as the mobile apps. */
|
|
241
282
|
export const STATUS_HELP: { word: string; meaning: string }[] = [
|
|
242
|
-
{ word: "Active", meaning: "It will
|
|
283
|
+
{ word: "Active", meaning: "It will be added to staff's checklist at its next date and time." },
|
|
243
284
|
{ word: "Paused", meaning: "Stopped for now. Nothing is added until you press Resume." },
|
|
244
285
|
{ word: "Finished", meaning: "Its last date has passed, so nothing more is added. Press \"Extend or repeat\" to keep it going." },
|
|
245
286
|
];
|
|
246
287
|
export const RESULT_HELP: { word: string; meaning: string }[] = [
|
|
247
288
|
{ word: "Not started", meaning: "Nothing is ticked yet." },
|
|
248
289
|
{ word: "In progress", meaning: "Some units are ticked; the rest can still be done today." },
|
|
249
|
-
{ word: "Done", meaning: "Staff ticked every unit of the
|
|
290
|
+
{ word: "Done", meaning: "Staff ticked every unit of the check." },
|
|
250
291
|
{ word: "Needs redo", meaning: "A supervisor sent a unit back to be done again." },
|
|
251
292
|
{ word: "Missed", meaning: "The day ended with units not done." },
|
|
252
293
|
];
|
|
253
294
|
|
|
254
|
-
/** "Done 20 Oct" / "Missed 20 Oct" / "
|
|
255
|
-
export function lastRunLabel(row: { lastRun?: string | null; lastRunStatus?: string | null }): string {
|
|
256
|
-
|
|
295
|
+
/** "Done 20 Oct" / "Missed 20 Oct" / "Not done yet". */
|
|
296
|
+
export function lastRunLabel(row: { lastRun?: string | null; lastRunStatus?: string | null; scheduleStatus?: string | null }): string {
|
|
297
|
+
// Finished rows from before run records existed (20 Oct) may well have run: say we do not know.
|
|
298
|
+
if (!row.lastRun) return row.scheduleStatus === "ended" ? "No record" : "Not added yet";
|
|
257
299
|
const m = /^(\d{4}-\d{2}-\d{2})/.exec(row.lastRun);
|
|
258
300
|
const day = m ? fmtDate(m[1]) : "";
|
|
259
301
|
const word = { done: "Done", missed: "Missed", pending: "In progress" }[row.lastRunStatus || ""] || "Added";
|
|
260
302
|
return `${word} ${day}`.trim();
|
|
261
303
|
}
|
|
262
304
|
|
|
263
|
-
/** Why an extra
|
|
305
|
+
/** Why an extra check says Finished, in one sentence. */
|
|
264
306
|
export function finishedReason(row: Partial<TRule>, nowYear?: string): string {
|
|
265
307
|
if (row.repeat && row.repeat !== "none" && row.endDate) {
|
|
266
308
|
return `Its end date (${fmtDate(row.endDate, nowYear)}) has passed.`;
|