@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AccessCardDeleteDialog.vue +109 -0
  3. package/components/AccessCardHistoryDialog.vue +133 -0
  4. package/components/AccessCardPreviewDialog.vue +308 -0
  5. package/components/AccessCardQrTagging.vue +183 -0
  6. package/components/AccessCardReplaceForm.vue +179 -0
  7. package/components/AccessManagement.vue +61 -251
  8. package/components/AreaChecklistHistoryLogs.vue +1 -1
  9. package/components/BuildingManagement/units.vue +33 -2
  10. package/components/BuildingUnitFormAdd.vue +45 -99
  11. package/components/BuildingUnitFormEdit.vue +59 -148
  12. package/components/BulletinBoardManagement.vue +6 -1
  13. package/components/BulletinBoardView.vue +2 -2
  14. package/components/Button/Close.vue +3 -1
  15. package/components/CleaningScheduleMain.vue +20 -9
  16. package/components/IncidentReport/IncidentInformation.vue +45 -6
  17. package/components/IncidentReport/affectedEntities.vue +29 -0
  18. package/components/PeopleForm.vue +1 -1
  19. package/components/PlateNumberDisplay.vue +45 -0
  20. package/components/ScheduleAreaMain.vue +5 -2
  21. package/components/ScheduleTaskForm.vue +59 -114
  22. package/components/ScheduleTaskMain.vue +19 -15
  23. package/components/VehicleAddSelection.vue +58 -0
  24. package/components/VehicleForm.vue +600 -0
  25. package/components/VehicleManagement.vue +298 -0
  26. package/components/VisitorForm.vue +1 -1
  27. package/composables/useAccessManagement.ts +16 -0
  28. package/composables/useBulletin.ts +11 -9
  29. package/composables/useCard.ts +14 -0
  30. package/composables/useScheduleTask.ts +4 -8
  31. package/composables/useVehicle.ts +114 -0
  32. package/package.json +1 -1
  33. package/types/bulletin-board.d.ts +1 -1
  34. package/types/checkout-item.d.ts +1 -0
  35. package/types/cleaner-schedule.d.ts +1 -1
  36. package/types/people.d.ts +1 -1
  37. 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: "Ready", value: "Ready" },
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 "ready":
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="Date Range" required />
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
- <div class="d-flex align-center gap-3 mb-3">
43
- <v-text-field
44
- v-model="startDateInput"
45
- type="date"
46
- variant="outlined"
47
- density="compact"
48
- label="Start Date"
49
- hide-details
50
- class="flex-grow-1"
51
- @update:model-value="handleStartDateChange"
52
- />
53
- <v-icon>mdi-arrow-right</v-icon>
54
- <v-text-field
55
- v-model="endDateInput"
56
- type="date"
57
- variant="outlined"
58
- density="compact"
59
- label="End Date"
60
- hide-details
61
- class="flex-grow-1"
62
- @update:model-value="handleEndDateChange"
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
- // Handle changes from the start date input
257
- const handleStartDateChange = (value: string) => {
258
- if (value && endDateInput.value) {
259
- const start = new Date(value);
260
- const end = new Date(endDateInput.value);
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
- // Handle changes from the calendar picker
281
- const handlePickerChange = (dates: Date[]) => {
282
- if (dates && dates.length > 0) {
283
- // Sort dates to find earliest and latest
284
- const sortedDates = [...dates].sort((a, b) => a.getTime() - b.getTime());
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
- if (task.startDate || task.endDate) {
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
- startDate: startDateInput.value,
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
- Date Range:
108
+ Dates:
111
109
  </v-col>
112
110
  <v-col cols="7" class="text-subtitle-2">
113
- <div v-if="selectedTask.startDate || selectedTask.endDate">
114
- {{
115
- selectedTask.startDate
116
- ? formatDate(selectedTask.startDate)
117
- : "N/A"
118
- }}
119
- -
120
- {{
121
- selectedTask.endDate
122
- ? formatDate(selectedTask.endDate)
123
- : "N/A"
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>