@7365admin1/layer-common 1.9.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 (93) hide show
  1. package/CHANGELOG.md +12 -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 +8 -9
  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/IncidentReport/Authorities.vue +226 -0
  25. package/components/IncidentReport/IncidentInformation.vue +258 -0
  26. package/components/IncidentReport/affectedEntities.vue +167 -0
  27. package/components/InvitationMain.vue +19 -17
  28. package/components/ManageChecklistMain.vue +384 -0
  29. package/components/MemberMain.vue +48 -20
  30. package/components/MyAttendanceMain.vue +224 -0
  31. package/components/OnlineFormsConfiguration.vue +9 -2
  32. package/components/PasswordConfirmation.vue +95 -0
  33. package/components/PhotoUpload.vue +410 -0
  34. package/components/RolePermissionMain.vue +17 -15
  35. package/components/ScheduleAreaMain.vue +313 -0
  36. package/components/ScheduleTaskAreaFormDialog.vue +144 -0
  37. package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
  38. package/components/ScheduleTaskForm.vue +471 -0
  39. package/components/ScheduleTaskMain.vue +345 -0
  40. package/components/ScheduleTastTicketMain.vue +182 -0
  41. package/components/ServiceProviderMain.vue +27 -7
  42. package/components/StockCard.vue +191 -0
  43. package/components/SupplyManagementMain.vue +557 -0
  44. package/components/TableHygiene.vue +617 -0
  45. package/components/UnitMain.vue +451 -0
  46. package/components/VisitorManagement.vue +28 -15
  47. package/composables/useAccessManagement.ts +90 -0
  48. package/composables/useAreaPermission.ts +51 -0
  49. package/composables/useAreas.ts +99 -0
  50. package/composables/useAttendance.ts +89 -0
  51. package/composables/useAttendancePermission.ts +68 -0
  52. package/composables/useBuilding.ts +2 -2
  53. package/composables/useBuildingUnit.ts +2 -2
  54. package/composables/useCard.ts +2 -0
  55. package/composables/useCheckout.ts +61 -0
  56. package/composables/useCheckoutPermission.ts +80 -0
  57. package/composables/useCleaningPermission.ts +229 -0
  58. package/composables/useCleaningSchedulePermission.ts +58 -0
  59. package/composables/useCleaningSchedules.ts +233 -0
  60. package/composables/useCountry.ts +8 -0
  61. package/composables/useDOBEntries.ts +13 -0
  62. package/composables/useDashboardData.ts +2 -2
  63. package/composables/useDocument.ts +3 -2
  64. package/composables/useFeedback.ts +1 -1
  65. package/composables/useFile.ts +4 -6
  66. package/composables/useLocation.ts +78 -0
  67. package/composables/useOnlineForm.ts +16 -9
  68. package/composables/usePeople.ts +87 -72
  69. package/composables/useQR.ts +29 -0
  70. package/composables/useRole.ts +3 -2
  71. package/composables/useScheduleTask.ts +89 -0
  72. package/composables/useScheduleTaskArea.ts +85 -0
  73. package/composables/useScheduleTaskPermission.ts +68 -0
  74. package/composables/useSiteEntryPassSettings.ts +4 -15
  75. package/composables/useStock.ts +45 -0
  76. package/composables/useSupply.ts +63 -0
  77. package/composables/useSupplyPermission.ts +92 -0
  78. package/composables/useUnitPermission.ts +51 -0
  79. package/composables/useUnits.ts +82 -0
  80. package/composables/useWebUsb.ts +389 -0
  81. package/composables/useWorkOrder.ts +1 -1
  82. package/nuxt.config.ts +3 -0
  83. package/package.json +4 -1
  84. package/types/area.d.ts +22 -0
  85. package/types/attendance.d.ts +38 -0
  86. package/types/checkout-item.d.ts +27 -0
  87. package/types/cleaner-schedule.d.ts +54 -0
  88. package/types/location.d.ts +42 -0
  89. package/types/schedule-task.d.ts +18 -0
  90. package/types/stock.d.ts +16 -0
  91. package/types/supply.d.ts +11 -0
  92. package/types/verification.d.ts +1 -1
  93. package/utils/acm-crypto.ts +30 -0
@@ -0,0 +1,271 @@
1
+ <template>
2
+ <v-row no-gutters align="center" justify="center">
3
+ <v-col cols="12" lg="12">
4
+ <TableMain
5
+ v-if="canViewSchedules"
6
+ :title="'Cleaner Checklist'"
7
+ :items="items"
8
+ :headers="headers"
9
+ :loading="loading"
10
+ :show-header="true"
11
+ v-model:page="page"
12
+ :pages="pages"
13
+ :pageRange="pageRange"
14
+ :no-data-text="'No Cleaner Checklist found'"
15
+ @refresh="getCleanerChecklistRefresh"
16
+ @row-click="onRowClick"
17
+ >
18
+ <template #extension>
19
+ <v-row no-gutters class="w-100 d-flex flex-column">
20
+ <v-card
21
+ class="w-100 px-3 d-flex align-center ga-5 py-2"
22
+ flat
23
+ :height="60"
24
+ >
25
+ <InputDateTimePicker
26
+ v-model:utc="startDate"
27
+ density="compact"
28
+ hide-details
29
+ />
30
+ <InputDateTimePicker
31
+ v-model:utc="endDate"
32
+ density="compact"
33
+ hide-details
34
+ />
35
+ <v-select
36
+ v-model="status"
37
+ :items="statusOptions"
38
+ density="compact"
39
+ hide-details
40
+ class="mx-3"
41
+ style="max-width: 160px"
42
+ item-title="title"
43
+ item-value="value"
44
+ label="Status"
45
+ />
46
+ </v-card>
47
+ </v-row>
48
+ </template>
49
+
50
+ <template #item.createdAt="{ value }">
51
+ {{
52
+ formatDate
53
+ ? formatDate(value)
54
+ : value
55
+ ? new Date(value).toLocaleString()
56
+ : ""
57
+ }}
58
+ </template>
59
+ <template #item.closeIn="{ item }">
60
+ <!-- <v-chip class="text-capitalize">{{ value || "No Status" }}</v-chip> -->
61
+ <v-chip class="text-capitalize" variant="flat" color="primary">{{
62
+ remainingTime[item._id] || "No Status"
63
+ }}</v-chip>
64
+ </template>
65
+ <template #item.status="{ value }">
66
+ <v-chip
67
+ class="text-capitalize"
68
+ :color="getStatusColor(value)"
69
+ variant="flat"
70
+ pill
71
+ >
72
+ {{ value || "No Status" }}
73
+ </v-chip>
74
+ </template>
75
+ <template #item.download="{ item }">
76
+ <v-btn
77
+ v-if="canDownloadSchedule"
78
+ variant="outlined"
79
+ size="medium"
80
+ class="text-capitalize px-4 py-2"
81
+ :loading="downloadingId === item._id"
82
+ @click.stop="downloadItem(item)"
83
+ >
84
+ <v-icon left>mdi-download</v-icon>
85
+ Download
86
+ </v-btn>
87
+ </template>
88
+ </TableMain>
89
+ <div v-else class="pa-4">
90
+ <v-card variant="outlined" class="pa-4">
91
+ <div class="text-subtitle-1">
92
+ You do not have permission to view cleaning schedules.
93
+ </div>
94
+ </v-card>
95
+ </div>
96
+ </v-col>
97
+ </v-row>
98
+ </template>
99
+
100
+ <script lang="ts" setup>
101
+ import { useCleaningSchedulePermission } from "../composables/useCleaningSchedulePermission";
102
+ import useCleaningSchedules from "../composables/useCleaningSchedules";
103
+
104
+ const props = defineProps({
105
+ orgId: { type: String, required: true },
106
+ site: { type: String, required: true },
107
+ type: { type: String, required: true },
108
+ });
109
+
110
+ const startDate = ref("");
111
+ const endDate = ref("");
112
+ const status = ref<TScheduleAreaStatus>("All");
113
+ const statusOptions = [
114
+ { title: "All", value: "all" },
115
+ { title: "Ready", value: "ready" },
116
+ { title: "Ongoing", value: "ongoing" },
117
+ { title: "Completed", value: "completed" },
118
+ ] as const;
119
+
120
+ const items = ref<Array<Record<string, any>>>([]);
121
+ const page = ref(1);
122
+ const pages = ref(0);
123
+ const pageRange = ref("-- - -- of --");
124
+
125
+ const headers = [
126
+ { title: "Created Date", value: "createdAt" },
127
+ { title: "Close in", value: "closeIn" },
128
+ { title: "Status", value: "status" },
129
+ { title: "Completion Date", value: "completionDate" },
130
+ { title: "", value: "download" },
131
+ ];
132
+ const remainingTime = ref({} as any);
133
+ const downloadingId = ref<string | null>(null);
134
+
135
+ const { getCleaningSchedules, downloadChecklistPdf } = useCleaningSchedules();
136
+ const { formatDate } = useUtils();
137
+
138
+ const getStatusColor = (status: unknown): string => {
139
+ if (!status) return "grey";
140
+
141
+ const normalized = String(status).toLowerCase();
142
+
143
+ switch (normalized) {
144
+ case "ready":
145
+ return "grey";
146
+ case "ongoing":
147
+ return "primary";
148
+ case "completed":
149
+ case "accepted":
150
+ return "success";
151
+ default:
152
+ return "secondary";
153
+ }
154
+ };
155
+
156
+ const {
157
+ canDownloadSchedule,
158
+ canViewSchedules,
159
+ canViewScheduleDetails,
160
+ canManageScheduleTasks,
161
+ } = useCleaningSchedulePermission();
162
+
163
+ const {
164
+ data: getCleanerChecklistReq,
165
+ refresh: getCleanerChecklistRefresh,
166
+ pending: loading,
167
+ } = await useLazyAsyncData(
168
+ "get-all-areas",
169
+ () =>
170
+ getCleaningSchedules({
171
+ page: page.value,
172
+ site: props.site,
173
+ startDate: startDate.value,
174
+ endDate: endDate.value,
175
+ status: status.value === "All" ? undefined : status.value,
176
+ }),
177
+ {
178
+ watch: [() => page.value, () => props.site, startDate, endDate, status],
179
+ }
180
+ );
181
+
182
+ function calculateRemainingTime(
183
+ item: Date | string | number | null | undefined
184
+ ) {
185
+ if (!item) return -1;
186
+ const _date = new Date(item);
187
+ if (isNaN(_date.getTime())) return -1;
188
+ const creationTime = _date.getTime();
189
+ const currentTime = Date.now();
190
+ const differenceInMillis = currentTime - creationTime;
191
+ const differenceInSeconds = Math.floor(differenceInMillis / 1000);
192
+ const desiredDurationInSeconds = 24 * 60 * 60;
193
+ const remainingTimeInSeconds = desiredDurationInSeconds - differenceInSeconds;
194
+ return remainingTimeInSeconds;
195
+ }
196
+
197
+ const formatTime = (seconds: number) => {
198
+ if (seconds <= 0 || isNaN(seconds)) return "00h 00m";
199
+ const hours = Math.floor(seconds / 3600);
200
+ const minutes = Math.floor((seconds % 3600) / 60);
201
+ return `${String(hours).padStart(2, "0")}h ${String(minutes).padStart(
202
+ 2,
203
+ "0"
204
+ )}m`;
205
+ };
206
+
207
+ const updateRemainingTime = () => {
208
+ console.log(items.value);
209
+ items.value.forEach((item) => {
210
+ const itemId = item._id as string;
211
+ const _time = calculateRemainingTime(item.date as string);
212
+ remainingTime.value[itemId] = _time < 0 ? "00h 00m" : formatTime(_time);
213
+ });
214
+ };
215
+
216
+ watchEffect(() => {
217
+ if (getCleanerChecklistReq.value) {
218
+ items.value = getCleanerChecklistReq.value.items;
219
+ pages.value = getCleanerChecklistReq.value.pages;
220
+ pageRange.value = getCleanerChecklistReq.value.pageRange;
221
+ }
222
+
223
+ updateRemainingTime();
224
+ });
225
+
226
+ async function downloadItem(item: any) {
227
+ const id = item?._id;
228
+ if (!id) return;
229
+
230
+ try {
231
+ downloadingId.value = id;
232
+ await downloadChecklistPdf(id);
233
+ } catch (err) {
234
+ console.error("Failed to download checklist PDF", err);
235
+ } finally {
236
+ downloadingId.value = null;
237
+ }
238
+ }
239
+
240
+ function onRowClick(data: any) {
241
+ const item = data?.item ?? data;
242
+ const id = item?._id || item?.id || item?.areaId;
243
+ if (id) {
244
+ if (!canViewScheduleDetails.value) {
245
+ showMessage(
246
+ "You do not have permission to view schedule details.",
247
+ "error"
248
+ );
249
+ return;
250
+ }
251
+
252
+ if (props.type === "toilet") {
253
+ const path = `/${props.orgId}/${props.site}/toilet-checklist/${id}`;
254
+ navigateTo(path);
255
+ return;
256
+ }
257
+ const path = `/${props.orgId}/${props.site}/cleaning-schedule/${id}`;
258
+ navigateTo(path);
259
+ } else {
260
+ console.warn("Row clicked but no id found to navigate:", item);
261
+ }
262
+ }
263
+
264
+ function showMessage(msg: string, color: string = "error") {
265
+ try {
266
+ console.warn(msg);
267
+ } catch (e) {
268
+ alert(msg);
269
+ }
270
+ }
271
+ </script>
@@ -165,12 +165,6 @@
165
165
  Are you sure you want to delete this document? This action cannot be
166
166
  undone.
167
167
  </p>
168
-
169
- <v-row v-if="message" no-gutters justify="center" class="mt-4">
170
- <span class="text-caption text-error text-center">
171
- {{ message }}
172
- </span>
173
- </v-row>
174
168
  </v-card-text>
175
169
 
176
170
  <v-toolbar density="compact">
@@ -270,6 +264,9 @@ const messageColor = ref("");
270
264
 
271
265
  const items = ref<Array<Record<string, any>>>([]);
272
266
 
267
+ const route = useRoute();
268
+ const siteId = route.params.site as string;
269
+
273
270
  const {
274
271
  data: getDocumentReq,
275
272
  refresh: getDocuments,
@@ -280,6 +277,7 @@ const {
280
277
  _getAllDocuments({
281
278
  page: page.value,
282
279
  search: headerSearch.value,
280
+ site: siteId,
283
281
  }),
284
282
  {
285
283
  watch: [page, headerSearch],
@@ -303,7 +301,7 @@ const selectedDocument = ref<TDocument>({
303
301
  _id: "",
304
302
  name: "",
305
303
  attachment: "",
306
- type: ""
304
+ type: "",
307
305
  });
308
306
  const deleteLoading = ref(false);
309
307
  const confirmDialog = ref(false);
@@ -354,7 +352,8 @@ function openEditDialog() {
354
352
  async function handleDeleteDocument() {
355
353
  deleteLoading.value = true;
356
354
  try {
357
- await deleteById(selectedDocument.value._id ?? "");
355
+ const response = await deleteById(selectedDocument.value._id ?? "");
356
+ showMessage(response.message, "success");
358
357
  await getDocuments();
359
358
  selectedDocumentId.value = null;
360
359
  confirmDialog.value = false;
@@ -372,5 +371,5 @@ function successUpdate() {
372
371
  previewDialog.value = false;
373
372
  getDocuments();
374
373
  showMessage("Document updated successfully!", "success");
375
- };
374
+ }
376
375
  </script>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <v-dialog
3
+ :model-value="modelValue"
4
+ transition="dialog-right-transition"
5
+ width="550"
6
+ persistent
7
+ @update:model-value="emit('update:modelValue', $event)"
8
+ >
9
+ <v-card>
10
+ <v-toolbar
11
+ style="height: 12px"
12
+ :color="isDark ? 'grey-darken-4' : 'white'"
13
+ >
14
+ <v-spacer></v-spacer>
15
+ <v-btn
16
+ color="grey-darken-1"
17
+ icon="mdi-close"
18
+ @click="emit('update:modelValue', false)"
19
+ ></v-btn>
20
+ </v-toolbar>
21
+
22
+ <v-card-text style="padding: 0 !important">
23
+ <v-container>
24
+ <v-row no-gutters class="text-center">
25
+ <v-col cols="12">
26
+ <p class="font-weight-bold text-h5 mb-3">{{ header }}</p>
27
+ <p>{{ subText }}</p>
28
+ </v-col>
29
+
30
+ <v-col cols="12" class="mt-5">
31
+ <!-- UPPER QR -->
32
+ <v-row no-gutters>
33
+ <v-col cols="6">
34
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
35
+ </v-col>
36
+ <v-col cols="6">
37
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
38
+ </v-col>
39
+ </v-row>
40
+
41
+ <!-- CENTER QR -->
42
+ <img v-if="qrLarge" :src="qrLarge" width="130" height="130" class="my-2" />
43
+
44
+ <!-- LOWER QR -->
45
+ <v-row no-gutters>
46
+ <v-col cols="6">
47
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
48
+ </v-col>
49
+ <v-col cols="6">
50
+ <img v-if="qrSmall" :src="qrSmall" width="80" height="80" />
51
+ </v-col>
52
+ </v-row>
53
+ </v-col>
54
+
55
+ <v-col cols="12" class="mt-5">
56
+ <p class="font-weight-bold text-h5 mb-2">Company Name</p>
57
+ <p class="font-weight-bold text-h5 mb-3">BL 01/03/24</p>
58
+ <p class="mb-1">{{ `Date: ${dateStr} ${timeStr}` }}</p>
59
+ <p class="mb-1">Access: Door</p>
60
+ <p>Scan and press level 1. One time use.</p>
61
+ </v-col>
62
+ </v-row>
63
+ </v-container>
64
+ </v-card-text>
65
+ </v-card>
66
+ </v-dialog>
67
+ </template>
68
+
69
+ <script lang="ts" setup>
70
+ import { useTheme } from "vuetify";
71
+ import QRCode from "qrcode";
72
+
73
+ defineProps({
74
+ modelValue: {
75
+ type: Boolean,
76
+ default: false,
77
+ },
78
+ header: {
79
+ type: String,
80
+ default: "",
81
+ },
82
+ subText: {
83
+ type: String,
84
+ default: "",
85
+ },
86
+ });
87
+
88
+ const emit = defineEmits(["update:modelValue"]);
89
+
90
+ const { global: themeGlobal } = useTheme();
91
+ const isDark = computed(() => themeGlobal.current.value.dark);
92
+
93
+ const qrSmall = ref<string>("");
94
+ const qrLarge = ref<string>("");
95
+
96
+ onMounted(async () => {
97
+ qrSmall.value = await QRCode.toDataURL("123123", { width: 80, margin: 1 });
98
+ qrLarge.value = await QRCode.toDataURL("123123", { width: 130, margin: 1 });
99
+ });
100
+
101
+ const now = new Date();
102
+ const dateStr = now.toLocaleDateString();
103
+ const timeStr = now.toLocaleTimeString();
104
+ </script>