@7365admin1/layer-common 1.11.10 → 1.11.12
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 +12 -0
- package/components/AreaMain.vue +11 -11
- package/components/AttendanceMain.vue +6 -1
- package/components/AttendanceSettingsDialog.vue +3 -2
- package/components/MemberMain.vue +29 -14
- package/components/QrTemplate/PrintDialog.vue +10 -9
- package/components/UnitMain.vue +1 -1
- package/components/VehicleForm.vue +8 -5
- package/composables/useAreas.ts +4 -1
- package/composables/useAttendance.ts +6 -3
- package/composables/useFacility.ts +9 -0
- package/composables/usePeople.ts +1 -1
- package/composables/useUnits.ts +4 -1
- package/package.json +1 -1
- package/types/vehicle.d.ts +2 -0
package/CHANGELOG.md
CHANGED
package/components/AreaMain.vue
CHANGED
|
@@ -614,18 +614,18 @@ async function onFileSelected(event: Event) {
|
|
|
614
614
|
|
|
615
615
|
if (!file) return;
|
|
616
616
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
617
|
+
try {
|
|
618
|
+
submitting.value = true;
|
|
619
|
+
const response = await uploadAreas(file, props.site, props.serviceType);
|
|
620
|
+
showMessage(response?.message, "success");
|
|
621
621
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
622
|
+
await getTaskRefresh();
|
|
623
|
+
} catch (err: any) {
|
|
624
|
+
showMessage(err?.data?.message, "error");
|
|
625
|
+
} finally {
|
|
626
|
+
submitting.value = false;
|
|
627
|
+
if (target) target.value = "";
|
|
628
|
+
}
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
function onCreateItem() {
|
|
@@ -46,12 +46,14 @@
|
|
|
46
46
|
<AttendanceDetailsDialog
|
|
47
47
|
v-model="dialogShowMoreActions"
|
|
48
48
|
:attendance-id="selectedAttendanceId"
|
|
49
|
+
:serviceType="props.serviceType"
|
|
49
50
|
@close="dialogShowMoreActions = false"
|
|
50
51
|
/>
|
|
51
52
|
|
|
52
53
|
<AttendanceSettingsDialog
|
|
53
54
|
v-model="dialogShowSettings"
|
|
54
55
|
:site="props.site"
|
|
56
|
+
:serviceType="props.serviceType"
|
|
55
57
|
@saved="onSettingsSaved"
|
|
56
58
|
@close="dialogShowSettings = false"
|
|
57
59
|
/>
|
|
@@ -60,10 +62,12 @@
|
|
|
60
62
|
<script setup lang="ts">
|
|
61
63
|
import useAttendance from "../composables/useAttendance";
|
|
62
64
|
import { useAttendancePermission } from "../composables/useAttendancePermission";
|
|
65
|
+
import useUtils from "../composables/useUtils";
|
|
63
66
|
|
|
64
67
|
const props = defineProps({
|
|
65
68
|
orgId: { type: String, default: "" },
|
|
66
69
|
site: { type: String, default: "" },
|
|
70
|
+
serviceType: { type: String, default: "" },
|
|
67
71
|
});
|
|
68
72
|
|
|
69
73
|
const {
|
|
@@ -117,7 +121,8 @@ const {
|
|
|
117
121
|
getAttendances({
|
|
118
122
|
page: page.value,
|
|
119
123
|
search: searchInput.value,
|
|
120
|
-
|
|
124
|
+
site: props.site,
|
|
125
|
+
serviceType: props.serviceType,
|
|
121
126
|
}),
|
|
122
127
|
{
|
|
123
128
|
watch: [page, searchInput, () => props.site],
|
|
@@ -181,6 +181,7 @@ const showDialog = defineModel({ type: Boolean, default: false });
|
|
|
181
181
|
|
|
182
182
|
const props = defineProps({
|
|
183
183
|
site: { type: String, required: true },
|
|
184
|
+
serviceType: { type: String, default: "" },
|
|
184
185
|
});
|
|
185
186
|
|
|
186
187
|
const emit = defineEmits(["saved", "close"]);
|
|
@@ -211,9 +212,9 @@ const {
|
|
|
211
212
|
pending: loading,
|
|
212
213
|
} = await useLazyAsyncData(
|
|
213
214
|
`attendance-settings-${props.site}`,
|
|
214
|
-
() => getAttendanceSettings(props.site),
|
|
215
|
+
() => getAttendanceSettings(props.site, props.serviceType),
|
|
215
216
|
{
|
|
216
|
-
watch: [() => props.site],
|
|
217
|
+
watch: [() => props.site, () => props.serviceType],
|
|
217
218
|
immediate: false,
|
|
218
219
|
}
|
|
219
220
|
);
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
<template #item.action-table="{ item }">
|
|
77
77
|
<v-menu
|
|
78
78
|
v-if="
|
|
79
|
-
(
|
|
79
|
+
(currentStatus === 'active' &&
|
|
80
80
|
(canSuspendMembers || canDeleteMembers)) ||
|
|
81
|
-
(
|
|
81
|
+
(currentStatus === 'suspended' &&
|
|
82
82
|
(canActivateMembers || canDeleteMembers))
|
|
83
83
|
"
|
|
84
84
|
v-model="item.menuOpen"
|
|
@@ -104,13 +104,13 @@
|
|
|
104
104
|
</v-list-item>
|
|
105
105
|
|
|
106
106
|
<v-list-item
|
|
107
|
-
v-if="
|
|
107
|
+
v-if="currentStatus === 'active' && canSuspendMembers"
|
|
108
108
|
@click="openUpdateDialog(item._id, 'suspended')"
|
|
109
109
|
>
|
|
110
110
|
Suspend
|
|
111
111
|
</v-list-item>
|
|
112
112
|
<v-list-item
|
|
113
|
-
v-if="
|
|
113
|
+
v-if="currentStatus === 'suspended' && canActivateMembers"
|
|
114
114
|
@click="openUpdateDialog(item._id, 'active')"
|
|
115
115
|
>
|
|
116
116
|
Activate
|
|
@@ -330,9 +330,20 @@ const { headerSearch } = useLocal();
|
|
|
330
330
|
const { replaceMatch, setRouteParams, requiredRule } = useUtils();
|
|
331
331
|
|
|
332
332
|
const selectedStatus = ref(props.status);
|
|
333
|
+
const currentStatus = computed(() => selectedStatus.value || props.status);
|
|
333
334
|
const route = useRoute();
|
|
334
|
-
const orgId = route.params.org as string;
|
|
335
335
|
const routeName = (useRoute().name as string) ?? "";
|
|
336
|
+
const normalizedRouteParams = computed(() => {
|
|
337
|
+
const params: Record<string, string> = {};
|
|
338
|
+
Object.entries(route.params as Record<string, unknown>).forEach(
|
|
339
|
+
([key, value]) => {
|
|
340
|
+
if (typeof value === "string") {
|
|
341
|
+
params[key] = value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
return params;
|
|
346
|
+
});
|
|
336
347
|
|
|
337
348
|
const tabOptions = [
|
|
338
349
|
{ name: "Active", status: "active" },
|
|
@@ -345,15 +356,19 @@ function toRoute(status: any) {
|
|
|
345
356
|
page.value = 1;
|
|
346
357
|
navigateTo({
|
|
347
358
|
name: routeName,
|
|
348
|
-
params: {
|
|
349
|
-
org: orgId,
|
|
350
|
-
},
|
|
351
|
-
query: {
|
|
352
|
-
status: obj.status,
|
|
353
|
-
},
|
|
359
|
+
params: setRouteParams(normalizedRouteParams.value, { status: obj.status }),
|
|
354
360
|
});
|
|
355
361
|
}
|
|
356
362
|
|
|
363
|
+
watch(
|
|
364
|
+
() => props.status,
|
|
365
|
+
(status) => {
|
|
366
|
+
if (status && status !== selectedStatus.value) {
|
|
367
|
+
selectedStatus.value = status;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
|
|
357
372
|
const {
|
|
358
373
|
data: getAllReq,
|
|
359
374
|
refresh: getAll,
|
|
@@ -362,14 +377,14 @@ const {
|
|
|
362
377
|
"get-all-members",
|
|
363
378
|
() =>
|
|
364
379
|
_getAll({
|
|
365
|
-
status:
|
|
366
|
-
org: orgId,
|
|
380
|
+
status: currentStatus.value,
|
|
381
|
+
org: props.orgId,
|
|
367
382
|
search: headerSearch.value,
|
|
368
383
|
page: page.value,
|
|
369
384
|
type: props.type,
|
|
370
385
|
}),
|
|
371
386
|
{
|
|
372
|
-
watch: [page,
|
|
387
|
+
watch: [page, currentStatus],
|
|
373
388
|
}
|
|
374
389
|
);
|
|
375
390
|
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<v-autocomplete
|
|
54
54
|
:items="
|
|
55
55
|
localTemplateList.filter(
|
|
56
|
-
(item) => !['All', 'Manually Added'].includes(item.name)
|
|
56
|
+
(item) => !['All', 'Manually Added'].includes(item.name),
|
|
57
57
|
)
|
|
58
58
|
"
|
|
59
59
|
item-title="name"
|
|
@@ -833,7 +833,7 @@ watch(
|
|
|
833
833
|
() => props.templateList,
|
|
834
834
|
(newVal) => {
|
|
835
835
|
localTemplateList.value = newVal;
|
|
836
|
-
}
|
|
836
|
+
},
|
|
837
837
|
);
|
|
838
838
|
|
|
839
839
|
// interset start
|
|
@@ -973,6 +973,7 @@ watch(
|
|
|
973
973
|
() => props.open,
|
|
974
974
|
async (newVal: any) => {
|
|
975
975
|
if (newVal) {
|
|
976
|
+
selectAll.value = false;
|
|
976
977
|
if (props.template.name !== "All") {
|
|
977
978
|
selectedTemplate.value = props.template;
|
|
978
979
|
} else {
|
|
@@ -998,7 +999,7 @@ watch(
|
|
|
998
999
|
}
|
|
999
1000
|
}
|
|
1000
1001
|
},
|
|
1001
|
-
{ immediate: true }
|
|
1002
|
+
{ immediate: true },
|
|
1002
1003
|
);
|
|
1003
1004
|
|
|
1004
1005
|
const router = useRouter();
|
|
@@ -1030,7 +1031,7 @@ const printContent = () => {
|
|
|
1030
1031
|
if (printOrientation == "KeyCard") {
|
|
1031
1032
|
localStorage.setItem(
|
|
1032
1033
|
"selectedKeysToPrint",
|
|
1033
|
-
JSON.stringify(selectedKeysToPrint.value)
|
|
1034
|
+
JSON.stringify(selectedKeysToPrint.value),
|
|
1034
1035
|
);
|
|
1035
1036
|
}
|
|
1036
1037
|
|
|
@@ -1082,14 +1083,14 @@ watch(selectedChoicePaperSizeOrientation, async (newVal: any) => {
|
|
|
1082
1083
|
resultKeys2.value.sort(
|
|
1083
1084
|
(firstItem: { value: string }, secondItem: { value: string }) => {
|
|
1084
1085
|
const numericValueOfFirstItem = Number(
|
|
1085
|
-
firstItem.value.replace(/\D/g, "")
|
|
1086
|
+
firstItem.value.replace(/\D/g, ""),
|
|
1086
1087
|
);
|
|
1087
1088
|
const numericValueOfSecondItem = Number(
|
|
1088
|
-
secondItem.value.replace(/\D/g, "")
|
|
1089
|
+
secondItem.value.replace(/\D/g, ""),
|
|
1089
1090
|
);
|
|
1090
1091
|
|
|
1091
1092
|
return numericValueOfFirstItem - numericValueOfSecondItem;
|
|
1092
|
-
}
|
|
1093
|
+
},
|
|
1093
1094
|
);
|
|
1094
1095
|
|
|
1095
1096
|
startRange.value = props.start.toString();
|
|
@@ -1106,7 +1107,7 @@ watch(startRange, (newVal: any, oldVal: any) => {
|
|
|
1106
1107
|
|
|
1107
1108
|
const endRangeKeys = computed(() => {
|
|
1108
1109
|
return resultKeys2.value.filter(
|
|
1109
|
-
(item: any) => item.value >= startRange.value
|
|
1110
|
+
(item: any) => item.value >= startRange.value,
|
|
1110
1111
|
);
|
|
1111
1112
|
});
|
|
1112
1113
|
const message = ref("");
|
|
@@ -1126,7 +1127,7 @@ const showSnackbarEndRange = () => {
|
|
|
1126
1127
|
? "Please select a start range first."
|
|
1127
1128
|
: `No ${tab.value == "key" ? "Key" : "Pass"} found. Please try to
|
|
1128
1129
|
generate first.`,
|
|
1129
|
-
"error"
|
|
1130
|
+
"error",
|
|
1130
1131
|
);
|
|
1131
1132
|
}
|
|
1132
1133
|
};
|
package/components/UnitMain.vue
CHANGED
|
@@ -364,7 +364,7 @@ async function onFileSelected(event: Event) {
|
|
|
364
364
|
try {
|
|
365
365
|
submitting.value = true;
|
|
366
366
|
|
|
367
|
-
const response = await uploadUnits(file, props.site);
|
|
367
|
+
const response = await uploadUnits(file, props.site, props.serviceType);
|
|
368
368
|
showMessage(response?.message, "success");
|
|
369
369
|
|
|
370
370
|
await getUnitsRefresh();
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<v-col v-if="shouldShowField('nric')" cols="12">
|
|
56
56
|
<InputLabel class="text-capitalize" title="NRIC" required />
|
|
57
|
-
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]"
|
|
57
|
+
<InputNRICNumber v-model="vehicle.nric" density="comfortable" :rules="[requiredRule]" :disabled="disablePrefilledInputs" />
|
|
58
58
|
</v-col>
|
|
59
59
|
|
|
60
60
|
<v-col v-if="shouldShowField('name')" cols="12">
|
|
@@ -290,6 +290,7 @@ const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicl
|
|
|
290
290
|
site: prop.site,
|
|
291
291
|
org: prop.org,
|
|
292
292
|
_id: '',
|
|
293
|
+
peopleId: '',
|
|
293
294
|
});
|
|
294
295
|
|
|
295
296
|
const newPlateNumbers = ref<string[]>(['']);
|
|
@@ -516,14 +517,15 @@ async function submit() {
|
|
|
516
517
|
}
|
|
517
518
|
|
|
518
519
|
|
|
519
|
-
const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org } = vehicle
|
|
520
|
+
const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org, peopleId } = vehicle
|
|
520
521
|
|
|
521
522
|
let payload: Partial<TVehiclePayload> = {
|
|
522
523
|
plateNumber: newPlateNumbers.value,
|
|
523
524
|
name,
|
|
524
525
|
phoneNumber,
|
|
525
526
|
type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
|
|
526
|
-
nric
|
|
527
|
+
nric,
|
|
528
|
+
peopleId
|
|
527
529
|
};
|
|
528
530
|
|
|
529
531
|
if (prop.mode === 'add') {
|
|
@@ -652,6 +654,7 @@ function selectPeopleRecord(record: TPeople) {
|
|
|
652
654
|
vehicle.plates = record.plates || [];
|
|
653
655
|
disablePrefilledInputs.value = true;
|
|
654
656
|
showMatchingPeopleDialog.value = false;
|
|
657
|
+
vehicle.peopleId = record._id || '';
|
|
655
658
|
}
|
|
656
659
|
|
|
657
660
|
|
|
@@ -696,7 +699,7 @@ async function checkPeopleById() {
|
|
|
696
699
|
// Lock editable fields while we verify if this NRIC has existing records.
|
|
697
700
|
disablePrefilledInputs.value = true;
|
|
698
701
|
try {
|
|
699
|
-
const res = await getPeopleByUnit(vehicle.unit) as TPeople[]| null;
|
|
702
|
+
const res = await getPeopleByUnit(vehicle.unit, { status: 'active', type: 'resident,tenant' }) as TPeople[]| null;
|
|
700
703
|
|
|
701
704
|
if (Array.isArray(res) && res.length > 0) {
|
|
702
705
|
matchingPeople.value = res || []
|
|
@@ -736,7 +739,7 @@ const resetVehicleDetails = () => {
|
|
|
736
739
|
vehicle.phoneNumber = '';
|
|
737
740
|
vehicle.plates = [];
|
|
738
741
|
vehicle.nric = '';
|
|
739
|
-
|
|
742
|
+
vehicle.peopleId = '';
|
|
740
743
|
}
|
|
741
744
|
|
|
742
745
|
|
package/composables/useAreas.ts
CHANGED
|
@@ -71,9 +71,12 @@ export default function useAreas() {
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function uploadAreas(file: File, site: string) {
|
|
74
|
+
function uploadAreas(file: File, site: string, serviceType?: string) {
|
|
75
75
|
const formData = new FormData();
|
|
76
76
|
formData.append("file", file);
|
|
77
|
+
if (serviceType) {
|
|
78
|
+
formData.append("serviceType", serviceType);
|
|
79
|
+
}
|
|
77
80
|
return useNuxtApp().$api<Record<string, any>>(
|
|
78
81
|
`/api/hygiene-areas/site/${site}/upload`,
|
|
79
82
|
{
|
|
@@ -4,12 +4,13 @@ export default function useAttendance() {
|
|
|
4
4
|
page = 1,
|
|
5
5
|
limit = 10,
|
|
6
6
|
search = "",
|
|
7
|
+
serviceType = "",
|
|
7
8
|
} = {}) {
|
|
8
9
|
return useNuxtApp().$api<Record<string, any>>(
|
|
9
10
|
`/api/attendances/site/${site}`,
|
|
10
11
|
{
|
|
11
12
|
method: "GET",
|
|
12
|
-
query: { page, limit, search },
|
|
13
|
+
query: { page, limit, search, serviceType },
|
|
13
14
|
}
|
|
14
15
|
);
|
|
15
16
|
}
|
|
@@ -19,12 +20,13 @@ export default function useAttendance() {
|
|
|
19
20
|
page = 1,
|
|
20
21
|
limit = 10,
|
|
21
22
|
search = "",
|
|
23
|
+
serviceType = "",
|
|
22
24
|
} = {}) {
|
|
23
25
|
return useNuxtApp().$api<Record<string, any>>(
|
|
24
26
|
`/api/attendances/site/${site}/user`,
|
|
25
27
|
{
|
|
26
28
|
method: "GET",
|
|
27
|
-
query: { page, limit, search },
|
|
29
|
+
query: { page, limit, search, serviceType },
|
|
28
30
|
}
|
|
29
31
|
);
|
|
30
32
|
}
|
|
@@ -55,11 +57,12 @@ export default function useAttendance() {
|
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
function getAttendanceSettings(site: string) {
|
|
60
|
+
function getAttendanceSettings(site: string, serviceType: string) {
|
|
59
61
|
return useNuxtApp().$api<TAttendanceSettings>(
|
|
60
62
|
`/api/attendance-settings/site/${site}`,
|
|
61
63
|
{
|
|
62
64
|
method: "GET",
|
|
65
|
+
query: { serviceType },
|
|
63
66
|
}
|
|
64
67
|
);
|
|
65
68
|
}
|
|
@@ -322,6 +322,7 @@ export default function usetFacility() {
|
|
|
322
322
|
maintenanceDatesWithTimes = [],
|
|
323
323
|
maintenanceWeekly = [],
|
|
324
324
|
maintenanceMonthly = [],
|
|
325
|
+
userType,
|
|
325
326
|
updatedBy,
|
|
326
327
|
}: {
|
|
327
328
|
_id: string;
|
|
@@ -329,6 +330,7 @@ export default function usetFacility() {
|
|
|
329
330
|
maintenanceDatesWithTimes: Record<string, any>[] | null;
|
|
330
331
|
maintenanceWeekly?: String[] | null;
|
|
331
332
|
maintenanceMonthly?: String[] | null;
|
|
333
|
+
userType?: string;
|
|
332
334
|
updatedBy?: string;
|
|
333
335
|
}) {
|
|
334
336
|
return useNuxtApp().$api("/api/facilities/maintenance/v1", {
|
|
@@ -339,6 +341,7 @@ export default function usetFacility() {
|
|
|
339
341
|
maintenanceDatesWithTimes,
|
|
340
342
|
maintenanceWeekly,
|
|
341
343
|
maintenanceMonthly,
|
|
344
|
+
userType,
|
|
342
345
|
updatedBy,
|
|
343
346
|
},
|
|
344
347
|
});
|
|
@@ -353,6 +356,8 @@ export default function usetFacility() {
|
|
|
353
356
|
maintenanceWeekly = [],
|
|
354
357
|
maintenanceMonthly = [],
|
|
355
358
|
status = "",
|
|
359
|
+
userType,
|
|
360
|
+
updatedBy,
|
|
356
361
|
}: {
|
|
357
362
|
page?: number;
|
|
358
363
|
limit?: number;
|
|
@@ -362,6 +367,8 @@ export default function usetFacility() {
|
|
|
362
367
|
maintenanceWeekly?: String[];
|
|
363
368
|
maintenanceMonthly?: String[];
|
|
364
369
|
status?: string;
|
|
370
|
+
userType?: string;
|
|
371
|
+
updatedBy?: string;
|
|
365
372
|
}) {
|
|
366
373
|
return useNuxtApp().$api(
|
|
367
374
|
"/api/facilities-booking/maintenance-affected/v1",
|
|
@@ -376,6 +383,8 @@ export default function usetFacility() {
|
|
|
376
383
|
maintenanceWeekly,
|
|
377
384
|
maintenanceMonthly,
|
|
378
385
|
status,
|
|
386
|
+
userType,
|
|
387
|
+
updatedBy,
|
|
379
388
|
},
|
|
380
389
|
}
|
|
381
390
|
);
|
package/composables/usePeople.ts
CHANGED
package/composables/useUnits.ts
CHANGED
|
@@ -54,9 +54,12 @@ export default function useUnits() {
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function uploadUnits(file: File, site: string) {
|
|
57
|
+
function uploadUnits(file: File, site: string, serviceType?: string) {
|
|
58
58
|
const formData = new FormData();
|
|
59
59
|
formData.append("file", file);
|
|
60
|
+
if (serviceType) {
|
|
61
|
+
formData.append("serviceType", serviceType);
|
|
62
|
+
}
|
|
60
63
|
|
|
61
64
|
return useNuxtApp().$api<Record<string, any>>(
|
|
62
65
|
`/api/hygiene-units/site/${site}/upload`,
|
package/package.json
CHANGED
package/types/vehicle.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ declare type TVehicle = {
|
|
|
19
19
|
_id?: string;
|
|
20
20
|
recNo?: string;
|
|
21
21
|
status?: TVehicleStatus;
|
|
22
|
+
peopleId?: string; // For linking to a people record, if applicable
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
declare type TVehicleType = "whitelist" | "blocklist";
|
|
@@ -41,4 +42,5 @@ declare type TVehiclePayload = Pick<
|
|
|
41
42
|
| "end"
|
|
42
43
|
| "site"
|
|
43
44
|
| "org"
|
|
45
|
+
| "peopleId"
|
|
44
46
|
> & { plateNumber: string[] };
|