@7365admin1/layer-common 4.97.1-staging.588 → 4.97.1-staging.590
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/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/Nfc/NFCPatrolReportMain.vue +4 -2
- package/components/Nfc/NFCPatrolRouteForm.vue +1 -1
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +4 -4
- package/components/Nfc/PatrolReport/ReportFilters.vue +23 -1
- package/components/ScheduleAreaMain.vue +5 -1
- package/components/UnitMain.vue +3 -1
- package/composables/useNFCPatrolMonthlyReport.ts +5 -3
- package/composables/useNFCPatrolReportFilters.ts +1 -0
- package/package.json +1 -1
- package/types/nfc-patrol-report.ts +2 -0
- package/utils/nfc-patrol-report.ts +15 -0
|
@@ -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>
|
|
@@ -100,6 +100,7 @@ import {
|
|
|
100
100
|
reportFileName,
|
|
101
101
|
type CsvSection,
|
|
102
102
|
} from "../../utils/report-export";
|
|
103
|
+
import { reportYear } from "../../utils/nfc-patrol-report";
|
|
103
104
|
|
|
104
105
|
import PatrolReportTab from "./PatrolReport/PatrolReportTab.vue";
|
|
105
106
|
import DailyReportTab from "./PatrolReport/DailyReportTab.vue";
|
|
@@ -137,6 +138,7 @@ function updateFilters(value: NFCPatrolReportFilters) {
|
|
|
137
138
|
filters.route = value.route;
|
|
138
139
|
filters.timeRange = value.timeRange;
|
|
139
140
|
filters.date = value.date;
|
|
141
|
+
filters.year = value.year;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
const { exportReportPdf } = useReportPdf();
|
|
@@ -147,10 +149,10 @@ function reportMeta(report: HTMLElement, extension: "pdf" | "csv"): ReportPdfMet
|
|
|
147
149
|
const route = options.value.routes.find(({ value }) => value === filters.route)?.title;
|
|
148
150
|
const text = (selector: string) => report.querySelector(selector)?.textContent?.trim() || undefined;
|
|
149
151
|
|
|
150
|
-
// The yearly table
|
|
152
|
+
// The yearly table covers the year picked on the Monthly Report.
|
|
151
153
|
const period =
|
|
152
154
|
activeTab.value === "monthly"
|
|
153
|
-
? { label: "Year", value:
|
|
155
|
+
? { label: "Year", value: reportYear(filters.year) }
|
|
154
156
|
: { label: "Date", value: formatReportDate(filters.date) };
|
|
155
157
|
const details = [{ label: "Route", value: route ?? "-" }, period];
|
|
156
158
|
if (activeTab.value === "patrol" && filters.timeRange) {
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
{{
|
|
112
112
|
form.timed
|
|
113
113
|
? "Patrols start at the route's start times, within the tolerance."
|
|
114
|
-
: "No time window:
|
|
114
|
+
: "No time window: patrol at any time of day, on the selected Days only. Use Select all for every day."
|
|
115
115
|
}}
|
|
116
116
|
</div>
|
|
117
117
|
</v-col>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
:options="options"
|
|
6
6
|
:show-time-filter="false"
|
|
7
7
|
:show-date-filter="false"
|
|
8
|
+
show-year-filter
|
|
8
9
|
@update:model-value="$emit('update:filters', $event)"
|
|
9
10
|
@export="(type) => $emit('export', type, csvSections())"
|
|
10
11
|
/>
|
|
@@ -53,7 +54,7 @@ import type {
|
|
|
53
54
|
} from "../../../types/nfc-patrol-report";
|
|
54
55
|
|
|
55
56
|
import { useNFCPatrolMonthlyReport } from "../../../composables/useNFCPatrolMonthlyReport";
|
|
56
|
-
import { monthlyReportCsv } from "../../../utils/nfc-patrol-report";
|
|
57
|
+
import { monthlyReportCsv, reportYear } from "../../../utils/nfc-patrol-report";
|
|
57
58
|
import PatrolMonthDialog from "./PatrolMonthDialog.vue";
|
|
58
59
|
import type { CsvSection } from "../../../utils/report-export";
|
|
59
60
|
|
|
@@ -84,8 +85,7 @@ const monthTitle = ref("");
|
|
|
84
85
|
const monthRows = shallowRef<NFCPatrolReportSummaryRow[]>([]);
|
|
85
86
|
|
|
86
87
|
function openMonth(monthIndex: number) {
|
|
87
|
-
|
|
88
|
-
monthTitle.value = `${report.value?.rows[monthIndex]?.month ?? ""} ${new Date().getFullYear()}`;
|
|
88
|
+
monthTitle.value = `${report.value?.rows[monthIndex]?.month ?? ""} ${reportYear(props.filters.year)}`;
|
|
89
89
|
monthRows.value = monthRuns(monthIndex);
|
|
90
90
|
monthOpen.value = true;
|
|
91
91
|
}
|
|
@@ -95,7 +95,7 @@ function csvSections() {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
watch(
|
|
98
|
-
() => [props.filters.route, props.active],
|
|
98
|
+
() => [props.filters.route, props.filters.year, props.active],
|
|
99
99
|
() => {
|
|
100
100
|
if (props.active) {
|
|
101
101
|
void fetchReport();
|
|
@@ -63,6 +63,25 @@
|
|
|
63
63
|
/>
|
|
64
64
|
</v-col>
|
|
65
65
|
|
|
66
|
+
<!-- Monthly Report: which year. Each month's row then opens that month. -->
|
|
67
|
+
<v-col
|
|
68
|
+
v-if="showYearFilter"
|
|
69
|
+
cols="12"
|
|
70
|
+
md="2"
|
|
71
|
+
class="px-md-2 mb-2 mb-md-0"
|
|
72
|
+
>
|
|
73
|
+
<v-select
|
|
74
|
+
:model-value="reportYear(modelValue.year)"
|
|
75
|
+
:items="reportYears()"
|
|
76
|
+
label="Year"
|
|
77
|
+
class="filter-field"
|
|
78
|
+
variant="outlined"
|
|
79
|
+
density="comfortable"
|
|
80
|
+
hide-details
|
|
81
|
+
@update:model-value="updateFilter('year', $event)"
|
|
82
|
+
/>
|
|
83
|
+
</v-col>
|
|
84
|
+
|
|
66
85
|
<v-spacer />
|
|
67
86
|
|
|
68
87
|
<v-col cols="12" md="2" class="pl-md-2">
|
|
@@ -99,6 +118,7 @@ import type {
|
|
|
99
118
|
NFCPatrolReportFilters,
|
|
100
119
|
} from "../../../types/nfc-patrol-report";
|
|
101
120
|
import ReportDatePicker from "./ReportDatePicker.vue";
|
|
121
|
+
import { reportYear, reportYears } from "../../../utils/nfc-patrol-report";
|
|
102
122
|
|
|
103
123
|
const props = withDefaults(
|
|
104
124
|
defineProps<{
|
|
@@ -106,12 +126,14 @@ const props = withDefaults(
|
|
|
106
126
|
options: NFCPatrolReportFilterOptions;
|
|
107
127
|
showTimeFilter?: boolean;
|
|
108
128
|
showDateFilter?: boolean;
|
|
129
|
+
showYearFilter?: boolean;
|
|
109
130
|
}>(),
|
|
110
131
|
{
|
|
111
132
|
showTimeFilter: true,
|
|
112
133
|
showDateFilter: true,
|
|
134
|
+
showYearFilter: false,
|
|
113
135
|
},
|
|
114
|
-
)
|
|
136
|
+
);
|
|
115
137
|
|
|
116
138
|
const emit = defineEmits<{
|
|
117
139
|
"update:modelValue": [value: NFCPatrolReportFilters];
|
|
@@ -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
|
|
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)"
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
mapLogsToMonthlyRows,
|
|
12
12
|
mapLogsToMonthRuns,
|
|
13
13
|
monthIndexOf,
|
|
14
|
+
reportYear,
|
|
14
15
|
} from "../utils/nfc-patrol-report";
|
|
15
16
|
|
|
16
17
|
interface NFCPatrolLogListResponse {
|
|
@@ -53,7 +54,7 @@ export function useNFCPatrolMonthlyReport(
|
|
|
53
54
|
*/
|
|
54
55
|
const failed = ref(false);
|
|
55
56
|
|
|
56
|
-
// The year's patrol logs, kept so a month's row can open its list without
|
|
57
|
+
// The picked year's patrol logs, kept so a month's row can open its list without
|
|
57
58
|
// asking the server again. Not deep-reactive: nothing edits them.
|
|
58
59
|
const logs = shallowRef<Record<string, any>[]>([]);
|
|
59
60
|
let siteTimezone: string | undefined;
|
|
@@ -70,7 +71,7 @@ export function useNFCPatrolMonthlyReport(
|
|
|
70
71
|
loading.value = true;
|
|
71
72
|
notFound.value = false;
|
|
72
73
|
failed.value = false;
|
|
73
|
-
const
|
|
74
|
+
const year = reportYear(filters.year);
|
|
74
75
|
try {
|
|
75
76
|
// The whole year, not page one of it. `collectAllPages` reads the page
|
|
76
77
|
// count out of the first reply and walks the rest.
|
|
@@ -81,7 +82,8 @@ export function useNFCPatrolMonthlyReport(
|
|
|
81
82
|
limit: 100,
|
|
82
83
|
site,
|
|
83
84
|
routeId: filters.route,
|
|
84
|
-
|
|
85
|
+
// "YYYY" + type "month": the server returns that whole year.
|
|
86
|
+
date: year,
|
|
85
87
|
type: "month",
|
|
86
88
|
}),
|
|
87
89
|
),
|
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.590",
|
|
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.",
|
|
@@ -102,6 +102,21 @@ export function isMissedCheckpoint(
|
|
|
102
102
|
return Number.isFinite(end) && now.getTime() > end;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** The Monthly Report's year: the one picked, or the current year. */
|
|
106
|
+
export function reportYear(year?: string, now = new Date()) {
|
|
107
|
+
return /^\d{4}$/.test(year ?? "") ? (year as string) : String(now.getFullYear());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The years the Monthly Report offers, newest first: this year and the five
|
|
112
|
+
* before it.
|
|
113
|
+
*/
|
|
114
|
+
// ponytail: fixed 6-year window; derive from the oldest log if sites need more.
|
|
115
|
+
export function reportYears(now = new Date(), count = 6) {
|
|
116
|
+
const current = now.getFullYear();
|
|
117
|
+
return Array.from({ length: count }, (_, i) => String(current - i));
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
/** The month (0-11) a patrol log counts under; NaN for a date that is not one. */
|
|
106
121
|
export function monthIndexOf(log: Record<string, any>) {
|
|
107
122
|
return new Date(log?.date).getMonth();
|