@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.
- package/CHANGELOG.md +16 -4
- package/assets/css/primitives.css +54 -11
- package/assets/css/screens.css +3 -3
- package/assets/css/tokens.css +17 -12
- package/components/AnprCameraBadge.vue +1 -1
- package/components/CameraWall.vue +7 -7
- package/components/CameraWallTile.vue +8 -8
- package/components/Chat/ListCard.vue +1 -1
- package/components/CleaningScheduleMain.vue +56 -20
- package/components/ClientMain.vue +1 -1
- package/components/ClientSubscriptionForm.vue +1 -1
- package/components/DashboardMain.vue +13 -13
- package/components/HidAccessLogDashboard.vue +3 -3
- package/components/HidIntercomManagement.vue +14 -14
- package/components/HidQrCodeConfiguration.vue +3 -3
- package/components/HidReaderManagement.vue +1 -1
- package/components/InventoryPhotoThumbs.vue +1 -1
- package/components/Layout/Header.vue +2 -2
- package/components/Layout/NavigationDrawer.vue +1 -1
- package/components/Nfc/NFCRouteScheduleTimes.vue +1 -1
- package/components/PlateNumberDisplay.vue +1 -1
- package/components/RolePermissionFormCreate.vue +6 -2
- package/components/RolePermissionFormPreviewUpdate.vue +20 -4
- package/components/ScheduleAreaMain.vue +6 -9
- package/components/ScheduleGuide.vue +101 -0
- package/components/ScheduleTaskForm.vue +88 -7
- package/components/ScheduleTaskMain.vue +163 -56
- package/components/StatusChip.vue +1 -1
- package/components/VisitorsReportPreview.vue +2 -2
- package/composables/useCleaningPermission.ts +3 -0
- package/composables/useCommonPermission.ts +12 -0
- package/composables/useLandscapingPermission.ts +2 -0
- package/composables/useMechanicalElectricalPermission.ts +3 -0
- package/composables/usePestControlPermission.ts +2 -0
- package/composables/usePoolMaintenancePermission.ts +2 -0
- package/constants/permissions.ts +3 -1
- package/package.json +1 -1
- package/utils/breadcrumb.ts +7 -0
- package/utils/nav-sections.ts +2 -0
- package/utils/org-modules.ts +3 -0
- package/utils/permission-labels.ts +6 -0
- package/utils/schedule-task.ts +106 -23
- package/utils/security-catalogue.ts +1 -1
- package/utils/trade-catalogue.ts +43 -0
|
@@ -1447,7 +1447,7 @@ function buildPdfHtml() {
|
|
|
1447
1447
|
background: #ffffff;
|
|
1448
1448
|
color: #0a2638;
|
|
1449
1449
|
font-family: Arial, Helvetica, sans-serif;
|
|
1450
|
-
font-size:
|
|
1450
|
+
font-size: 12px;
|
|
1451
1451
|
}
|
|
1452
1452
|
|
|
1453
1453
|
.pdf-summary {
|
|
@@ -1482,7 +1482,7 @@ function buildPdfHtml() {
|
|
|
1482
1482
|
}
|
|
1483
1483
|
|
|
1484
1484
|
.pdf-value {
|
|
1485
|
-
font-size:
|
|
1485
|
+
font-size: 12px;
|
|
1486
1486
|
line-height: 1.35;
|
|
1487
1487
|
}
|
|
1488
1488
|
|
|
@@ -1693,7 +1693,7 @@ watch(selectedReaderId, (value) => {
|
|
|
1693
1693
|
.history-table-card :deep(th) {
|
|
1694
1694
|
background: var(--thead);
|
|
1695
1695
|
color: var(--muted);
|
|
1696
|
-
font-size:
|
|
1696
|
+
font-size: 12px;
|
|
1697
1697
|
font-weight: 800;
|
|
1698
1698
|
text-transform: uppercase;
|
|
1699
1699
|
letter-spacing: 0.04em;
|
|
@@ -2246,7 +2246,7 @@ export default {
|
|
|
2246
2246
|
.field-label {
|
|
2247
2247
|
margin: 12px 0 7px;
|
|
2248
2248
|
color: var(--text2);
|
|
2249
|
-
font-size:
|
|
2249
|
+
font-size: 12px;
|
|
2250
2250
|
font-weight: 500;
|
|
2251
2251
|
}
|
|
2252
2252
|
|
|
@@ -2257,7 +2257,7 @@ export default {
|
|
|
2257
2257
|
.field-hint {
|
|
2258
2258
|
margin: 6px 0 0;
|
|
2259
2259
|
color: var(--text2);
|
|
2260
|
-
font-size:
|
|
2260
|
+
font-size: 12px;
|
|
2261
2261
|
line-height: 1.4;
|
|
2262
2262
|
}
|
|
2263
2263
|
|
|
@@ -2282,7 +2282,7 @@ export default {
|
|
|
2282
2282
|
.sip-label {
|
|
2283
2283
|
margin: 12px 0 6px;
|
|
2284
2284
|
color: var(--text);
|
|
2285
|
-
font-size:
|
|
2285
|
+
font-size: 12px;
|
|
2286
2286
|
font-weight: 500;
|
|
2287
2287
|
line-height: 1.2;
|
|
2288
2288
|
}
|
|
@@ -2320,7 +2320,7 @@ export default {
|
|
|
2320
2320
|
|
|
2321
2321
|
.sip-section-switch small {
|
|
2322
2322
|
color: var(--muted);
|
|
2323
|
-
font-size:
|
|
2323
|
+
font-size: 12px;
|
|
2324
2324
|
line-height: 1.4;
|
|
2325
2325
|
}
|
|
2326
2326
|
|
|
@@ -2462,7 +2462,7 @@ export default {
|
|
|
2462
2462
|
.call-label {
|
|
2463
2463
|
margin: 8px 0 6px;
|
|
2464
2464
|
color: var(--text);
|
|
2465
|
-
font-size:
|
|
2465
|
+
font-size: 12px;
|
|
2466
2466
|
font-weight: 500;
|
|
2467
2467
|
letter-spacing: 0;
|
|
2468
2468
|
}
|
|
@@ -2485,7 +2485,7 @@ export default {
|
|
|
2485
2485
|
|
|
2486
2486
|
.call-radio-list :deep(.v-label) {
|
|
2487
2487
|
color: var(--text2);
|
|
2488
|
-
font-size:
|
|
2488
|
+
font-size: 12px;
|
|
2489
2489
|
letter-spacing: 0;
|
|
2490
2490
|
opacity: 1;
|
|
2491
2491
|
}
|
|
@@ -2501,7 +2501,7 @@ export default {
|
|
|
2501
2501
|
border-radius: 3px;
|
|
2502
2502
|
background: var(--card);
|
|
2503
2503
|
color: var(--text);
|
|
2504
|
-
font-size:
|
|
2504
|
+
font-size: 12px;
|
|
2505
2505
|
letter-spacing: 0;
|
|
2506
2506
|
}
|
|
2507
2507
|
|
|
@@ -2657,7 +2657,7 @@ export default {
|
|
|
2657
2657
|
.web-phone-status {
|
|
2658
2658
|
min-width: 92px;
|
|
2659
2659
|
justify-content: center;
|
|
2660
|
-
font-size:
|
|
2660
|
+
font-size: 12px;
|
|
2661
2661
|
}
|
|
2662
2662
|
|
|
2663
2663
|
.web-phone-status.ready {
|
|
@@ -2749,7 +2749,7 @@ export default {
|
|
|
2749
2749
|
|
|
2750
2750
|
.sip-account-label {
|
|
2751
2751
|
color: var(--muted);
|
|
2752
|
-
font-size:
|
|
2752
|
+
font-size: 12px;
|
|
2753
2753
|
}
|
|
2754
2754
|
|
|
2755
2755
|
.sip-account-extension {
|
|
@@ -2762,7 +2762,7 @@ export default {
|
|
|
2762
2762
|
max-width: 170px;
|
|
2763
2763
|
overflow: hidden;
|
|
2764
2764
|
color: var(--text2);
|
|
2765
|
-
font-size:
|
|
2765
|
+
font-size: 12px;
|
|
2766
2766
|
text-align: right;
|
|
2767
2767
|
text-overflow: ellipsis;
|
|
2768
2768
|
white-space: nowrap;
|
|
@@ -2838,7 +2838,7 @@ export default {
|
|
|
2838
2838
|
max-width: 100%;
|
|
2839
2839
|
overflow: hidden;
|
|
2840
2840
|
color: var(--border);
|
|
2841
|
-
font-size:
|
|
2841
|
+
font-size: 12px;
|
|
2842
2842
|
text-overflow: ellipsis;
|
|
2843
2843
|
white-space: nowrap;
|
|
2844
2844
|
}
|
|
@@ -2895,7 +2895,7 @@ export default {
|
|
|
2895
2895
|
|
|
2896
2896
|
.web-phone-error {
|
|
2897
2897
|
margin-top: 14px;
|
|
2898
|
-
font-size:
|
|
2898
|
+
font-size: 12px;
|
|
2899
2899
|
}
|
|
2900
2900
|
|
|
2901
2901
|
.call-two-col {
|
|
@@ -2922,7 +2922,7 @@ export default {
|
|
|
2922
2922
|
min-height: 38px;
|
|
2923
2923
|
border: 1px dashed var(--border);
|
|
2924
2924
|
color: var(--text2);
|
|
2925
|
-
font-size:
|
|
2925
|
+
font-size: 12px;
|
|
2926
2926
|
letter-spacing: 0;
|
|
2927
2927
|
}
|
|
2928
2928
|
|
|
@@ -2952,7 +2952,7 @@ export default {
|
|
|
2952
2952
|
min-height: 32px;
|
|
2953
2953
|
margin: 0 0 10px;
|
|
2954
2954
|
color: var(--text2);
|
|
2955
|
-
font-size:
|
|
2955
|
+
font-size: 12px;
|
|
2956
2956
|
}
|
|
2957
2957
|
|
|
2958
2958
|
.call-file-name {
|
|
@@ -644,7 +644,7 @@ function showMessage(text: string, color: string) {
|
|
|
644
644
|
.pass-subtext {
|
|
645
645
|
min-height: 18px;
|
|
646
646
|
color: #6b7280;
|
|
647
|
-
font-size:
|
|
647
|
+
font-size: 12px;
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
.qr-grid {
|
|
@@ -667,7 +667,7 @@ function showMessage(text: string, color: string) {
|
|
|
667
667
|
background-size: 10px 10px;
|
|
668
668
|
background-position: 0 0, 0 5px, 5px -5px, -5px 0;
|
|
669
669
|
color: #6b7280;
|
|
670
|
-
font-size:
|
|
670
|
+
font-size: 12px;
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
.qr-placeholder.small {
|
|
@@ -685,7 +685,7 @@ function showMessage(text: string, color: string) {
|
|
|
685
685
|
display: grid;
|
|
686
686
|
gap: 4px;
|
|
687
687
|
color: #4b5563;
|
|
688
|
-
font-size:
|
|
688
|
+
font-size: 12px;
|
|
689
689
|
}
|
|
690
690
|
|
|
691
691
|
.pass-footer strong {
|
|
@@ -307,7 +307,7 @@ const { getNameInitials } = useUtils();
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
.app-profile-menu__name {
|
|
310
|
-
font-size:
|
|
310
|
+
font-size: 14.5px;
|
|
311
311
|
font-weight: 800;
|
|
312
312
|
line-height: 1.3;
|
|
313
313
|
color: var(--text);
|
|
@@ -317,7 +317,7 @@ const { getNameInitials } = useUtils();
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
.app-profile-menu__site {
|
|
320
|
-
font-size:
|
|
320
|
+
font-size: 13px;
|
|
321
321
|
font-weight: 600;
|
|
322
322
|
line-height: 1.3;
|
|
323
323
|
color: var(--muted);
|
|
@@ -284,6 +284,7 @@ import useUtils from "../composables/useUtils";
|
|
|
284
284
|
import useSite from "../composables/useSite";
|
|
285
285
|
import { readApiError } from "../utils/subscription-form";
|
|
286
286
|
import { withSecurityMarker } from "../utils/security-catalogue";
|
|
287
|
+
import { isTradeCatalogue, withTradeMarker } from "../utils/trade-catalogue";
|
|
287
288
|
import { applyRolePreset, ROLE_PRESETS, type TRolePreset } from "../utils/inventory-presets";
|
|
288
289
|
|
|
289
290
|
/** Shown after "Couldn't save:" when the server gave no reason (network, timeout). */
|
|
@@ -534,10 +535,13 @@ async function submit() {
|
|
|
534
535
|
try {
|
|
535
536
|
await createRole({
|
|
536
537
|
name: name.value,
|
|
537
|
-
// Security catalogue
|
|
538
|
+
// Security catalogue (it carries `offline-data`) / trade catalogue (it
|
|
539
|
+
// offers See calendar): stamp that catalogue's v2 marker.
|
|
538
540
|
permissions: "offline-data" in (props.permissions || {})
|
|
539
541
|
? withSecurityMarker(permState.value.selected)
|
|
540
|
-
:
|
|
542
|
+
: isTradeCatalogue(props.permissions)
|
|
543
|
+
? withTradeMarker(permState.value.selected)
|
|
544
|
+
: permState.value.selected,
|
|
541
545
|
type: props.type,
|
|
542
546
|
org: props.org,
|
|
543
547
|
site: props.siteId,
|
|
@@ -277,6 +277,11 @@ import {
|
|
|
277
277
|
securityV2PreTicks,
|
|
278
278
|
withSecurityMarker,
|
|
279
279
|
} from "../utils/security-catalogue";
|
|
280
|
+
import {
|
|
281
|
+
isTradeCatalogue as isTradeCatalogueOf,
|
|
282
|
+
tradeV2PreTicks,
|
|
283
|
+
withTradeMarker,
|
|
284
|
+
} from "../utils/trade-catalogue";
|
|
280
285
|
import {
|
|
281
286
|
holdsPermission,
|
|
282
287
|
withoutPermission,
|
|
@@ -651,12 +656,21 @@ const isSecurityCatalogue = computed(
|
|
|
651
656
|
() => "offline-data" in (props.permissions as Record<string, unknown>)
|
|
652
657
|
);
|
|
653
658
|
|
|
654
|
-
|
|
659
|
+
/** Trade catalogue only: the five trade trees offer Task Calendar -> See calendar. */
|
|
660
|
+
const isTradeCatalogue = computed(() =>
|
|
661
|
+
isTradeCatalogueOf(props.permissions as Record<string, any>)
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
// Opening an UNMARKED security (or trade) role for edit: pre-tick the new boxes the role
|
|
655
665
|
// already effectively has (legacy fallback), so an unrelated save never strips
|
|
656
666
|
// access. Cancel restores the original list.
|
|
657
667
|
watch(edit, (on) => {
|
|
658
|
-
if (!on
|
|
659
|
-
const add =
|
|
668
|
+
if (!on) return;
|
|
669
|
+
const add = isSecurityCatalogue.value
|
|
670
|
+
? securityV2PreTicks(definedModel.value ?? [])
|
|
671
|
+
: isTradeCatalogue.value
|
|
672
|
+
? tradeV2PreTicks(definedModel.value ?? [])
|
|
673
|
+
: [];
|
|
660
674
|
if (add.length) definedModel.value = [...definedModel.value, ...add];
|
|
661
675
|
});
|
|
662
676
|
|
|
@@ -735,7 +749,9 @@ async function submit() {
|
|
|
735
749
|
props.id,
|
|
736
750
|
isSecurityCatalogue.value
|
|
737
751
|
? withSecurityMarker(definedModel.value)
|
|
738
|
-
:
|
|
752
|
+
: isTradeCatalogue.value
|
|
753
|
+
? withTradeMarker(definedModel.value)
|
|
754
|
+
: definedModel.value
|
|
739
755
|
);
|
|
740
756
|
emit("success");
|
|
741
757
|
} catch (error: any) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
:site="site"
|
|
7
7
|
:service-type="serviceType"
|
|
8
8
|
:schedule-route="scheduleRoute"
|
|
9
|
-
text="These are the areas on this day's checklist.
|
|
9
|
+
text="These are the areas on this day's checklist. Click an area to see each unit, tick it as done, or send it back to be redone."
|
|
10
10
|
/>
|
|
11
11
|
<TableMain
|
|
12
12
|
:title="pageTitle"
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
|
|
143
143
|
<script setup lang="ts">
|
|
144
144
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
145
|
-
import {
|
|
145
|
+
import { dailyChecklistTitle, dailyStatusLabel } from "../utils/schedule-task";
|
|
146
146
|
import useUtils from "../composables/useUtils";
|
|
147
147
|
|
|
148
148
|
const props = defineProps({
|
|
@@ -157,11 +157,8 @@ const props = defineProps({
|
|
|
157
157
|
canManageScheduleTasks: { type: Boolean, default: false },
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
// "Daily
|
|
161
|
-
const pageTitle =
|
|
162
|
-
/ Schedule$/,
|
|
163
|
-
"",
|
|
164
|
-
)}`;
|
|
160
|
+
// "Daily Checklist – Cleaning" / "– Landscape" / ... (same title as the day list)
|
|
161
|
+
const pageTitle = dailyChecklistTitle(props.serviceType);
|
|
165
162
|
|
|
166
163
|
const submitting = ref(false);
|
|
167
164
|
const message = ref("");
|
|
@@ -198,9 +195,9 @@ const items = ref<Array<Record<string, any>>>([]);
|
|
|
198
195
|
const statusFilter = ref<TScheduleAreaStatus>("all");
|
|
199
196
|
const statusOptions = [
|
|
200
197
|
{ title: "All", value: "all" },
|
|
201
|
-
{ title: "
|
|
198
|
+
{ title: "Not started", value: "open" },
|
|
202
199
|
{ title: "In progress", value: "ongoing" },
|
|
203
|
-
{ title: "
|
|
200
|
+
{ title: "Done", value: "completed" },
|
|
204
201
|
];
|
|
205
202
|
const areaTypeFilter = ref<TAreaType>("all");
|
|
206
203
|
const typeOptions = [
|
|
@@ -9,6 +9,29 @@
|
|
|
9
9
|
<template>
|
|
10
10
|
<div class="schedule-guide">
|
|
11
11
|
<p v-if="text" class="schedule-guide__text">{{ text }}</p>
|
|
12
|
+
<!--
|
|
13
|
+
"How it works": the page's own short manual (owner 21 Oct - a supervisor
|
|
14
|
+
who was never trained must understand the screen from the screen). Open
|
|
15
|
+
by default until the viewer closes it; the choice is remembered on this
|
|
16
|
+
browser only.
|
|
17
|
+
-->
|
|
18
|
+
<v-expansion-panels
|
|
19
|
+
v-if="$slots.default && howTitle"
|
|
20
|
+
v-model="howPanel"
|
|
21
|
+
class="schedule-guide__how"
|
|
22
|
+
variant="accordion"
|
|
23
|
+
flat
|
|
24
|
+
>
|
|
25
|
+
<v-expansion-panel value="how">
|
|
26
|
+
<v-expansion-panel-title class="schedule-guide__how-title">
|
|
27
|
+
<v-icon size="20" class="mr-2">mdi-help-circle-outline</v-icon>
|
|
28
|
+
{{ howTitle }}
|
|
29
|
+
</v-expansion-panel-title>
|
|
30
|
+
<v-expansion-panel-text class="schedule-guide__how-body">
|
|
31
|
+
<slot />
|
|
32
|
+
</v-expansion-panel-text>
|
|
33
|
+
</v-expansion-panel>
|
|
34
|
+
</v-expansion-panels>
|
|
12
35
|
<v-alert
|
|
13
36
|
v-if="problems.length"
|
|
14
37
|
type="warning"
|
|
@@ -44,6 +67,27 @@ const props = defineProps({
|
|
|
44
67
|
serviceType: { type: String, default: "" },
|
|
45
68
|
scheduleRoute: { type: String, default: "" },
|
|
46
69
|
text: { type: String, default: "" },
|
|
70
|
+
/** Heading of the "How it works" panel; the panel shows only with this and a default slot. */
|
|
71
|
+
howTitle: { type: String, default: "" },
|
|
72
|
+
/** Remembers open/closed per panel on this browser. */
|
|
73
|
+
howKey: { type: String, default: "" },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const HOW_STORE = () => `schedule-guide-how:${props.howKey || props.howTitle}`;
|
|
77
|
+
const howPanel = ref<string | undefined>("how");
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
try {
|
|
80
|
+
if (localStorage.getItem(HOW_STORE()) === "closed") howPanel.value = undefined;
|
|
81
|
+
} catch {
|
|
82
|
+
// storage blocked: stay open
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
watch(howPanel, (v) => {
|
|
86
|
+
try {
|
|
87
|
+
localStorage.setItem(HOW_STORE(), v ? "open" : "closed");
|
|
88
|
+
} catch {
|
|
89
|
+
// storage blocked: nothing to remember
|
|
90
|
+
}
|
|
47
91
|
});
|
|
48
92
|
|
|
49
93
|
const { getAreas, getAreaById } = useAreas();
|
|
@@ -125,8 +169,65 @@ watch(() => [props.site, props.serviceType], check);
|
|
|
125
169
|
font-size: var(--fs-cell);
|
|
126
170
|
color: var(--text2);
|
|
127
171
|
}
|
|
172
|
+
.schedule-guide__how {
|
|
173
|
+
margin-bottom: 12px;
|
|
174
|
+
border: 1px solid var(--border);
|
|
175
|
+
border-radius: var(--r-inner);
|
|
176
|
+
overflow: hidden;
|
|
177
|
+
}
|
|
178
|
+
.schedule-guide__how-title {
|
|
179
|
+
font-family: var(--font-sans);
|
|
180
|
+
font-size: var(--fs-card-title);
|
|
181
|
+
font-weight: 700;
|
|
182
|
+
}
|
|
183
|
+
.schedule-guide__how-body {
|
|
184
|
+
font-family: var(--font-sans);
|
|
185
|
+
font-size: var(--fs-cell);
|
|
186
|
+
line-height: 1.55;
|
|
187
|
+
}
|
|
128
188
|
.schedule-guide__list {
|
|
129
189
|
margin: 0;
|
|
130
190
|
padding-left: 18px;
|
|
131
191
|
}
|
|
132
192
|
</style>
|
|
193
|
+
|
|
194
|
+
<!-- Shared by the "How it works" content the schedule screens put in the slot. -->
|
|
195
|
+
<style>
|
|
196
|
+
.sg-steps {
|
|
197
|
+
margin: 0 0 12px;
|
|
198
|
+
padding-left: 22px;
|
|
199
|
+
}
|
|
200
|
+
.sg-steps li {
|
|
201
|
+
margin-bottom: 6px;
|
|
202
|
+
}
|
|
203
|
+
.sg-example {
|
|
204
|
+
margin: 0 0 12px;
|
|
205
|
+
padding: 12px 14px;
|
|
206
|
+
border-left: 3px solid rgb(var(--v-theme-primary));
|
|
207
|
+
border-radius: var(--r-inner-sm);
|
|
208
|
+
background: var(--hover);
|
|
209
|
+
}
|
|
210
|
+
.sg-legend {
|
|
211
|
+
display: grid;
|
|
212
|
+
grid-template-columns: max-content 1fr;
|
|
213
|
+
gap: 8px 12px;
|
|
214
|
+
align-items: center;
|
|
215
|
+
margin: 0;
|
|
216
|
+
}
|
|
217
|
+
.sg-legend dd {
|
|
218
|
+
margin: 0;
|
|
219
|
+
}
|
|
220
|
+
.sg-h {
|
|
221
|
+
margin: 4px 0 8px;
|
|
222
|
+
font-weight: 700;
|
|
223
|
+
}
|
|
224
|
+
@media (max-width: 599.98px) {
|
|
225
|
+
.sg-legend {
|
|
226
|
+
grid-template-columns: 1fr;
|
|
227
|
+
gap: 2px 0;
|
|
228
|
+
}
|
|
229
|
+
.sg-legend dd {
|
|
230
|
+
margin-bottom: 8px;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
@@ -59,8 +59,10 @@
|
|
|
59
59
|
variant="outlined"
|
|
60
60
|
density="comfortable"
|
|
61
61
|
class="filter-field mb-4"
|
|
62
|
-
placeholder="For example:
|
|
62
|
+
placeholder="For example: Lunch toilet check"
|
|
63
63
|
:error-messages="fieldError('title')"
|
|
64
|
+
hint="Staff see this name on their phone, on the extra round."
|
|
65
|
+
persistent-hint
|
|
64
66
|
hide-details="auto"
|
|
65
67
|
/>
|
|
66
68
|
<InputLabel for="description" title="Instructions" />
|
|
@@ -72,6 +74,8 @@
|
|
|
72
74
|
class="filter-field"
|
|
73
75
|
rows="3"
|
|
74
76
|
placeholder="Optional. Anything staff should know for this visit."
|
|
77
|
+
hint="Optional. For example: refill soap and tissue as well."
|
|
78
|
+
persistent-hint
|
|
75
79
|
hide-details="auto"
|
|
76
80
|
/>
|
|
77
81
|
</div>
|
|
@@ -81,8 +85,9 @@
|
|
|
81
85
|
<v-stepper-window-item :value="2">
|
|
82
86
|
<div class="st-step">
|
|
83
87
|
<p class="st-step__help">
|
|
84
|
-
Pick the areas that get the extra visit. Every unit in
|
|
85
|
-
area
|
|
88
|
+
Pick the areas that get the extra visit. Every unit in a
|
|
89
|
+
picked area gets one more tick box on the Daily Checklist
|
|
90
|
+
for this visit.
|
|
86
91
|
</p>
|
|
87
92
|
<InputLabel for="selected-areas" title="Areas" required />
|
|
88
93
|
<v-select
|
|
@@ -165,7 +170,8 @@
|
|
|
165
170
|
<div class="st-step">
|
|
166
171
|
<p class="st-step__help">
|
|
167
172
|
Choose the time, and whether it happens once or repeats.
|
|
168
|
-
Times are Singapore time.
|
|
173
|
+
Times are Singapore time. The extra round appears on staff's
|
|
174
|
+
checklist at this time.
|
|
169
175
|
</p>
|
|
170
176
|
<v-row dense>
|
|
171
177
|
<v-col cols="12" sm="6">
|
|
@@ -193,6 +199,8 @@
|
|
|
193
199
|
variant="outlined"
|
|
194
200
|
density="comfortable"
|
|
195
201
|
class="filter-field"
|
|
202
|
+
:hint="REPEAT_HINTS[repeat]"
|
|
203
|
+
persistent-hint
|
|
196
204
|
hide-details="auto"
|
|
197
205
|
/>
|
|
198
206
|
</v-col>
|
|
@@ -200,7 +208,7 @@
|
|
|
200
208
|
|
|
201
209
|
<!-- Once: pick the day(s) -->
|
|
202
210
|
<div v-if="repeat === 'none'" class="mt-4">
|
|
203
|
-
<InputLabel title="Day" required />
|
|
211
|
+
<InputLabel title="Day(s)" required />
|
|
204
212
|
<div class="st-panel">
|
|
205
213
|
<v-date-picker
|
|
206
214
|
v-model="pickerDates"
|
|
@@ -213,7 +221,8 @@
|
|
|
213
221
|
class="ma-0"
|
|
214
222
|
/>
|
|
215
223
|
<div class="st-step__help mt-2">
|
|
216
|
-
{{ selectedDates.length }} day(s) selected.
|
|
224
|
+
{{ selectedDates.length }} day(s) selected. Click a day
|
|
225
|
+
again to remove it.
|
|
217
226
|
</div>
|
|
218
227
|
</div>
|
|
219
228
|
<div v-if="fieldError('dates').length" class="st-error">
|
|
@@ -304,6 +313,32 @@
|
|
|
304
313
|
<div class="st-summary__label">This will:</div>
|
|
305
314
|
<div class="st-summary__text">{{ summary }}</div>
|
|
306
315
|
</div>
|
|
316
|
+
|
|
317
|
+
<!-- What staff will see: the same card shape as the mobile checklist. -->
|
|
318
|
+
<div class="st-summary__label mt-4">
|
|
319
|
+
What staff will see on their phone:
|
|
320
|
+
</div>
|
|
321
|
+
<div class="st-preview">
|
|
322
|
+
<div class="st-preview__head">
|
|
323
|
+
<v-icon size="18" class="mr-1">mdi-clock-outline</v-icon>
|
|
324
|
+
{{ previewTime }} · Extra visit
|
|
325
|
+
</div>
|
|
326
|
+
<div class="st-preview__title">
|
|
327
|
+
{{ subject.trim() || "(your title)" }}
|
|
328
|
+
</div>
|
|
329
|
+
<div v-if="description.trim()" class="st-preview__desc">
|
|
330
|
+
{{ description.trim() }}
|
|
331
|
+
</div>
|
|
332
|
+
<div
|
|
333
|
+
v-for="area in selectedAreas"
|
|
334
|
+
:key="areaKey(area)"
|
|
335
|
+
class="st-preview__row"
|
|
336
|
+
>
|
|
337
|
+
<v-icon size="18" class="mr-1">mdi-checkbox-blank-outline</v-icon>
|
|
338
|
+
{{ area.name }} -
|
|
339
|
+
{{ unitCountOf(area) }} unit{{ unitCountOf(area) === 1 ? "" : "s" }} to tick
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
307
342
|
<div v-if="fieldError('never').length" class="st-error mt-2">
|
|
308
343
|
{{ fieldError("never")[0] }}
|
|
309
344
|
</div>
|
|
@@ -372,6 +407,7 @@ import {
|
|
|
372
407
|
REPEAT_OPTIONS,
|
|
373
408
|
WEEK_DAYS,
|
|
374
409
|
buildSummary,
|
|
410
|
+
fmtTime,
|
|
375
411
|
sgtToday,
|
|
376
412
|
validateSchedule,
|
|
377
413
|
type TRepeat,
|
|
@@ -395,6 +431,11 @@ const props = defineProps({
|
|
|
395
431
|
type: String,
|
|
396
432
|
default: "",
|
|
397
433
|
},
|
|
434
|
+
/** The step the dialog opens on (3 = When, used by "Extend or repeat"). */
|
|
435
|
+
startStep: {
|
|
436
|
+
type: Number,
|
|
437
|
+
default: 1,
|
|
438
|
+
},
|
|
398
439
|
});
|
|
399
440
|
|
|
400
441
|
const show = defineModel("show", { type: Boolean, default: false });
|
|
@@ -476,6 +517,15 @@ const formState = computed(() => ({
|
|
|
476
517
|
const errors = computed<TScheduleErrors>(() => validateSchedule(formState.value));
|
|
477
518
|
const summary = computed(() => buildSummary(formState.value, totalUnits.value));
|
|
478
519
|
|
|
520
|
+
const REPEAT_HINTS: Record<TRepeat, string> = {
|
|
521
|
+
none: "Only on the day(s) you pick below.",
|
|
522
|
+
daily: "Every day, weekends included.",
|
|
523
|
+
weekdays: "Monday to Friday.",
|
|
524
|
+
weekly: "On the weekdays you pick below.",
|
|
525
|
+
monthly: "Once a month, on the start date's day.",
|
|
526
|
+
};
|
|
527
|
+
const previewTime = computed(() => fmtTime(scheduleTime.value) || "--");
|
|
528
|
+
|
|
479
529
|
const STEP_FIELDS: Record<number, (keyof TScheduleErrors)[]> = {
|
|
480
530
|
1: ["title"],
|
|
481
531
|
2: ["areas"],
|
|
@@ -694,6 +744,7 @@ const close = () => {
|
|
|
694
744
|
watch(
|
|
695
745
|
[show, () => props.mode, () => props.taskData],
|
|
696
746
|
async ([isOpen, mode]) => {
|
|
747
|
+
if (isOpen) step.value = props.startStep || 1;
|
|
697
748
|
if (isOpen && mode === "edit") {
|
|
698
749
|
await loadTaskData();
|
|
699
750
|
} else if (!isOpen) {
|
|
@@ -710,7 +761,7 @@ watch(
|
|
|
710
761
|
}
|
|
711
762
|
.st-step__help {
|
|
712
763
|
margin: 0 0 12px;
|
|
713
|
-
font-size: var(--fs-
|
|
764
|
+
font-size: var(--fs-cell);
|
|
714
765
|
font-weight: 600;
|
|
715
766
|
color: var(--text2);
|
|
716
767
|
}
|
|
@@ -748,6 +799,36 @@ watch(
|
|
|
748
799
|
font-size: 15px;
|
|
749
800
|
font-weight: 600;
|
|
750
801
|
}
|
|
802
|
+
.st-preview {
|
|
803
|
+
margin-top: 6px;
|
|
804
|
+
padding: 14px 16px;
|
|
805
|
+
border: 1px solid var(--border);
|
|
806
|
+
border-left: 4px solid rgb(var(--v-theme-primary));
|
|
807
|
+
border-radius: var(--r-inner);
|
|
808
|
+
background: var(--card);
|
|
809
|
+
font-size: var(--fs-cell);
|
|
810
|
+
}
|
|
811
|
+
.st-preview__head {
|
|
812
|
+
display: flex;
|
|
813
|
+
align-items: center;
|
|
814
|
+
font-size: var(--fs-chip-lg);
|
|
815
|
+
font-weight: 700;
|
|
816
|
+
color: var(--text2);
|
|
817
|
+
}
|
|
818
|
+
.st-preview__title {
|
|
819
|
+
margin: 4px 0;
|
|
820
|
+
font-size: var(--fs-card-title);
|
|
821
|
+
font-weight: 800;
|
|
822
|
+
}
|
|
823
|
+
.st-preview__desc {
|
|
824
|
+
margin-bottom: 6px;
|
|
825
|
+
color: var(--text2);
|
|
826
|
+
}
|
|
827
|
+
.st-preview__row {
|
|
828
|
+
display: flex;
|
|
829
|
+
align-items: center;
|
|
830
|
+
padding: 4px 0;
|
|
831
|
+
}
|
|
751
832
|
.st-stepper,
|
|
752
833
|
.st-stepper__header {
|
|
753
834
|
background: transparent;
|