@7365admin1/layer-common 1.10.3 → 1.10.5
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/AccessCardDeleteDialog.vue +109 -0
- package/components/AccessCardHistoryDialog.vue +133 -0
- package/components/AccessCardPreviewDialog.vue +308 -0
- package/components/AccessCardQrTagging.vue +183 -0
- package/components/AccessCardReplaceForm.vue +179 -0
- package/components/AccessManagement.vue +61 -251
- package/components/AreaChecklistHistoryLogs.vue +1 -1
- package/components/BuildingManagement/units.vue +33 -2
- package/components/BuildingUnitFormAdd.vue +45 -99
- package/components/BuildingUnitFormEdit.vue +59 -148
- package/components/BulletinBoardManagement.vue +6 -1
- package/components/BulletinBoardView.vue +2 -2
- package/components/Button/Close.vue +3 -1
- package/components/CleaningScheduleMain.vue +20 -9
- package/components/IncidentReport/IncidentInformation.vue +45 -6
- package/components/IncidentReport/affectedEntities.vue +29 -0
- package/components/PeopleForm.vue +1 -1
- package/components/PlateNumberDisplay.vue +45 -0
- package/components/ScheduleAreaMain.vue +5 -2
- package/components/ScheduleTaskForm.vue +59 -114
- package/components/ScheduleTaskMain.vue +19 -15
- package/components/VehicleAddSelection.vue +58 -0
- package/components/VehicleForm.vue +600 -0
- package/components/VehicleManagement.vue +298 -0
- package/components/VisitorForm.vue +1 -1
- package/composables/useAccessManagement.ts +16 -0
- package/composables/useBulletin.ts +11 -9
- package/composables/useCard.ts +14 -0
- package/composables/useScheduleTask.ts +4 -8
- package/composables/useVehicle.ts +114 -0
- package/package.json +1 -1
- package/types/bulletin-board.d.ts +1 -1
- package/types/checkout-item.d.ts +1 -0
- package/types/cleaner-schedule.d.ts +1 -1
- package/types/people.d.ts +1 -1
- package/types/vehicle.d.ts +43 -0
|
@@ -181,7 +181,7 @@ const items = ref<Array<Record<string, any>>>([]);
|
|
|
181
181
|
const statusFilter = ref<TScheduleAreaStatus>("All");
|
|
182
182
|
const statusOptions = [
|
|
183
183
|
{ title: "All", value: "All" },
|
|
184
|
-
{ title: "
|
|
184
|
+
{ title: "Open", value: "Open" },
|
|
185
185
|
{ title: "Ongoing", value: "Ongoing" },
|
|
186
186
|
{ title: "Completed", value: "Completed" },
|
|
187
187
|
];
|
|
@@ -236,13 +236,16 @@ const getStatusColor = (status: unknown): string => {
|
|
|
236
236
|
const normalized = String(status).toLowerCase();
|
|
237
237
|
|
|
238
238
|
switch (normalized) {
|
|
239
|
-
case "
|
|
239
|
+
case "open":
|
|
240
240
|
return "grey";
|
|
241
241
|
case "ongoing":
|
|
242
242
|
return "primary";
|
|
243
243
|
case "completed":
|
|
244
244
|
case "accepted":
|
|
245
245
|
return "success";
|
|
246
|
+
case "closed":
|
|
247
|
+
case "rejected":
|
|
248
|
+
return "error";
|
|
246
249
|
default:
|
|
247
250
|
return "secondary";
|
|
248
251
|
}
|
|
@@ -37,57 +37,35 @@
|
|
|
37
37
|
</v-col>
|
|
38
38
|
|
|
39
39
|
<v-col cols="12">
|
|
40
|
-
<InputLabel for="date-range" title="
|
|
40
|
+
<InputLabel for="date-range" title="Select Dates" required />
|
|
41
41
|
<v-sheet border rounded class="pa-4 mb-4" color="grey-lighten-5">
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
<v-date-picker
|
|
43
|
+
v-model="pickerDates"
|
|
44
|
+
multiple
|
|
45
|
+
:show-current="false"
|
|
46
|
+
color="primary"
|
|
47
|
+
elevation="0"
|
|
48
|
+
width="100%"
|
|
49
|
+
class="ma-0"
|
|
50
|
+
/>
|
|
51
|
+
<v-divider class="my-3" />
|
|
52
|
+
<div class="text-caption text-medium-emphasis mb-2">
|
|
53
|
+
{{ selectedDates.length }} date(s) selected
|
|
54
|
+
</div>
|
|
55
|
+
<div v-if="selectedDates.length > 0" class="d-flex flex-wrap gap-1">
|
|
56
|
+
<v-chip
|
|
57
|
+
v-for="date in selectedDates"
|
|
58
|
+
:key="date"
|
|
59
|
+
size="small"
|
|
60
|
+
closable
|
|
61
|
+
@click:close="removeDate(date)"
|
|
62
|
+
>
|
|
63
|
+
{{ date }}
|
|
64
|
+
</v-chip>
|
|
65
|
+
</div>
|
|
66
|
+
<div v-else class="text-caption text-disabled">
|
|
67
|
+
No dates selected. Click on the calendar to select dates.
|
|
64
68
|
</div>
|
|
65
|
-
|
|
66
|
-
<v-expand-transition>
|
|
67
|
-
<div v-if="showCalendar">
|
|
68
|
-
<v-divider class="mb-3" />
|
|
69
|
-
<v-date-picker
|
|
70
|
-
v-model="pickerDates"
|
|
71
|
-
multiple
|
|
72
|
-
@update:model-value="handlePickerChange"
|
|
73
|
-
:show-current="false"
|
|
74
|
-
color="primary"
|
|
75
|
-
elevation="0"
|
|
76
|
-
width="100%"
|
|
77
|
-
class="ma-0"
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
</v-expand-transition>
|
|
81
|
-
|
|
82
|
-
<v-btn
|
|
83
|
-
variant="text"
|
|
84
|
-
size="small"
|
|
85
|
-
@click="showCalendar = !showCalendar"
|
|
86
|
-
class="mt-2"
|
|
87
|
-
prepend-icon="mdi-calendar"
|
|
88
|
-
>
|
|
89
|
-
{{ showCalendar ? "Hide Calendar" : "Show Calendar" }}
|
|
90
|
-
</v-btn>
|
|
91
69
|
</v-sheet>
|
|
92
70
|
</v-col>
|
|
93
71
|
|
|
@@ -199,6 +177,9 @@
|
|
|
199
177
|
</template>
|
|
200
178
|
|
|
201
179
|
<script setup lang="ts">
|
|
180
|
+
import useAreas from "../composables/useAreas";
|
|
181
|
+
import useScheduleTask from "../composables/useScheduleTask";
|
|
182
|
+
|
|
202
183
|
const props = defineProps({
|
|
203
184
|
mode: {
|
|
204
185
|
type: String as PropType<"add" | "edit">,
|
|
@@ -225,14 +206,16 @@ const formRef = ref<HTMLFormElement | null>(null);
|
|
|
225
206
|
const valid = ref(false);
|
|
226
207
|
const submitting = ref(false);
|
|
227
208
|
const loadingTask = ref(false);
|
|
228
|
-
const showCalendar = ref(false);
|
|
229
209
|
|
|
230
210
|
const subject = ref("");
|
|
231
211
|
const description = ref("");
|
|
232
212
|
const scheduleTime = ref("08:00");
|
|
233
|
-
const startDateInput = ref("");
|
|
234
|
-
const endDateInput = ref("");
|
|
235
213
|
const pickerDates = ref<Date[]>([]);
|
|
214
|
+
// Derived from pickerDates — single source of truth, always in sync
|
|
215
|
+
const selectedDates = computed(() =>
|
|
216
|
+
pickerDates.value.map((d) => toLocalISO(d)).sort()
|
|
217
|
+
);
|
|
218
|
+
|
|
236
219
|
const selectedAreas = ref<any[]>([]);
|
|
237
220
|
|
|
238
221
|
const message = ref("");
|
|
@@ -253,45 +236,19 @@ const generateDateRange = (start: Date, end: Date): Date[] => {
|
|
|
253
236
|
return dates;
|
|
254
237
|
};
|
|
255
238
|
|
|
256
|
-
//
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (start <= end) {
|
|
263
|
-
pickerDates.value = generateDateRange(start, end);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
// Handle changes from the end date input
|
|
269
|
-
const handleEndDateChange = (value: string) => {
|
|
270
|
-
if (startDateInput.value && value) {
|
|
271
|
-
const start = new Date(startDateInput.value);
|
|
272
|
-
const end = new Date(value);
|
|
273
|
-
|
|
274
|
-
if (start <= end) {
|
|
275
|
-
pickerDates.value = generateDateRange(start, end);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
239
|
+
// Helper: convert a Date to a local YYYY-MM-DD string (avoids UTC-shift issues)
|
|
240
|
+
const toLocalISO = (d: Date): string => {
|
|
241
|
+
const y = d.getFullYear();
|
|
242
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
243
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
244
|
+
return `${y}-${m}-${day}`;
|
|
278
245
|
};
|
|
279
246
|
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const start = sortedDates[0];
|
|
286
|
-
const end = sortedDates[sortedDates.length - 1];
|
|
287
|
-
|
|
288
|
-
// Update input fields
|
|
289
|
-
startDateInput.value = start.toISOString().slice(0, 10);
|
|
290
|
-
endDateInput.value = end.toISOString().slice(0, 10);
|
|
291
|
-
|
|
292
|
-
// Fill in all dates between start and end
|
|
293
|
-
pickerDates.value = generateDateRange(start, end);
|
|
294
|
-
}
|
|
247
|
+
// Remove a single date from the selection via chip close
|
|
248
|
+
const removeDate = (dateISO: string) => {
|
|
249
|
+
pickerDates.value = pickerDates.value.filter(
|
|
250
|
+
(d) => toLocalISO(d) !== dateISO
|
|
251
|
+
);
|
|
295
252
|
};
|
|
296
253
|
|
|
297
254
|
const showMessage = (text: string, color: string = "success") => {
|
|
@@ -304,11 +261,8 @@ const resetForm = () => {
|
|
|
304
261
|
subject.value = "";
|
|
305
262
|
description.value = "";
|
|
306
263
|
scheduleTime.value = "08:00";
|
|
307
|
-
startDateInput.value = "";
|
|
308
|
-
endDateInput.value = "";
|
|
309
264
|
pickerDates.value = [];
|
|
310
265
|
selectedAreas.value = [];
|
|
311
|
-
showCalendar.value = false;
|
|
312
266
|
};
|
|
313
267
|
|
|
314
268
|
const setFormFromTask = (task: any) => {
|
|
@@ -329,13 +283,13 @@ const setFormFromTask = (task: any) => {
|
|
|
329
283
|
}
|
|
330
284
|
};
|
|
331
285
|
|
|
332
|
-
|
|
286
|
+
// Handle dates array from task
|
|
287
|
+
if (task.dates && Array.isArray(task.dates) && task.dates.length > 0) {
|
|
288
|
+
pickerDates.value = task.dates.map((d: string) => new Date(d));
|
|
289
|
+
} else if (task.startDate || task.endDate) {
|
|
290
|
+
// Fallback for old format (startDate/endDate)
|
|
333
291
|
const start = toISODate(task.startDate || task.start);
|
|
334
292
|
const end = toISODate(task.endDate || task.end);
|
|
335
|
-
|
|
336
|
-
if (start) startDateInput.value = start;
|
|
337
|
-
if (end) endDateInput.value = end;
|
|
338
|
-
|
|
339
293
|
if (start && end) {
|
|
340
294
|
pickerDates.value = generateDateRange(new Date(start), new Date(end));
|
|
341
295
|
}
|
|
@@ -343,9 +297,9 @@ const setFormFromTask = (task: any) => {
|
|
|
343
297
|
|
|
344
298
|
selectedAreas.value = Array.isArray(task.areas)
|
|
345
299
|
? task.areas.map((area: any) => ({
|
|
346
|
-
_id: area.value,
|
|
300
|
+
_id: area.value || area._id,
|
|
347
301
|
name: area.name,
|
|
348
|
-
value: area.value,
|
|
302
|
+
value: area.value || area._id,
|
|
349
303
|
}))
|
|
350
304
|
: [];
|
|
351
305
|
};
|
|
@@ -356,7 +310,7 @@ const { data: getAreasReq, pending: loadingAreas } = await useLazyAsyncData(
|
|
|
356
310
|
{
|
|
357
311
|
watch: [show, () => props.site],
|
|
358
312
|
immediate: false,
|
|
359
|
-
}
|
|
313
|
+
}
|
|
360
314
|
);
|
|
361
315
|
|
|
362
316
|
watchEffect(() => {
|
|
@@ -391,7 +345,7 @@ const loadTaskData = async () => {
|
|
|
391
345
|
if ((props.taskData as any)?.title) setFormFromTask(props.taskData);
|
|
392
346
|
showMessage(
|
|
393
347
|
error?.data?.message || "Failed to load schedule task",
|
|
394
|
-
"error"
|
|
348
|
+
"error"
|
|
395
349
|
);
|
|
396
350
|
} finally {
|
|
397
351
|
loadingTask.value = false;
|
|
@@ -404,8 +358,7 @@ const submitTask = async () => {
|
|
|
404
358
|
const payload = {
|
|
405
359
|
title: subject.value,
|
|
406
360
|
time: scheduleTime.value,
|
|
407
|
-
|
|
408
|
-
endDate: endDateInput.value,
|
|
361
|
+
dates: selectedDates.value, // Array of YYYY-MM-DD strings
|
|
409
362
|
description: description.value?.trim() || "",
|
|
410
363
|
areas: selectedAreas.value.map((area: any) => ({
|
|
411
364
|
name: area.name || area.title || "",
|
|
@@ -413,14 +366,6 @@ const submitTask = async () => {
|
|
|
413
366
|
})),
|
|
414
367
|
};
|
|
415
368
|
|
|
416
|
-
if (startDateInput.value) {
|
|
417
|
-
payload.startDate = startDateInput.value;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (endDateInput.value) {
|
|
421
|
-
payload.endDate = endDateInput.value;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
369
|
let response;
|
|
425
370
|
|
|
426
371
|
if (props.mode === "edit" && props.taskData) {
|
|
@@ -436,7 +381,7 @@ const submitTask = async () => {
|
|
|
436
381
|
`Schedule task ${
|
|
437
382
|
props.mode === "edit" ? "updated" : "created"
|
|
438
383
|
} successfully`,
|
|
439
|
-
"success"
|
|
384
|
+
"success"
|
|
440
385
|
);
|
|
441
386
|
|
|
442
387
|
emit("saved");
|
|
@@ -445,7 +390,7 @@ const submitTask = async () => {
|
|
|
445
390
|
} catch (error: any) {
|
|
446
391
|
showMessage(
|
|
447
392
|
error?.data?.message || "Failed to submit schedule task",
|
|
448
|
-
"error"
|
|
393
|
+
"error"
|
|
449
394
|
);
|
|
450
395
|
} finally {
|
|
451
396
|
submitting.value = false;
|
|
@@ -466,6 +411,6 @@ watch(
|
|
|
466
411
|
resetForm();
|
|
467
412
|
}
|
|
468
413
|
},
|
|
469
|
-
{ immediate: false }
|
|
414
|
+
{ immediate: false }
|
|
470
415
|
);
|
|
471
416
|
</script>
|
|
@@ -47,8 +47,6 @@
|
|
|
47
47
|
</v-row>
|
|
48
48
|
</template>
|
|
49
49
|
|
|
50
|
-
<!-- frequency removed: using explicit date range in details -->
|
|
51
|
-
|
|
52
50
|
<template #item.areas="{ value }">
|
|
53
51
|
<div v-if="value && value.length > 0">
|
|
54
52
|
<v-chip
|
|
@@ -107,21 +105,21 @@
|
|
|
107
105
|
|
|
108
106
|
<v-row no-gutters class="mb-2">
|
|
109
107
|
<v-col cols="5" class="text-subtitle-2 font-weight-bold">
|
|
110
|
-
|
|
108
|
+
Dates:
|
|
111
109
|
</v-col>
|
|
112
110
|
<v-col cols="7" class="text-subtitle-2">
|
|
113
|
-
<div
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
<div
|
|
112
|
+
v-if="selectedTask.dates && selectedTask.dates.length > 0"
|
|
113
|
+
class="d-flex flex-wrap gap-1"
|
|
114
|
+
>
|
|
115
|
+
<v-chip
|
|
116
|
+
v-for="date in selectedTask.dates"
|
|
117
|
+
:key="date"
|
|
118
|
+
size="x-small"
|
|
119
|
+
class="mr-1 mb-1"
|
|
120
|
+
>
|
|
121
|
+
{{ formatDateChip(date) }}
|
|
122
|
+
</v-chip>
|
|
125
123
|
</div>
|
|
126
124
|
<div v-else>N/A</div>
|
|
127
125
|
</v-col>
|
|
@@ -258,6 +256,12 @@ const showMessage = (text: string, color: string = "success") => {
|
|
|
258
256
|
messageSnackbar.value = true;
|
|
259
257
|
};
|
|
260
258
|
|
|
259
|
+
const formatDateChip = (dateStr: string) => {
|
|
260
|
+
const parts = dateStr?.split("-");
|
|
261
|
+
if (parts?.length === 3) return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
|
262
|
+
return dateStr;
|
|
263
|
+
};
|
|
264
|
+
|
|
261
265
|
const mapShortToDay = (short: string) => {
|
|
262
266
|
const mapping: Record<string, string> = {
|
|
263
267
|
Mon: "Monday",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
+
Add Vehicle
|
|
7
|
+
</span>
|
|
8
|
+
</v-row>
|
|
9
|
+
</v-toolbar>
|
|
10
|
+
|
|
11
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5">
|
|
12
|
+
<span class="text-subtitle-2 w-100 font-weight-medium d-flex justify-center mb-3">Please Select Status</span>
|
|
13
|
+
<template v-for="item in selection" :key="item.value">
|
|
14
|
+
<v-btn color="primary-button" block variant="flat" rounded="md" size="48" :text="item.label" @click="select(item.value)" class="my-2 text-capitalize text-subtitle-2" />
|
|
15
|
+
</template>
|
|
16
|
+
</v-card-text>
|
|
17
|
+
|
|
18
|
+
<v-toolbar density="compact">
|
|
19
|
+
<v-row no-gutters>
|
|
20
|
+
<v-col cols="12">
|
|
21
|
+
<v-btn
|
|
22
|
+
tile
|
|
23
|
+
block
|
|
24
|
+
variant="text"
|
|
25
|
+
class="text-none"
|
|
26
|
+
size="48"
|
|
27
|
+
@click="cancel"
|
|
28
|
+
>
|
|
29
|
+
Cancel
|
|
30
|
+
</v-btn>
|
|
31
|
+
</v-col>
|
|
32
|
+
</v-row>
|
|
33
|
+
</v-toolbar>
|
|
34
|
+
</v-card>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
const prop = defineProps({
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(['cancel', 'select']);
|
|
42
|
+
|
|
43
|
+
const selection = computed<{label: string, value: TVehicleType}[]>(() => {
|
|
44
|
+
return [
|
|
45
|
+
{ label: "Whitelist", value: "whitelist" },
|
|
46
|
+
{ label: "Season Pass", value: "seasonpass" },
|
|
47
|
+
{ label: "Blocklist", value: "blocklist" },
|
|
48
|
+
];
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function cancel() {
|
|
52
|
+
emit("cancel");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function select(value: string) {
|
|
56
|
+
emit("select", value);
|
|
57
|
+
}
|
|
58
|
+
</script>
|