@7365admin1/layer-common 1.10.3 → 1.10.4

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.
@@ -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",
@@ -54,7 +54,7 @@
54
54
  <v-col v-if="shouldShowField('nric')" cols="12">
55
55
  <v-row>
56
56
  <v-col cols="12">
57
- <InputLabel class="text-capitalize" title="NRIC/Passport/ID No." required />
57
+ <InputLabel class="text-capitalize" title="NRIC" required />
58
58
  <InputNRICNumber v-model="visitor.nric" density="comfortable" :rules="[requiredRule]"
59
59
  @update:model-value="handleUpdateNRIC" :loading="fetchPersonByNRICPending" />
60
60
  </v-col>
@@ -148,6 +148,13 @@ export default function useAccessManagement() {
148
148
  );
149
149
  }
150
150
 
151
+ function deleteCard(payload: { cardId: string }) {
152
+ return useNuxtApp().$api(`/api/access-management/delete-card`, {
153
+ method: "PATCH",
154
+ body: payload,
155
+ });
156
+ }
157
+
151
158
  return {
152
159
  getDoorAccessLevels,
153
160
  getLiftAccessLevels,
@@ -159,5 +166,6 @@ export default function useAccessManagement() {
159
166
  bulkPhysicalAccessCard,
160
167
  assignAccessCard,
161
168
  getAvailableAccessCards,
169
+ deleteCard,
162
170
  };
163
171
  }
@@ -1,11 +1,11 @@
1
1
  export default function(){
2
2
 
3
3
  const recipientList: { title: string, value: TAnnouncementRecipients }[] = [
4
- { title: "Admin", value: "admin" },
5
- { title: "Management Agency", value: "organization" },
6
- { title: "Site Personnel", value: "site" },
7
- { title: "Service Provider", value: "service-provider" },
8
- { title: "Service Provider Member", value: "service-provider-member" },
4
+ // resident | security_agency | cleaning_services | mechanical_electrical | property_management_agency
5
+ { title: "Security Agency", value: "security_agency" },
6
+ { title: "Cleaning Services", value: "cleaning_services" },
7
+ { title: "Mechanical & Electrical", value: "mechanical_electrical" },
8
+ { title: "Property Management Agency", value: "property_management_agency" },
9
9
  { title: "Resident", value: "resident" }
10
10
  ]
11
11
 
@@ -39,10 +39,24 @@ export default function useCard() {
39
39
  });
40
40
  }
41
41
 
42
+ function replaceCard(payload: {
43
+ cardId: string;
44
+ issuedCardId: string;
45
+ unitId: string;
46
+ remarks: string;
47
+ userId: string;
48
+ }) {
49
+ return useNuxtApp().$api("/api/access-management/card-replacement", {
50
+ method: "POST",
51
+ body: payload,
52
+ });
53
+ }
54
+
42
55
  return {
43
56
  getAll,
44
57
  add,
45
58
  deleteById,
46
59
  updateById,
60
+ replaceCard,
47
61
  };
48
62
  }
@@ -31,8 +31,7 @@ export default function useScheduleTask() {
31
31
  function createScheduleTask(payload: {
32
32
  title: string;
33
33
  time: string;
34
- startDate?: string;
35
- endDate?: string;
34
+ dates: string[];
36
35
  description?: string;
37
36
  areas: { name: string; value: string }[];
38
37
  site: string;
@@ -44,8 +43,7 @@ export default function useScheduleTask() {
44
43
  body: {
45
44
  title: payload.title,
46
45
  time: payload.time,
47
- startDate: payload.startDate,
48
- endDate: payload.endDate,
46
+ dates: payload.dates,
49
47
  description: payload.description,
50
48
  areas: payload.areas,
51
49
  },
@@ -58,8 +56,7 @@ export default function useScheduleTask() {
58
56
  payload: {
59
57
  title: string;
60
58
  time: string;
61
- startDate?: string;
62
- endDate?: string;
59
+ dates: string[];
63
60
  description?: string;
64
61
  areas: { name: string; value: string }[];
65
62
  },
@@ -71,8 +68,7 @@ export default function useScheduleTask() {
71
68
  body: {
72
69
  title: payload.title,
73
70
  time: payload.time,
74
- startDate: payload.startDate,
75
- endDate: payload.endDate,
71
+ dates: payload.dates,
76
72
  description: payload.description,
77
73
  areas: payload.areas,
78
74
  },
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.10.3",
5
+ "version": "1.10.4",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -20,7 +20,7 @@ declare interface TAnnouncementFile {
20
20
  preview: boolean;
21
21
  }
22
22
 
23
- declare type TAnnouncementRecipients = "admin" | "organization" | "site" | "service-provider" | "service-provider-member" | "resident";
23
+ declare type TAnnouncementRecipients = "resident" | "security_agency" | "cleaning_services" | "mechanical_electrical" | "property_management_agency"
24
24
 
25
25
 
26
26
 
@@ -22,6 +22,7 @@ declare type TCheckoutItem = {
22
22
  supply: string;
23
23
  supplyName: string;
24
24
  qty: number;
25
+ stockQty?: number;
25
26
  photos: string[];
26
27
  photoIds: string[];
27
28
  };
@@ -50,5 +50,5 @@ declare type TFlattenedUnitItem = TUnitChecklistItem & {
50
50
  set: number;
51
51
  };
52
52
 
53
- declare type TScheduleAreaStatus = "All" | "Ready" | "Ongoing" | "Completed";
53
+ declare type TScheduleAreaStatus = "All" | "Open" | "Ongoing" | "Completed";
54
54
  declare type TAreaType = "all" | "common" | "toilet";
package/types/people.d.ts CHANGED
@@ -15,7 +15,7 @@ declare type TPeople = {
15
15
  end?: string;
16
16
  org?: string;
17
17
  site?: string,
18
- type?: TPeoplePayload;
18
+ type?: TPeopleType;
19
19
  };
20
20
 
21
21