@7365admin1/layer-common 1.10.0 → 1.10.1

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 (81) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/AcceptDialog.vue +44 -0
  3. package/components/AccessCardAddForm.vue +101 -13
  4. package/components/AccessManagement.vue +130 -47
  5. package/components/AddSupplyForm.vue +165 -0
  6. package/components/AreaChecklistHistoryLogs.vue +235 -0
  7. package/components/AreaChecklistHistoryMain.vue +176 -0
  8. package/components/AreaFormDialog.vue +266 -0
  9. package/components/AreaMain.vue +841 -0
  10. package/components/AttendanceCheckInOutDialog.vue +416 -0
  11. package/components/AttendanceDetailsDialog.vue +184 -0
  12. package/components/AttendanceMain.vue +155 -0
  13. package/components/AttendanceMapSearchDialog.vue +393 -0
  14. package/components/AttendanceSettingsDialog.vue +398 -0
  15. package/components/BuildingManagement/buildings.vue +5 -5
  16. package/components/BuildingManagement/units.vue +5 -5
  17. package/components/ChecklistItemRow.vue +54 -0
  18. package/components/CheckoutItemMain.vue +705 -0
  19. package/components/CleaningScheduleMain.vue +271 -0
  20. package/components/DocumentManagement.vue +4 -0
  21. package/components/EntryPass/QrTemplatePreview.vue +104 -0
  22. package/components/EntryPassMain.vue +252 -200
  23. package/components/HygieneUpdateMoreAction.vue +238 -0
  24. package/components/ManageChecklistMain.vue +384 -0
  25. package/components/MemberMain.vue +48 -20
  26. package/components/MyAttendanceMain.vue +224 -0
  27. package/components/OnlineFormsConfiguration.vue +9 -2
  28. package/components/PhotoUpload.vue +410 -0
  29. package/components/ScheduleAreaMain.vue +313 -0
  30. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  31. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  32. package/components/ScheduleTaskForm.vue +471 -0
  33. package/components/ScheduleTaskMain.vue +345 -0
  34. package/components/ScheduleTastTicketMain.vue +182 -0
  35. package/components/StockCard.vue +191 -0
  36. package/components/SupplyManagementMain.vue +557 -0
  37. package/components/TableHygiene.vue +617 -0
  38. package/components/UnitMain.vue +451 -0
  39. package/components/VisitorManagement.vue +28 -15
  40. package/composables/useAccessManagement.ts +90 -0
  41. package/composables/useAreaPermission.ts +51 -0
  42. package/composables/useAreas.ts +99 -0
  43. package/composables/useAttendance.ts +89 -0
  44. package/composables/useAttendancePermission.ts +68 -0
  45. package/composables/useBuilding.ts +2 -2
  46. package/composables/useBuildingUnit.ts +2 -2
  47. package/composables/useCard.ts +2 -0
  48. package/composables/useCheckout.ts +61 -0
  49. package/composables/useCheckoutPermission.ts +80 -0
  50. package/composables/useCleaningPermission.ts +229 -0
  51. package/composables/useCleaningSchedulePermission.ts +58 -0
  52. package/composables/useCleaningSchedules.ts +233 -0
  53. package/composables/useCountry.ts +8 -0
  54. package/composables/useDashboardData.ts +2 -2
  55. package/composables/useFeedback.ts +1 -1
  56. package/composables/useLocation.ts +78 -0
  57. package/composables/useOnlineForm.ts +16 -9
  58. package/composables/usePeople.ts +87 -72
  59. package/composables/useQR.ts +29 -0
  60. package/composables/useScheduleTask.ts +89 -0
  61. package/composables/useScheduleTaskArea.ts +85 -0
  62. package/composables/useScheduleTaskPermission.ts +68 -0
  63. package/composables/useSiteEntryPassSettings.ts +4 -15
  64. package/composables/useStock.ts +45 -0
  65. package/composables/useSupply.ts +63 -0
  66. package/composables/useSupplyPermission.ts +92 -0
  67. package/composables/useUnitPermission.ts +51 -0
  68. package/composables/useUnits.ts +82 -0
  69. package/composables/useWebUsb.ts +389 -0
  70. package/composables/useWorkOrder.ts +1 -1
  71. package/nuxt.config.ts +3 -0
  72. package/package.json +4 -1
  73. package/types/area.d.ts +22 -0
  74. package/types/attendance.d.ts +38 -0
  75. package/types/checkout-item.d.ts +27 -0
  76. package/types/cleaner-schedule.d.ts +54 -0
  77. package/types/location.d.ts +42 -0
  78. package/types/schedule-task.d.ts +18 -0
  79. package/types/stock.d.ts +16 -0
  80. package/types/supply.d.ts +11 -0
  81. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,471 @@
1
+ <template>
2
+ <v-dialog v-model="show" max-width="680" persistent>
3
+ <v-card class="d-flex flex-column" max-height="90vh">
4
+ <v-toolbar flat color="grey-lighten-4">
5
+ <v-row no-gutters class="fill-height px-6" align="center">
6
+ <v-toolbar-title class="text-h6 font-weight-bold">
7
+ {{
8
+ props.mode === "edit"
9
+ ? "Edit Schedule Task"
10
+ : "Create Schedule Task"
11
+ }}
12
+ </v-toolbar-title>
13
+ <v-spacer />
14
+ <v-btn
15
+ icon="mdi-close"
16
+ variant="text"
17
+ @click="close"
18
+ :disabled="submitting"
19
+ />
20
+ </v-row>
21
+ </v-toolbar>
22
+
23
+ <v-card-text class="pa-8 flex-grow-1" style="overflow-y: auto">
24
+ <v-form ref="formRef" v-model="valid">
25
+ <v-row dense>
26
+ <v-col cols="12">
27
+ <InputLabel for="title" title="Title" required />
28
+ <v-text-field
29
+ id="title"
30
+ v-model="subject"
31
+ variant="outlined"
32
+ density="comfortable"
33
+ placeholder="Enter task title"
34
+ hide-details="auto"
35
+ class="mb-4"
36
+ />
37
+ </v-col>
38
+
39
+ <v-col cols="12">
40
+ <InputLabel for="date-range" title="Date Range" required />
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
+ />
64
+ </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
+ </v-sheet>
92
+ </v-col>
93
+
94
+ <v-col cols="12">
95
+ <InputLabel for="schedule-time" title="Time" required />
96
+ <v-text-field
97
+ id="schedule-time"
98
+ v-model="scheduleTime"
99
+ type="time"
100
+ variant="outlined"
101
+ density="comfortable"
102
+ hide-details="auto"
103
+ prepend-inner-icon="mdi-clock-outline"
104
+ class="mb-4"
105
+ />
106
+ </v-col>
107
+
108
+ <v-col cols="12">
109
+ <InputLabel for="selected-areas" title="Select Areas" required />
110
+ <v-select
111
+ id="selected-areas"
112
+ v-model="selectedAreas"
113
+ :items="areasList"
114
+ item-title="name"
115
+ item-value="value"
116
+ return-object
117
+ multiple
118
+ variant="outlined"
119
+ density="comfortable"
120
+ :loading="loadingAreas"
121
+ placeholder="Choose one or more areas"
122
+ hide-details="auto"
123
+ class="mb-4"
124
+ chips
125
+ closable-chips
126
+ >
127
+ <template v-slot:selection="{ item, index }">
128
+ <v-chip
129
+ v-if="index < 3"
130
+ size="small"
131
+ closable
132
+ @click:close="selectedAreas.splice(index, 1)"
133
+ >
134
+ {{ item.title }}
135
+ </v-chip>
136
+ <span
137
+ v-if="index === 3"
138
+ class="text-grey text-caption align-self-center"
139
+ >
140
+ (+{{ selectedAreas.length - 3 }} others)
141
+ </span>
142
+ </template>
143
+ </v-select>
144
+ </v-col>
145
+
146
+ <v-col cols="12">
147
+ <InputLabel for="description" title="Task Description" />
148
+ <v-textarea
149
+ id="description"
150
+ v-model="description"
151
+ variant="outlined"
152
+ density="comfortable"
153
+ rows="4"
154
+ placeholder="Add additional details about this task..."
155
+ hide-details="auto"
156
+ class="mb-2"
157
+ />
158
+ </v-col>
159
+ </v-row>
160
+ </v-form>
161
+ </v-card-text>
162
+
163
+ <v-divider />
164
+
165
+ <v-card-actions class="pa-0">
166
+ <v-row no-gutters class="fill-width">
167
+ <v-col cols="6" class="pa-0">
168
+ <v-btn
169
+ block
170
+ variant="text"
171
+ class="text-none rounded-0"
172
+ size="x-large"
173
+ @click="close"
174
+ height="56"
175
+ :disabled="submitting"
176
+ >
177
+ Cancel
178
+ </v-btn>
179
+ </v-col>
180
+ <v-col cols="6" class="pa-0">
181
+ <v-btn
182
+ block
183
+ variant="flat"
184
+ color="primary"
185
+ class="text-none font-weight-bold rounded-0"
186
+ height="56"
187
+ @click="submitTask"
188
+ :loading="submitting"
189
+ >
190
+ {{ props.mode === "edit" ? "Save Changes" : "Add Schedule Task" }}
191
+ </v-btn>
192
+ </v-col>
193
+ </v-row>
194
+ </v-card-actions>
195
+ </v-card>
196
+ </v-dialog>
197
+
198
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
199
+ </template>
200
+
201
+ <script setup lang="ts">
202
+ const props = defineProps({
203
+ mode: {
204
+ type: String as PropType<"add" | "edit">,
205
+ default: "add",
206
+ },
207
+ taskData: {
208
+ type: Object,
209
+ default: null,
210
+ },
211
+ site: {
212
+ type: String,
213
+ required: true,
214
+ },
215
+ });
216
+
217
+ const show = defineModel("show", { type: Boolean, default: false });
218
+ const emit = defineEmits(["saved", "close", "refresh"]);
219
+
220
+ const { getAreas } = useAreas();
221
+ const { createScheduleTask, updateScheduleTask, getScheduleTaskById } =
222
+ useScheduleTask();
223
+
224
+ const formRef = ref<HTMLFormElement | null>(null);
225
+ const valid = ref(false);
226
+ const submitting = ref(false);
227
+ const loadingTask = ref(false);
228
+ const showCalendar = ref(false);
229
+
230
+ const subject = ref("");
231
+ const description = ref("");
232
+ const scheduleTime = ref("08:00");
233
+ const startDateInput = ref("");
234
+ const endDateInput = ref("");
235
+ const pickerDates = ref<Date[]>([]);
236
+ const selectedAreas = ref<any[]>([]);
237
+
238
+ const message = ref("");
239
+ const messageSnackbar = ref(false);
240
+ const messageColor = ref("success");
241
+ const areasList = ref<any[]>([]);
242
+
243
+ // Helper function to generate all dates between start and end
244
+ const generateDateRange = (start: Date, end: Date): Date[] => {
245
+ const dates: Date[] = [];
246
+ const current = new Date(start);
247
+
248
+ while (current <= end) {
249
+ dates.push(new Date(current));
250
+ current.setDate(current.getDate() + 1);
251
+ }
252
+
253
+ return dates;
254
+ };
255
+
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
+ }
278
+ };
279
+
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
+ }
295
+ };
296
+
297
+ const showMessage = (text: string, color: string = "success") => {
298
+ message.value = text;
299
+ messageColor.value = color;
300
+ messageSnackbar.value = true;
301
+ };
302
+
303
+ const resetForm = () => {
304
+ subject.value = "";
305
+ description.value = "";
306
+ scheduleTime.value = "08:00";
307
+ startDateInput.value = "";
308
+ endDateInput.value = "";
309
+ pickerDates.value = [];
310
+ selectedAreas.value = [];
311
+ showCalendar.value = false;
312
+ };
313
+
314
+ const setFormFromTask = (task: any) => {
315
+ if (!task) return;
316
+
317
+ subject.value = task.title || "";
318
+ description.value = task.description || "";
319
+ scheduleTime.value = task.time || "08:00";
320
+
321
+ const toISODate = (d: any) => {
322
+ if (!d) return null;
323
+ try {
324
+ const dt = new Date(d);
325
+ if (isNaN(dt.getTime())) return null;
326
+ return dt.toISOString().slice(0, 10);
327
+ } catch (e) {
328
+ return null;
329
+ }
330
+ };
331
+
332
+ if (task.startDate || task.endDate) {
333
+ const start = toISODate(task.startDate || task.start);
334
+ const end = toISODate(task.endDate || task.end);
335
+
336
+ if (start) startDateInput.value = start;
337
+ if (end) endDateInput.value = end;
338
+
339
+ if (start && end) {
340
+ pickerDates.value = generateDateRange(new Date(start), new Date(end));
341
+ }
342
+ }
343
+
344
+ selectedAreas.value = Array.isArray(task.areas)
345
+ ? task.areas.map((area: any) => ({
346
+ _id: area.value,
347
+ name: area.name,
348
+ value: area.value,
349
+ }))
350
+ : [];
351
+ };
352
+
353
+ const { data: getAreasReq, pending: loadingAreas } = await useLazyAsyncData(
354
+ `get-areas-${props.site}`,
355
+ () => getAreas({ site: props.site, limit: 1000 }),
356
+ {
357
+ watch: [show, () => props.site],
358
+ immediate: false,
359
+ },
360
+ );
361
+
362
+ watchEffect(() => {
363
+ if (getAreasReq.value) {
364
+ areasList.value = (getAreasReq.value?.items || []).map((item: any) => ({
365
+ ...item,
366
+ value: item._id || item.value,
367
+ }));
368
+ }
369
+ });
370
+
371
+ const loadTaskData = async () => {
372
+ if (props.mode !== "edit") return;
373
+
374
+ const id = (props.taskData as any)?._id || (props.taskData as any)?.id;
375
+
376
+ if (!id) {
377
+ if ((props.taskData as any)?.title) setFormFromTask(props.taskData);
378
+ return;
379
+ }
380
+
381
+ loadingTask.value = true;
382
+ try {
383
+ const response = await getScheduleTaskById(id);
384
+
385
+ if (response) {
386
+ setFormFromTask(response as any);
387
+ } else if ((props.taskData as any)?.title) {
388
+ setFormFromTask(props.taskData);
389
+ }
390
+ } catch (error: any) {
391
+ if ((props.taskData as any)?.title) setFormFromTask(props.taskData);
392
+ showMessage(
393
+ error?.data?.message || "Failed to load schedule task",
394
+ "error",
395
+ );
396
+ } finally {
397
+ loadingTask.value = false;
398
+ }
399
+ };
400
+
401
+ const submitTask = async () => {
402
+ submitting.value = true;
403
+ try {
404
+ const payload = {
405
+ title: subject.value,
406
+ time: scheduleTime.value,
407
+ startDate: startDateInput.value,
408
+ endDate: endDateInput.value,
409
+ description: description.value?.trim() || "",
410
+ areas: selectedAreas.value.map((area: any) => ({
411
+ name: area.name || area.title || "",
412
+ value: area._id || area.value || area,
413
+ })),
414
+ };
415
+
416
+ if (startDateInput.value) {
417
+ payload.startDate = startDateInput.value;
418
+ }
419
+
420
+ if (endDateInput.value) {
421
+ payload.endDate = endDateInput.value;
422
+ }
423
+
424
+ let response;
425
+
426
+ if (props.mode === "edit" && props.taskData) {
427
+ const taskId =
428
+ (props.taskData as any)?._id || (props.taskData as any)?.id;
429
+ response = await updateScheduleTask(taskId, payload);
430
+ } else {
431
+ response = await createScheduleTask({ ...payload, site: props.site });
432
+ }
433
+
434
+ showMessage(
435
+ response?.message ||
436
+ `Schedule task ${
437
+ props.mode === "edit" ? "updated" : "created"
438
+ } successfully`,
439
+ "success",
440
+ );
441
+
442
+ emit("saved");
443
+ emit("refresh");
444
+ close();
445
+ } catch (error: any) {
446
+ showMessage(
447
+ error?.data?.message || "Failed to submit schedule task",
448
+ "error",
449
+ );
450
+ } finally {
451
+ submitting.value = false;
452
+ }
453
+ };
454
+
455
+ const close = () => {
456
+ show.value = false;
457
+ emit("close");
458
+ };
459
+
460
+ watch(
461
+ [show, () => props.mode, () => props.taskData],
462
+ async ([isOpen, mode, taskData]) => {
463
+ if (isOpen && mode === "edit") {
464
+ await loadTaskData();
465
+ } else if (!isOpen) {
466
+ resetForm();
467
+ }
468
+ },
469
+ { immediate: false },
470
+ );
471
+ </script>