@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,235 @@
1
+ <template>
2
+ <v-row no-gutters align="center" justify="center">
3
+ <v-col cols="12">
4
+ <v-card class="mx-auto">
5
+ <v-toolbar
6
+ density="compact"
7
+ color="grey-lighten-4"
8
+ class="d-flex align-center px-4 py-3"
9
+ >
10
+ <v-btn icon variant="plain" @click="back()" class="mr-2">
11
+ <v-icon>mdi-chevron-left</v-icon>
12
+ </v-btn>
13
+ <span class="text-h6 font-weight-bold">Area Checklist</span>
14
+ </v-toolbar>
15
+
16
+ <v-divider />
17
+
18
+ <v-card-text class="pa-0">
19
+ <v-row no-gutters>
20
+ <v-col
21
+ cols="12"
22
+ md="6"
23
+ lg="5"
24
+ xl="4"
25
+ style="border-right: 1px solid rgba(0, 0, 0, 0.12)"
26
+ >
27
+ <v-row no-gutters class="pa-6">
28
+ <v-col cols="12" class="mb-4">
29
+ <h3 class="text-h6 font-weight-bold">Checklist Details</h3>
30
+ </v-col>
31
+
32
+ <v-col cols="12" class="mb-4">
33
+ <v-row no-gutters>
34
+ <v-col cols="12" class="mb-3">
35
+ <div class="text-body-2 mb-1">Date Created:</div>
36
+ <div class="text-body-1 font-weight-bold">
37
+ {{ createdAt }}
38
+ </div>
39
+ </v-col>
40
+
41
+ <v-col cols="12" class="mb-3">
42
+ <div class="text-body-2 mb-1">Date Completed:</div>
43
+ <div class="text-body-1 font-weight-bold">
44
+ {{ timeOut }}
45
+ </div>
46
+ </v-col>
47
+
48
+ <v-col cols="12" class="mb-3">
49
+ <div class="text-body-2 mb-1">Status:</div>
50
+ <v-chip
51
+ class="text-capitalize text-body-2 font-weight-medium"
52
+ >
53
+ {{ status || "N/A" }}
54
+ </v-chip>
55
+ </v-col>
56
+ </v-row>
57
+ </v-col>
58
+
59
+ <!-- <v-col cols="12" class="mb-4">
60
+ <v-sheet
61
+ height="220"
62
+ rounded
63
+ border
64
+ class="d-flex align-center justify-center"
65
+ >
66
+ <v-img
67
+ v-if="signatureUrl"
68
+ :src="signatureUrl"
69
+ height="220"
70
+ contain
71
+ class="rounded"
72
+ >
73
+ <template v-slot:placeholder>
74
+ <div
75
+ class="d-flex align-center justify-center fill-height"
76
+ >
77
+ <v-progress-circular
78
+ color="grey-lighten-4"
79
+ indeterminate
80
+ ></v-progress-circular>
81
+ </div>
82
+ </template>
83
+ </v-img>
84
+ <span v-else class="text-body-2 text-grey">
85
+ Completion / Signature
86
+ </span>
87
+ </v-sheet>
88
+ </v-col> -->
89
+
90
+ <!-- <v-col cols="12">
91
+ <v-row no-gutters align="center">
92
+ <v-col cols="auto">
93
+ <v-avatar size="40" color="primary" class="mr-3">
94
+ <span class="text-body-1 font-weight-bold">HS</span>
95
+ </v-avatar>
96
+ </v-col>
97
+ <v-col>
98
+ <span class="text-body-1 font-weight-medium"
99
+ >Cleaner</span
100
+ >
101
+ </v-col>
102
+ </v-row>
103
+ </v-col> -->
104
+ </v-row>
105
+ </v-col>
106
+
107
+ <v-col cols="12" md="6" lg="7" xl="8">
108
+ <v-row no-gutters class="pa-6">
109
+ <v-col cols="12">
110
+ <h4 class="text-subtitle-1 font-weight-bold mb-3">Units</h4>
111
+
112
+ <v-list class="pa-0">
113
+ <template
114
+ v-for="(checklistSet, setIndex) in unitSets"
115
+ :key="`set-${setIndex}`"
116
+ >
117
+ <v-list-subheader
118
+ v-if="checklistSet.set !== undefined"
119
+ class="text-subtitle-2 font-weight-bold px-0"
120
+ >
121
+ Set {{ checklistSet.set }}
122
+ </v-list-subheader>
123
+
124
+ <v-list-item
125
+ v-for="(unit, idx) in checklistSet.units"
126
+ :key="`${setIndex}-${idx}`"
127
+ class="px-0"
128
+ >
129
+ <v-row no-gutters align="center" class="py-2">
130
+ <v-col cols="12" sm="3">
131
+ <div class="text-body-2 font-weight-medium">
132
+ {{ unit.name }}
133
+ </div>
134
+ </v-col>
135
+ <v-col cols="12" sm="2">
136
+ <div class="text-body-2 text-medium-emphasis">
137
+ {{ unit.remarks || "N/A" }}
138
+ </div>
139
+ </v-col>
140
+ <v-col cols="12" sm="2">
141
+ <div class="text-body-2 text-medium-emphasis">
142
+ {{ unit.completedByName || "N/A" }}
143
+ </div>
144
+ </v-col>
145
+ <v-col cols="12" sm="3">
146
+ <div class="text-body-2 text-medium-emphasis">
147
+ {{
148
+ unit.timestamp
149
+ ? formatDate(String(unit.timestamp))
150
+ : "N/A"
151
+ }}
152
+ </div>
153
+ </v-col>
154
+ <v-col cols="12" sm="2" class="text-right">
155
+ <v-chip
156
+ :color="
157
+ unit.status?.toLowerCase() === 'completed'
158
+ ? 'success'
159
+ : 'grey'
160
+ "
161
+ :variant="
162
+ unit.status?.toLowerCase() === 'completed'
163
+ ? 'flat'
164
+ : 'outlined'
165
+ "
166
+ size="small"
167
+ class="text-capitalize"
168
+ >
169
+ {{ unit.status || "Ready" }}
170
+ </v-chip>
171
+ </v-col>
172
+ </v-row>
173
+ </v-list-item>
174
+
175
+ <v-divider
176
+ v-if="setIndex < unitSets.length - 1"
177
+ class="my-2"
178
+ />
179
+ </template>
180
+ </v-list>
181
+ </v-col>
182
+ </v-row>
183
+ </v-col>
184
+ </v-row>
185
+ </v-card-text>
186
+ </v-card>
187
+ </v-col>
188
+ </v-row>
189
+ </template>
190
+
191
+ <script setup lang="ts">
192
+ const props = defineProps({
193
+ orgId: { type: String, required: true, default: "" },
194
+ site: { type: String, required: true, default: "" },
195
+ scheduleAreaId: { type: String, required: true, default: "" },
196
+ type: { type: String, required: true, default: "cleaner" },
197
+ });
198
+
199
+ const { back, formatDate } = useUtils();
200
+ const { getFileUrl } = useFile();
201
+
202
+ const { getAreaChecklistHistoryById } = useCleaningSchedules();
203
+ const unitSets = ref<TChecklistSet[]>([]);
204
+
205
+ const { data: getAreaChecklistHistoryReq } = await useLazyAsyncData(
206
+ "get-area-checklist-history-by-id",
207
+ () => getAreaChecklistHistoryById(props.scheduleAreaId)
208
+ );
209
+
210
+ watchEffect(() => {
211
+ if (getAreaChecklistHistoryReq.value) {
212
+ const data = getAreaChecklistHistoryReq.value;
213
+ const area = data.area;
214
+ unitSets.value = data.units || [];
215
+
216
+ createdAt.value = area?.createdAt
217
+ ? formatDate(String(area.createdAt))
218
+ : "N/A";
219
+ timeOut.value = area?.completedAt
220
+ ? formatDate(String(area.completedAt))
221
+ : "N/A";
222
+ status.value = area?.status || "N/A";
223
+
224
+ if (area?.signature) {
225
+ signatureUrl.value = getFileUrl(area.signature);
226
+ }
227
+ }
228
+ });
229
+
230
+ const createdAt = ref<string>("N/A");
231
+ const createdBy = ref<string>("N/A");
232
+ const timeOut = ref<string>("N/A");
233
+ const status = ref<string>("N/A");
234
+ const signatureUrl = ref<string>("");
235
+ </script>
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <v-row no-gutters align="center" justify="center">
3
+ <v-col cols="12" lg="12">
4
+ <TableMain
5
+ :title="'Cleaner Checklist'"
6
+ :headers="headers"
7
+ :items="items"
8
+ :loading="loading"
9
+ :show-header="true"
10
+ v-model:page="page"
11
+ :pages="pages"
12
+ :pageRange="pageRange"
13
+ @update:items="items = $event"
14
+ @refresh="getAreaChecklistHistoryRefresh"
15
+ @row-click="handleRowClick"
16
+ >
17
+ <template #extension>
18
+ <v-row no-gutters class="w-100 d-flex flex-column">
19
+ <v-card
20
+ class="w-100 px-3 d-flex align-center ga-5 py-2"
21
+ flat
22
+ :height="60"
23
+ >
24
+ <v-text-field
25
+ v-model="searchInput"
26
+ density="compact"
27
+ placeholder="Search"
28
+ clearable
29
+ max-width="300"
30
+ append-inner-icon="mdi-magnify"
31
+ hide-details
32
+ />
33
+ <v-select
34
+ v-model="status"
35
+ :items="statusOptions"
36
+ item-title="label"
37
+ item-value="value"
38
+ density="compact"
39
+ clearable
40
+ hide-details
41
+ max-width="200"
42
+ />
43
+ </v-card>
44
+ </v-row>
45
+ </template>
46
+
47
+ <template #item.createdAt="{ value, item }">
48
+ <div>
49
+ {{
50
+ formatDate
51
+ ? formatDate(value)
52
+ : value
53
+ ? new Date(value).toLocaleString()
54
+ : ""
55
+ }}
56
+ <span v-if="item?.endedAt">
57
+ -
58
+ {{
59
+ formatDate
60
+ ? formatDate(item.endedAt)
61
+ : new Date(item.endedAt).toLocaleString()
62
+ }}</span
63
+ >
64
+ </div>
65
+ </template>
66
+ <template #item.status="{ value }">
67
+ <v-chip class="text-capitalize">{{ value || "No Status" }}</v-chip>
68
+ </template>
69
+ </TableMain></v-col
70
+ >
71
+ </v-row>
72
+
73
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
74
+ </template>
75
+
76
+ <script setup lang="ts">
77
+ import useCleaningSchedules from "../composables/useCleaningSchedules";
78
+
79
+ const props = defineProps({
80
+ orgId: { type: String, required: true, default: "" },
81
+ site: { type: String, required: true, default: "" },
82
+ scheduleAreaId: { type: String, required: true, default: "" },
83
+ type: { type: String, required: true, default: "cleaner" },
84
+ });
85
+
86
+ const { formatDate } = useUtils();
87
+
88
+ const loading = ref(false);
89
+ const message = ref("");
90
+ const messageSnackbar = ref(false);
91
+ const messageColor = ref("");
92
+ const searchInput = ref("");
93
+ const page = ref(1);
94
+ const pages = ref(0);
95
+ const pageRange = ref("-- - -- of --");
96
+
97
+ const headers = [
98
+ { title: "Area / Created Date", value: "name" },
99
+ { title: "Time In / Time Out", value: "createdAt" },
100
+ { title: "Status", value: "status" },
101
+ { title: "No. Of Visits", value: "set" },
102
+ { title: "Type", value: "type" },
103
+ ];
104
+
105
+ const items = ref<Array<Record<string, any>>>([]);
106
+
107
+ const route = useRoute();
108
+ const router = useRouter();
109
+
110
+ const status = ref<string>((route.query.status as string) ?? "ongoing");
111
+ const statusOptions = [
112
+ { label: "Ongoing", value: "ongoing" },
113
+ { label: "Completed", value: "completed" },
114
+ ];
115
+
116
+ watch(status, (v) => {
117
+ const newQuery: Record<string, any> = {
118
+ ...(route.query as Record<string, any>),
119
+ };
120
+ if (v) {
121
+ newQuery.status = v;
122
+ } else {
123
+ delete newQuery.status;
124
+ }
125
+ router.replace({ query: newQuery }).catch(() => {});
126
+ });
127
+
128
+ const { getAreaChecklistHistory } = useCleaningSchedules();
129
+
130
+ const {
131
+ data: getAreaChecklistHistoryReq,
132
+ refresh: getAreaChecklistHistoryRefresh,
133
+ } = await useLazyAsyncData(
134
+ "get-area-checklist-history",
135
+ () =>
136
+ getAreaChecklistHistory({
137
+ parentChecklistId: props.scheduleAreaId,
138
+
139
+ page: page.value,
140
+ status: status.value,
141
+ search: searchInput.value,
142
+ }),
143
+ {
144
+ watch: [
145
+ page,
146
+ () => status.value,
147
+ () => props.site,
148
+ () => props.scheduleAreaId,
149
+ () => props.type,
150
+ ],
151
+ }
152
+ );
153
+
154
+ watchEffect(() => {
155
+ if (getAreaChecklistHistoryReq.value) {
156
+ items.value = getAreaChecklistHistoryReq.value.items;
157
+ pages.value = getAreaChecklistHistoryReq.value.pages;
158
+ pageRange.value = getAreaChecklistHistoryReq.value.pageRange;
159
+ }
160
+ });
161
+
162
+ function handleRowClick(data: any) {
163
+ const item = data?.item ?? data;
164
+ const id = item?._id || item?.id || item?.areaId;
165
+
166
+ if (id) {
167
+ if (props.type === "toilet") {
168
+ const path = `/${props.orgId}/${props.site}/toilet-checklist/${props.scheduleAreaId}/history/${id}`;
169
+ navigateTo(path);
170
+ return;
171
+ }
172
+ const path = `/${props.orgId}/${props.site}/cleaning-schedule/${props.scheduleAreaId}/history/${id}`;
173
+ navigateTo(path);
174
+ }
175
+ }
176
+ </script>
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <v-dialog v-model="showDialog" max-width="450">
3
+ <v-card>
4
+ <v-toolbar>
5
+ <template v-if="$slots.header">
6
+ <slot
7
+ name="header"
8
+ :title="
9
+ prop.mode === 'edit'
10
+ ? `Edit ${prop.entityType}`
11
+ : `Add New ${prop.entityType}`
12
+ "
13
+ :close="close"
14
+ />
15
+ </template>
16
+ <template v-else>
17
+ <v-row no-gutters class="fill-height px-6" align="center">
18
+ <span class="font-weight-bold text-h6 text-capitalize">{{
19
+ prop.mode === "edit"
20
+ ? `Edit ${prop.entityType}`
21
+ : `Add New ${prop.entityType}`
22
+ }}</span>
23
+ </v-row>
24
+ </template>
25
+ </v-toolbar>
26
+
27
+ <v-card-text class="pa-4">
28
+ <v-form ref="formRef" v-model="valid">
29
+ <v-row no-gutters class="pa-0">
30
+ <v-col v-if="prop.showType" cols="12" class="pa-0 mb-2">
31
+ <InputLabel for="type" title="Type" required class="mb-1" />
32
+ <v-select
33
+ v-model="typeModel"
34
+ :items="typeOptions"
35
+ density="comfortable"
36
+ variant="outlined"
37
+ hide-details
38
+ class="w-100 mb-0"
39
+ />
40
+ </v-col>
41
+
42
+ <v-col cols="12" class="pa-0 mb-0">
43
+ <InputLabel
44
+ for="name"
45
+ :title="prop.label"
46
+ required
47
+ class="mb-1"
48
+ />
49
+ <v-text-field
50
+ v-model="model"
51
+ :rules="[requiredRule]"
52
+ class="mb-0"
53
+ />
54
+ </v-col>
55
+
56
+ <v-col v-if="prop.showUnits" cols="12" class="pa-0 mb-2">
57
+ <InputLabel for="units" title="Units" class="mb-1" />
58
+ <v-autocomplete
59
+ v-model="unitsModel"
60
+ :items="unitItems"
61
+ :loading="loadingUnits"
62
+ item-title="title"
63
+ item-value="value"
64
+ density="comfortable"
65
+ variant="outlined"
66
+ :rules="[unitsRequiredRule]"
67
+ multiple
68
+ chips
69
+ closable-chips
70
+ placeholder="Select units"
71
+ class="mb-0"
72
+ />
73
+ </v-col>
74
+
75
+ <v-col v-if="prop.showOptional" cols="12" class="mb-4">
76
+ <v-row dense justify="center">
77
+ <v-col cols="6" class="pa-0">
78
+ <v-btn
79
+ block
80
+ color="primary"
81
+ variant="text"
82
+ class="text-none font-weight-bold"
83
+ text="Optional"
84
+ @click="show = !show"
85
+ ></v-btn>
86
+ </v-col>
87
+ </v-row>
88
+ </v-col>
89
+
90
+ <v-expand-transition v-if="prop.showOptional">
91
+ <v-row v-show="show" no-gutters>
92
+ <v-col cols="12">
93
+ <v-row no-gutters>
94
+ <InputLabel
95
+ class="text-capitalize font-weight-bold"
96
+ title="Number of visits"
97
+ />
98
+ <v-col cols="12">
99
+ <v-text-field
100
+ v-model="set"
101
+ density="comfortable"
102
+ type="number"
103
+ min="1"
104
+ ></v-text-field>
105
+ </v-col>
106
+ </v-row>
107
+ </v-col>
108
+ </v-row>
109
+ </v-expand-transition>
110
+ </v-row>
111
+ </v-form>
112
+ </v-card-text>
113
+
114
+ <v-toolbar class="pa-0" density="compact">
115
+ <v-row no-gutters>
116
+ <v-col cols="6" class="pa-0">
117
+ <v-btn
118
+ block
119
+ variant="text"
120
+ class="text-none"
121
+ size="large"
122
+ @click="close"
123
+ height="48"
124
+ >
125
+ Cancel
126
+ </v-btn>
127
+ </v-col>
128
+
129
+ <v-col cols="6" class="pa-0">
130
+ <v-btn
131
+ block
132
+ variant="flat"
133
+ color="black"
134
+ class="text-none font-weight-bold rounded-0"
135
+ height="48"
136
+ :loading="submitting"
137
+ @click="submit"
138
+ >
139
+ {{ prop.mode === "edit" ? "Save" : "Create" }}
140
+ </v-btn>
141
+ </v-col>
142
+ </v-row>
143
+ </v-toolbar>
144
+ </v-card>
145
+ </v-dialog>
146
+ </template>
147
+
148
+ <script setup lang="ts">
149
+ const model = defineModel("name", { type: String, default: "" });
150
+ const typeModel = defineModel("type", { type: String, default: "Common" });
151
+ const set = defineModel("set", { type: Number, default: 1 });
152
+ const unitsModel = defineModel("units", { type: Array, default: () => [] });
153
+ const showDialog = defineModel({ type: Boolean, default: false });
154
+
155
+ const prop = defineProps({
156
+ mode: {
157
+ type: String,
158
+ default: "add",
159
+ },
160
+ areaData: {
161
+ type: Object,
162
+ default: null,
163
+ },
164
+ label: { type: String, default: "Name" },
165
+ entityType: { type: String, default: "Area" }, // values: "Area", "Unit"
166
+ showType: { type: Boolean, default: true },
167
+ showOptional: { type: Boolean, default: true },
168
+ showUnits: { type: Boolean, default: false },
169
+ site: { type: String, default: "" },
170
+ });
171
+
172
+ const emit = defineEmits(["saved", "close"]);
173
+
174
+ const formRef = ref<any>(null);
175
+ const valid = ref(false);
176
+ const submitting = ref(false);
177
+ const show = ref(false);
178
+
179
+ const { requiredRule } = useUtils();
180
+
181
+ const unitsRequiredRule = (v: any) => {
182
+ if (Array.isArray(v)) return v.length > 0 || "Select at least one unit";
183
+ return !!v || "Select at least one unit";
184
+ };
185
+
186
+ const typeOptions = ["Common", "Toilet"];
187
+
188
+ const unitItems = ref<Array<{ title: string; value: string }>>([]);
189
+ const loadingUnits = ref(false);
190
+
191
+ const { getUnits } = useUnits();
192
+
193
+ watch(
194
+ [showDialog, () => prop.showUnits, () => prop.site],
195
+ async ([isOpen, shouldShow, siteId]) => {
196
+ if (isOpen && shouldShow && siteId) {
197
+ loadingUnits.value = true;
198
+ try {
199
+ const response = await getUnits({ site: siteId, limit: 100 });
200
+ unitItems.value = (response?.items || []).map((unit: any) => ({
201
+ title: unit.name,
202
+ value: unit._id || unit.id,
203
+ }));
204
+ } catch (error) {
205
+ console.error("Error loading units:", error);
206
+ } finally {
207
+ loadingUnits.value = false;
208
+ }
209
+ }
210
+ }
211
+ );
212
+
213
+ watchEffect(() => {
214
+ model.value = prop.areaData?.name || "";
215
+ typeModel.value = prop.areaData?.type || "Common";
216
+ set.value = prop.areaData?.set || 1;
217
+ unitsModel.value =
218
+ prop.areaData?.units?.map((u: any) => u.unit || u._id || u.id || u) || [];
219
+ });
220
+
221
+ function close() {
222
+ showDialog.value = false;
223
+ emit("close");
224
+ }
225
+
226
+ async function submit() {
227
+ const form = formRef.value as any;
228
+ let ok = valid.value;
229
+ if (form && typeof form.validate === "function") {
230
+ try {
231
+ ok = await form.validate();
232
+ } catch (e) {
233
+ ok = false;
234
+ }
235
+ }
236
+
237
+ if (!ok) return;
238
+
239
+ submitting.value = true;
240
+ try {
241
+ await new Promise((r) => setTimeout(r, 500));
242
+
243
+ if (prop.showType) {
244
+ emit("saved", {
245
+ name: model.value,
246
+ type: typeModel.value,
247
+ set: set.value,
248
+ units: prop.showUnits ? unitsModel.value : undefined,
249
+ });
250
+ } else {
251
+ emit("saved", model.value);
252
+ }
253
+
254
+ model.value = "";
255
+ typeModel.value = "Common";
256
+ set.value = 1;
257
+ unitsModel.value = [];
258
+
259
+ showDialog.value = false;
260
+ emit("close");
261
+ } catch (e) {
262
+ } finally {
263
+ submitting.value = false;
264
+ }
265
+ }
266
+ </script>