@7365admin1/layer-common 4.62.0 → 4.63.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 +10 -4
- package/components/AreaChecklistHistoryMain.vue +5 -1
- package/components/AttendanceMain.vue +2 -1
- package/components/CleaningScheduleMain.vue +6 -2
- package/components/DashboardMain.vue +10 -7
- package/components/InventoryMain.vue +5 -1
- package/components/InventoryReportsTab.vue +8 -4
- package/components/ManageChecklistMain.vue +5 -2
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +3 -1
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +3 -1
- package/components/ScheduleAreaMain.vue +5 -1
- package/composables/useInventory.ts +1 -1
- package/composables/useWorkerTerm.ts +10 -0
- package/package.json +1 -1
- package/utils/worker-terms.ts +80 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.63.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cd0a5aa: Worker nouns follow the account type: one term map (`utils/worker-terms.ts`, `useWorkerTerm()`), keyed by the app's `appSlug`. Cleaning says Cleaners, security Security officers, pest control Pest technicians, M&E Technicians, landscape Gardeners, pool Pool attendants, property management Staff / Service staff, anything else Staff. Applied to Inventory (tab, report, usage column, export file name and sheet headers via `appSlug` on the export), Attendance, the schedule checklists, the security dashboard and NFC patrol report. Display only - field names and API keys are unchanged. `scripts/sync-worker-terms.mjs` copies the map into the mobile apps and API-core.
|
|
8
|
+
|
|
3
9
|
## 4.62.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -1679,11 +1685,11 @@
|
|
|
1679
1685
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1680
1686
|
own rule from two endpoints the console already calls, unprojected:
|
|
1681
1687
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1688
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1689
|
+
GET /api/roles/id/:role that membership's role document
|
|
1684
1690
|
|
|
1685
|
-
|
|
1686
|
-
|
|
1691
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1692
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1687
1693
|
|
|
1688
1694
|
`role.default` is the marker because it is the only property of a platform
|
|
1689
1695
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12" lg="12">
|
|
4
4
|
<TableMain
|
|
5
|
-
:title="
|
|
5
|
+
:title="checklistTitle"
|
|
6
6
|
:headers="headers"
|
|
7
7
|
:items="items"
|
|
8
8
|
:loading="loading"
|
|
@@ -98,6 +98,10 @@
|
|
|
98
98
|
|
|
99
99
|
<script setup lang="ts">
|
|
100
100
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
101
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
102
|
+
import { workerTitle } from "../utils/worker-terms";
|
|
103
|
+
// "Cleaner Checklist" in Hygiene, "Gardener Checklist" in Landscape, ...
|
|
104
|
+
const checklistTitle = workerTitle(`${useWorkerTerm().one} checklist`);
|
|
101
105
|
|
|
102
106
|
const props = defineProps({
|
|
103
107
|
orgId: { type: String, required: true, default: "" },
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
|
|
63
63
|
<script setup lang="ts">
|
|
64
64
|
import useAttendance from "../composables/useAttendance";
|
|
65
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
65
66
|
import useUtils from "../composables/useUtils";
|
|
66
67
|
|
|
67
68
|
const props = defineProps({
|
|
@@ -86,7 +87,7 @@ const searchInput = ref("");
|
|
|
86
87
|
const items = ref<Array<Record<string, any>>>([]);
|
|
87
88
|
|
|
88
89
|
const headers = [
|
|
89
|
-
{ title:
|
|
90
|
+
{ title: useWorkerTerm().one, value: "userName" },
|
|
90
91
|
{ title: "Check In", value: "checkInTimestamp" },
|
|
91
92
|
{ title: "Check Out", value: "checkOutTimestamp" },
|
|
92
93
|
{ title: "Total Hours", value: "totalHours" },
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<v-col cols="12" lg="12">
|
|
4
4
|
<TableMain
|
|
5
5
|
v-if="canViewSchedules"
|
|
6
|
-
:title="
|
|
6
|
+
:title="checklistTitle"
|
|
7
7
|
:items="items"
|
|
8
8
|
:headers="headers"
|
|
9
9
|
:loading="loading"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
v-model:page="page"
|
|
12
12
|
:pages="pages"
|
|
13
13
|
:pageRange="pageRange"
|
|
14
|
-
:no-data-text="
|
|
14
|
+
:no-data-text="`No ${checklistTitle} found`"
|
|
15
15
|
@refresh="getCleanerChecklistRefresh"
|
|
16
16
|
@row-click="onRowClick"
|
|
17
17
|
>
|
|
@@ -114,6 +114,10 @@
|
|
|
114
114
|
|
|
115
115
|
<script lang="ts" setup>
|
|
116
116
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
117
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
118
|
+
import { workerTitle } from "../utils/worker-terms";
|
|
119
|
+
// "Cleaner Checklist" in Hygiene, "Gardener Checklist" in Landscape, ...
|
|
120
|
+
const checklistTitle = workerTitle(`${useWorkerTerm().one} checklist`);
|
|
117
121
|
import useUtils from "../composables/useUtils";
|
|
118
122
|
|
|
119
123
|
const props = defineProps({
|
|
@@ -996,7 +996,7 @@
|
|
|
996
996
|
<strong>{{
|
|
997
997
|
item.userName || item.personName || "Unassigned"
|
|
998
998
|
}}</strong>
|
|
999
|
-
<p>{{ item.role || item.designation ||
|
|
999
|
+
<p>{{ item.role || item.designation || worker.one }}</p>
|
|
1000
1000
|
</div>
|
|
1001
1001
|
<StatusChip :status="item.status || 'On duty'" pill />
|
|
1002
1002
|
</div>
|
|
@@ -1233,6 +1233,7 @@ import type { CookieOptions } from "#app";
|
|
|
1233
1233
|
import { buildWorkOrderStatus } from "../utils/dashboard";
|
|
1234
1234
|
import { permissionsDenied } from "../utils/module-routes";
|
|
1235
1235
|
import { grantsPermission } from "../utils/permission-spellings";
|
|
1236
|
+
import { workerTerm, workerTitle } from "../utils/worker-terms";
|
|
1236
1237
|
import {
|
|
1237
1238
|
hiddenDashboardWidgets,
|
|
1238
1239
|
saveHiddenDashboardWidgets,
|
|
@@ -1369,6 +1370,8 @@ const isPropertyMode = computed(
|
|
|
1369
1370
|
() => props.serviceType === "property_management_agency"
|
|
1370
1371
|
);
|
|
1371
1372
|
const isSecurityMode = computed(() => props.serviceType === "security_agency");
|
|
1373
|
+
// "Security officer" on the security dashboard; each service type's own noun elsewhere.
|
|
1374
|
+
const worker = computed(() => workerTerm(props.serviceType));
|
|
1372
1375
|
const isHygieneMode = computed(
|
|
1373
1376
|
() => !isPropertyMode.value && !isSecurityMode.value
|
|
1374
1377
|
);
|
|
@@ -2963,7 +2966,7 @@ const printTableSections = computed<PrintTableSection[]>(() => {
|
|
|
2963
2966
|
columns: [
|
|
2964
2967
|
{ key: "title", label: "Patrol Route", width: "30%", strong: true },
|
|
2965
2968
|
{ key: "subtitle", label: "Location", width: "30%" },
|
|
2966
|
-
{ key: "person", label:
|
|
2969
|
+
{ key: "person", label: workerTitle(`Assigned ${worker.value.one}`), width: "25%" },
|
|
2967
2970
|
{ key: "status", label: "Status", width: "15%", status: true },
|
|
2968
2971
|
],
|
|
2969
2972
|
rows: activePatrolItems.value.map((item, index) => ({
|
|
@@ -2978,7 +2981,7 @@ const printTableSections = computed<PrintTableSection[]>(() => {
|
|
|
2978
2981
|
key: "staffStatus",
|
|
2979
2982
|
title: "Staff Status",
|
|
2980
2983
|
columns: [
|
|
2981
|
-
{ key: "name", label:
|
|
2984
|
+
{ key: "name", label: workerTitle(`${worker.value.one} name`), width: "40%", strong: true },
|
|
2982
2985
|
{ key: "role", label: "Role / Designation", width: "40%" },
|
|
2983
2986
|
{
|
|
2984
2987
|
key: "status",
|
|
@@ -2991,7 +2994,7 @@ const printTableSections = computed<PrintTableSection[]>(() => {
|
|
|
2991
2994
|
rows: securityStaffItems.value.map((item, index) => ({
|
|
2992
2995
|
id: item.id || item._id || `security-staff-${index}`,
|
|
2993
2996
|
name: item.userName || item.personName || "Unassigned",
|
|
2994
|
-
role: item.role || item.designation ||
|
|
2997
|
+
role: item.role || item.designation || worker.value.one,
|
|
2995
2998
|
status: item.status || "On duty",
|
|
2996
2999
|
})),
|
|
2997
3000
|
}
|
|
@@ -3927,7 +3930,7 @@ async function exportDashboard(format: "PDF" | "Word") {
|
|
|
3927
3930
|
if (isSecurityMode.value) {
|
|
3928
3931
|
wordHtml += `<div class="section-title">Active Patrol</div>`;
|
|
3929
3932
|
wordHtml += `<table class="data-table">`;
|
|
3930
|
-
wordHtml += `<thead><tr><th>Patrol Route</th><th>Location</th><th
|
|
3933
|
+
wordHtml += `<thead><tr><th>Patrol Route</th><th>Location</th><th>${workerTitle(`Assigned ${worker.value.one}`)}</th><th>Status</th></tr></thead><tbody>`;
|
|
3931
3934
|
if (activePatrolItems.value.length > 0) {
|
|
3932
3935
|
activePatrolItems.value.forEach((item) => {
|
|
3933
3936
|
wordHtml += `<tr>
|
|
@@ -3946,14 +3949,14 @@ async function exportDashboard(format: "PDF" | "Word") {
|
|
|
3946
3949
|
|
|
3947
3950
|
wordHtml += `<div class="section-title">Staff Status</div>`;
|
|
3948
3951
|
wordHtml += `<table class="data-table">`;
|
|
3949
|
-
wordHtml += `<thead><tr><th
|
|
3952
|
+
wordHtml += `<thead><tr><th>${workerTitle(`${worker.value.one} name`)}</th><th>Role / Designation</th><th>Status</th></tr></thead><tbody>`;
|
|
3950
3953
|
if (securityStaffItems.value.length > 0) {
|
|
3951
3954
|
securityStaffItems.value.forEach((item) => {
|
|
3952
3955
|
wordHtml += `<tr>
|
|
3953
3956
|
<td><strong>${
|
|
3954
3957
|
item.userName || item.personName || "Unassigned"
|
|
3955
3958
|
}</strong></td>
|
|
3956
|
-
<td>${item.role || item.designation ||
|
|
3959
|
+
<td>${item.role || item.designation || worker.value.one}</td>
|
|
3957
3960
|
<td><span class="status-badge badge-${getStatusTone(
|
|
3958
3961
|
item.status
|
|
3959
3962
|
)}">${item.status || "On duty"}</span></td>
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
|
|
113
113
|
<script setup lang="ts">
|
|
114
114
|
import useInventory from "../composables/useInventory";
|
|
115
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
115
116
|
import useSite from "../composables/useSite";
|
|
116
117
|
import useUtils from "../composables/useUtils";
|
|
117
118
|
import { inventoryStoreLabel } from "../utils/inventory-store-label";
|
|
@@ -160,6 +161,9 @@ const subtitle = computed(() => {
|
|
|
160
161
|
return inventoryStoreLabel(store.value, stores.value);
|
|
161
162
|
});
|
|
162
163
|
|
|
164
|
+
// Who holds this store's stock: the property manager's store lends to service providers' workers.
|
|
165
|
+
const worker = useWorkerTerm({ providers: true });
|
|
166
|
+
|
|
163
167
|
const tabs = computed(() => {
|
|
164
168
|
const a = store.value?.access;
|
|
165
169
|
if (!a) return [];
|
|
@@ -167,7 +171,7 @@ const tabs = computed(() => {
|
|
|
167
171
|
{ value: "stock", label: "Stock" },
|
|
168
172
|
...(a.canRequest && !a.canManage ? [{ value: "mine", label: "My Stock" }] : []),
|
|
169
173
|
{ value: "requests", label: a.canApprove ? "Requests" : "My Requests", badge: a.canApprove ? pendingCount.value : 0 },
|
|
170
|
-
...(a.canApprove ? [{ value: "technicians", label:
|
|
174
|
+
...(a.canApprove ? [{ value: "technicians", label: worker.many }] : []),
|
|
171
175
|
...(isStaff.value ? [{ value: "reports", label: "Reports" }] : []),
|
|
172
176
|
...(a.canManage ? [{ value: "settings", label: "Settings" }] : []),
|
|
173
177
|
];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<v-btn-toggle v-model="report" mandatory density="comfortable" variant="outlined" divided>
|
|
5
5
|
<v-btn value="movement" class="text-none">Stock Movement</v-btn>
|
|
6
6
|
<v-btn value="usage" class="text-none">Usage</v-btn>
|
|
7
|
-
<v-btn value="technicians" class="text-none">
|
|
7
|
+
<v-btn value="technicians" class="text-none">{{ worker.many }}</v-btn>
|
|
8
8
|
<v-btn value="purchases" class="text-none">Purchases</v-btn>
|
|
9
9
|
</v-btn-toggle>
|
|
10
10
|
<v-select
|
|
@@ -133,6 +133,7 @@
|
|
|
133
133
|
|
|
134
134
|
<script setup lang="ts">
|
|
135
135
|
import useInventory, { saveBlob } from "../composables/useInventory";
|
|
136
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
136
137
|
import useUtils from "../composables/useUtils";
|
|
137
138
|
import useFile from "../composables/useFile";
|
|
138
139
|
|
|
@@ -150,6 +151,9 @@ const usagePhotos = (row: any) => [
|
|
|
150
151
|
];
|
|
151
152
|
const notify = inject<(text: string, color?: string) => void>("inventoryNotify", () => {});
|
|
152
153
|
|
|
154
|
+
const worker = useWorkerTerm({ providers: true });
|
|
155
|
+
const appSlug = useRuntimeConfig().public?.appSlug as string | undefined;
|
|
156
|
+
|
|
153
157
|
const now = new Date();
|
|
154
158
|
const month = ref(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
|
|
155
159
|
const report = ref<"movement" | "usage" | "technicians" | "purchases">("movement");
|
|
@@ -204,7 +208,7 @@ const headers: Record<string, any[]> = {
|
|
|
204
208
|
{ title: "Qty", key: "qty" },
|
|
205
209
|
{ title: "Unit price", key: "unitCost" },
|
|
206
210
|
{ title: "Total", key: "totalCost" },
|
|
207
|
-
{ title:
|
|
211
|
+
{ title: worker.one, key: "technician" },
|
|
208
212
|
{ title: "Photos", key: "photos", sortable: false },
|
|
209
213
|
{ title: "Reference", key: "reference" },
|
|
210
214
|
{ title: "Reported by", key: "reportedBy" },
|
|
@@ -283,8 +287,8 @@ async function setChecked(checked: boolean) {
|
|
|
283
287
|
async function exportExcel() {
|
|
284
288
|
exporting.value = true;
|
|
285
289
|
try {
|
|
286
|
-
const blob = await api.exportReport(props.store._id, report.value, period());
|
|
287
|
-
saveBlob(blob, `${props.store.name} - ${report.value} - ${month.value}.xlsx`);
|
|
290
|
+
const blob = await api.exportReport(props.store._id, report.value, { ...period(), appSlug });
|
|
291
|
+
saveBlob(blob, `${props.store.name} - ${report.value === "technicians" ? worker.many : report.value} - ${month.value}.xlsx`);
|
|
288
292
|
} catch {
|
|
289
293
|
notify("Could not export the report.", "error");
|
|
290
294
|
} finally {
|
|
@@ -460,6 +460,8 @@
|
|
|
460
460
|
import { readListPage } from "../utils/list-page";
|
|
461
461
|
import { isPhotoRefusal, isSetFinished } from "../utils/checklist-set";
|
|
462
462
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
463
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
464
|
+
import { workerTitle } from "../utils/worker-terms";
|
|
463
465
|
import useCustomerSite from "../composables/useCustomerSite";
|
|
464
466
|
import useFile from "../composables/useFile";
|
|
465
467
|
import useUtils from "../composables/useUtils";
|
|
@@ -480,14 +482,15 @@ const { getBySiteAsServiceProvider } = useCustomerSite();
|
|
|
480
482
|
const { back } = useUtils();
|
|
481
483
|
|
|
482
484
|
const route = useRoute();
|
|
485
|
+
const workerChecklist = workerTitle(`${useWorkerTerm().one} checklist`);
|
|
483
486
|
const scheduleTitle = computed(() => {
|
|
484
487
|
const path = route.path;
|
|
485
488
|
if (path.includes("cleaning-schedule")) return "Cleaning Checklist";
|
|
486
|
-
if (path.includes("technician-schedule")) return
|
|
489
|
+
if (path.includes("technician-schedule")) return workerChecklist;
|
|
487
490
|
if (path.includes("landscape-schedule")) return "Landscape Checklist";
|
|
488
491
|
if (path.includes("pool-schedule")) return "Pool Checklist";
|
|
489
492
|
if (path.includes("pest-schedule")) return "Pest Checklist";
|
|
490
|
-
return
|
|
493
|
+
return workerChecklist;
|
|
491
494
|
});
|
|
492
495
|
|
|
493
496
|
const canAddRemarks = computed(() => props.canAddRemarks);
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<v-img
|
|
46
46
|
v-if="activity.guardSignatureFileId"
|
|
47
47
|
:src="getFileUrl(activity.guardSignatureFileId)"
|
|
48
|
-
:alt="`${activity.guardName ||
|
|
48
|
+
:alt="`${activity.guardName || worker.one} signature`"
|
|
49
49
|
class="guard-signature"
|
|
50
50
|
contain
|
|
51
51
|
/>
|
|
@@ -78,11 +78,13 @@
|
|
|
78
78
|
</template>
|
|
79
79
|
|
|
80
80
|
<script setup lang="ts">
|
|
81
|
+
import useWorkerTerm from "../../../composables/useWorkerTerm";
|
|
81
82
|
import type {
|
|
82
83
|
NFCPatrolActivity,
|
|
83
84
|
NFCPatrolActivityStatus,
|
|
84
85
|
} from "../../../types/nfc-patrol-report";
|
|
85
86
|
|
|
87
|
+
const worker = useWorkerTerm();
|
|
86
88
|
defineProps<{
|
|
87
89
|
activities: NFCPatrolActivity[];
|
|
88
90
|
}>();
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<v-img
|
|
45
45
|
v-if="row.guardSignatureFileId"
|
|
46
46
|
:src="getFileUrl(row.guardSignatureFileId)"
|
|
47
|
-
:alt="`${row.guardName ||
|
|
47
|
+
:alt="`${row.guardName || worker.one} signature`"
|
|
48
48
|
class="guard-signature"
|
|
49
49
|
contain
|
|
50
50
|
/>
|
|
@@ -65,11 +65,13 @@
|
|
|
65
65
|
</template>
|
|
66
66
|
|
|
67
67
|
<script setup lang="ts">
|
|
68
|
+
import useWorkerTerm from "../../../composables/useWorkerTerm";
|
|
68
69
|
import type {
|
|
69
70
|
NFCPatrolReportSummaryRow,
|
|
70
71
|
NFCPatrolReportSummaryStatus,
|
|
71
72
|
} from "../../../types/nfc-patrol-report";
|
|
72
73
|
|
|
74
|
+
const worker = useWorkerTerm();
|
|
73
75
|
defineProps<{
|
|
74
76
|
title: string;
|
|
75
77
|
rows: NFCPatrolReportSummaryRow[];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<v-row no-gutters align="center" justify="center">
|
|
3
3
|
<v-col cols="12" lg="12">
|
|
4
4
|
<TableMain
|
|
5
|
-
:title="
|
|
5
|
+
:title="checklistTitle"
|
|
6
6
|
:headers="headers"
|
|
7
7
|
:items="items"
|
|
8
8
|
:loading="loading"
|
|
@@ -160,6 +160,10 @@
|
|
|
160
160
|
|
|
161
161
|
<script setup lang="ts">
|
|
162
162
|
import useCleaningSchedules from "../composables/useCleaningSchedules";
|
|
163
|
+
import useWorkerTerm from "../composables/useWorkerTerm";
|
|
164
|
+
import { workerTitle } from "../utils/worker-terms";
|
|
165
|
+
// "Cleaner Checklist" in Hygiene, "Gardener Checklist" in Landscape, ...
|
|
166
|
+
const checklistTitle = workerTitle(`${useWorkerTerm().one} checklist`);
|
|
163
167
|
import useUtils from "../composables/useUtils";
|
|
164
168
|
|
|
165
169
|
const props = defineProps({
|
|
@@ -57,7 +57,7 @@ export default function useInventory() {
|
|
|
57
57
|
|
|
58
58
|
getReport: (store: string, report: "movement" | "usage" | "technicians" | "purchases", query: TInventoryPeriod & { unchecked?: boolean }) =>
|
|
59
59
|
api<{ rows: any[]; totalQty?: number; totalCost?: number }>(`/stores/${store}/reports/${report}`, { query }),
|
|
60
|
-
exportReport: (store: string, report: string, query: TInventoryPeriod) =>
|
|
60
|
+
exportReport: (store: string, report: string, query: TInventoryPeriod & { appSlug?: string }) =>
|
|
61
61
|
api<Blob>(`/stores/${store}/reports/${report}/export`, { query, responseType: "blob" }),
|
|
62
62
|
checkUsage: (store: string, ids: string[], checked: boolean) =>
|
|
63
63
|
api<{ updated: number }>(`/stores/${store}/usage/check`, { method: "POST", body: { ids, checked } }),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { workerTerm } from "../utils/worker-terms";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The worker noun for the app this page runs in ("Cleaner" in Hygiene,
|
|
5
|
+
* "Security officer" in Security, ...). See utils/worker-terms.ts.
|
|
6
|
+
* Apps without `runtimeConfig.public.appSlug` get the neutral "Staff".
|
|
7
|
+
*/
|
|
8
|
+
export default function useWorkerTerm(opts?: { providers?: boolean }) {
|
|
9
|
+
return workerTerm(useRuntimeConfig().public?.appSlug as string | undefined, opts);
|
|
10
|
+
}
|
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.
|
|
5
|
+
"version": "4.63.0",
|
|
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.",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each kind of account calls the people who do its work.
|
|
3
|
+
*
|
|
4
|
+
* A cleaning company's workers are cleaners, not technicians. Every screen,
|
|
5
|
+
* export and mobile app takes the noun from here, keyed by the app it runs in
|
|
6
|
+
* (`appSlug`, e.g. "iservice365-web-app-hygiene" or
|
|
7
|
+
* "iservice365-hygiene-mobile-app"); a service-type code ("hygiene",
|
|
8
|
+
* "security_agency", ...) resolves the same way. Only the words change -
|
|
9
|
+
* field names and API keys (`technician`, `usedByTechnicians`,
|
|
10
|
+
* `/reports/technicians`) stay as they are.
|
|
11
|
+
*
|
|
12
|
+
* Copied into the React Native apps and API-core by
|
|
13
|
+
* `scripts/sync-worker-terms.mjs`, so it must stay free of imports.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type TWorkerTerm = {
|
|
17
|
+
/** Singular, sentence case: "Cleaner", "Security officer". */
|
|
18
|
+
one: string;
|
|
19
|
+
/** Plural, sentence case: "Cleaners", "Staff". */
|
|
20
|
+
many: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type TWorkerKind =
|
|
24
|
+
| "hygiene"
|
|
25
|
+
| "security"
|
|
26
|
+
| "pest"
|
|
27
|
+
| "mechanical"
|
|
28
|
+
| "landscape"
|
|
29
|
+
| "pool"
|
|
30
|
+
| "property"
|
|
31
|
+
| "service"
|
|
32
|
+
| "staff";
|
|
33
|
+
|
|
34
|
+
export const WORKER_TERMS: Record<TWorkerKind, TWorkerTerm> = {
|
|
35
|
+
hygiene: { one: "Cleaner", many: "Cleaners" },
|
|
36
|
+
security: { one: "Security officer", many: "Security officers" },
|
|
37
|
+
pest: { one: "Pest technician", many: "Pest technicians" },
|
|
38
|
+
mechanical: { one: "Technician", many: "Technicians" },
|
|
39
|
+
landscape: { one: "Gardener", many: "Gardeners" },
|
|
40
|
+
pool: { one: "Pool attendant", many: "Pool attendants" },
|
|
41
|
+
// The property manager's own team.
|
|
42
|
+
property: { one: "Staff member", many: "Staff" },
|
|
43
|
+
// Other companies' workers, as a property manager sees them.
|
|
44
|
+
service: { one: "Service staff member", many: "Service staff" },
|
|
45
|
+
// Anything else (organisation console, Super Admin, unknown app).
|
|
46
|
+
staff: { one: "Staff member", many: "Staff" },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ponytail: keyword match covers every web slug, mobile slug and service-type code in use; add a row when a new app kind appears.
|
|
50
|
+
const KINDS: [RegExp, TWorkerKind][] = [
|
|
51
|
+
[/hygiene|clean/, "hygiene"],
|
|
52
|
+
[/secur/, "security"],
|
|
53
|
+
[/pest/, "pest"],
|
|
54
|
+
[/mechanical|m-and-e/, "mechanical"],
|
|
55
|
+
[/landscap/, "landscape"],
|
|
56
|
+
[/pool/, "pool"],
|
|
57
|
+
[/property|-ma-/, "property"],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export function workerKind(appSlug?: string | null): TWorkerKind {
|
|
61
|
+
const slug = String(appSlug ?? "").toLowerCase();
|
|
62
|
+
for (const [pattern, kind] of KINDS) if (pattern.test(slug)) return kind;
|
|
63
|
+
return "staff";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The noun for this app. `providers: true` is for lists of people from the
|
|
68
|
+
* service providers a property manager supplies (e.g. who holds stock from
|
|
69
|
+
* the property store): the property manager's app says "Service staff";
|
|
70
|
+
* every other app keeps its own noun.
|
|
71
|
+
*/
|
|
72
|
+
export function workerTerm(appSlug?: string | null, opts?: { providers?: boolean }): TWorkerTerm {
|
|
73
|
+
const kind = workerKind(appSlug);
|
|
74
|
+
return WORKER_TERMS[opts?.providers && kind === "property" ? "service" : kind];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Title case for headings and column titles: "Security officer name" -> "Security Officer Name". */
|
|
78
|
+
export function workerTitle(text: string): string {
|
|
79
|
+
return String(text).replace(/(^|\s)(\S)/g, (_m, space: string, first: string) => space + first.toUpperCase());
|
|
80
|
+
}
|