@iservice365/layer-common 1.1.0 → 1.3.0

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 (47) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/components/CameraForm.vue +264 -0
  3. package/components/CameraMain.vue +352 -0
  4. package/components/Card/DeleteConfirmation.vue +51 -0
  5. package/components/Card/MemberInfoSummary.vue +44 -0
  6. package/components/Chat/Information.vue +28 -11
  7. package/components/Dialog/DeleteConfirmation.vue +51 -0
  8. package/components/Dialog/UpdateMoreAction.vue +99 -0
  9. package/components/Feedback/Form.vue +17 -3
  10. package/components/FeedbackDetail.vue +0 -11
  11. package/components/FeedbackMain.vue +21 -11
  12. package/components/Input/DateTimePicker.vue +10 -5
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +106 -63
  15. package/components/Input/InputPhoneNumberV2.vue +114 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +49 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +452 -0
  21. package/components/TableMain.vue +2 -1
  22. package/components/VehicleUpdateMoreAction.vue +84 -0
  23. package/components/VisitorForm.vue +712 -0
  24. package/components/VisitorFormSelection.vue +53 -0
  25. package/components/VisitorManagement.vue +569 -0
  26. package/components/WorkOrder/Create.vue +87 -49
  27. package/components/WorkOrder/Main.vue +17 -12
  28. package/composables/useBuilding.ts +250 -0
  29. package/composables/useBuildingUnit.ts +116 -0
  30. package/composables/useFeedback.ts +3 -3
  31. package/composables/useFile.ts +7 -9
  32. package/composables/useLocal.ts +67 -0
  33. package/composables/usePeople.ts +48 -0
  34. package/composables/useSecurityUtils.ts +18 -0
  35. package/composables/useSiteSettings.ts +111 -0
  36. package/composables/useUtils.ts +30 -1
  37. package/composables/useVisitor.ts +80 -0
  38. package/composables/useWorkOrder.ts +3 -3
  39. package/package.json +1 -1
  40. package/plugins/vuetify.ts +6 -1
  41. package/types/building.d.ts +19 -0
  42. package/types/camera.d.ts +31 -0
  43. package/types/people.d.ts +22 -0
  44. package/types/select.d.ts +4 -0
  45. package/types/site.d.ts +10 -7
  46. package/types/visitor.d.ts +42 -0
  47. package/utils/phoneMasks.ts +1703 -0
@@ -16,22 +16,27 @@
16
16
  @errored="onImageError"
17
17
  />
18
18
 
19
- <v-text-field
19
+ <v-autocomplete
20
+ v-model="workOrder.subject"
21
+ :items="subjects"
22
+ item-title="title"
23
+ item-value="value"
24
+ item-props
20
25
  label="Subject"
21
26
  variant="outlined"
22
27
  density="compact"
23
- v-model="localWorkOrder.subject"
24
- class="mb-1"
25
- dense
28
+ class="mb-2"
29
+ clearable
26
30
  :readonly="
27
- props.createdFrom === 'feedback' && localWorkOrder.subject !== ''
31
+ props.createdFrom === 'feedback' && workOrder.subject !== ''
28
32
  ? true
29
33
  : false
30
34
  "
35
+ :custom-filter="customFilter"
31
36
  />
32
37
 
33
38
  <v-autocomplete
34
- v-model="localWorkOrder.category"
39
+ v-model="workOrder.category"
35
40
  :items="categories"
36
41
  item-title="title"
37
42
  item-value="value"
@@ -41,22 +46,18 @@
41
46
  density="compact"
42
47
  class="mb-2"
43
48
  :clearable="
44
- props.createdFrom === 'feedback' && localWorkOrder.category !== ''
49
+ props.createdFrom === 'feedback' && workOrder.category !== ''
45
50
  ? false
46
51
  : true
47
52
  "
48
53
  :readonly="
49
- props.createdFrom === 'feedback' && localWorkOrder.category !== ''
54
+ props.createdFrom === 'feedback' && workOrder.category !== ''
50
55
  ? true
51
56
  : false
52
57
  "
53
58
  />
54
59
 
55
- <v-checkbox
56
- v-model="localWorkOrder.highPriority"
57
- density="compact"
58
- class="ma-0"
59
- >
60
+ <v-checkbox v-model="workOrder.highPriority" density="compact" class="ma-0">
60
61
  <template #label>
61
62
  <span class="text-caption">
62
63
  Set work order as High Priority (Optional)
@@ -64,55 +65,55 @@
64
65
  </template>
65
66
  </v-checkbox>
66
67
 
67
- <v-autocomplete
68
+ <!-- <v-autocomplete
68
69
  label="Block"
69
70
  variant="outlined"
70
71
  density="compact"
71
- v-model="localWorkOrder.block"
72
+ v-model="workOrder.block"
72
73
  :items="blocks"
73
74
  class="mt-1"
74
75
  clearable
75
- />
76
+ /> -->
76
77
 
77
- <v-autocomplete
78
+ <!-- <v-autocomplete
78
79
  label="Level"
79
80
  variant="outlined"
80
81
  density="compact"
81
- v-model="localWorkOrder.level"
82
+ v-model="workOrder.level"
82
83
  :items="levels"
83
84
  class="mt-1"
84
85
  clearable
85
- />
86
+ /> -->
86
87
 
87
88
  <v-autocomplete
88
89
  label="Unit"
89
90
  variant="outlined"
90
91
  density="compact"
91
- v-model="localWorkOrder.unit"
92
+ v-model="workOrder.unit"
92
93
  :items="units"
93
94
  class="mt-1"
94
95
  clearable
95
96
  />
96
97
 
97
- <v-text-field
98
+ <!-- <v-text-field
98
99
  label="Specific Location"
99
100
  variant="outlined"
100
101
  density="compact"
101
- v-model="localWorkOrder.location"
102
+ v-model="workOrder.location"
102
103
  class="mt-1"
103
104
  dense
104
105
  :readonly="
105
- props.createdFrom === 'feedback' && localWorkOrder.location !== ''
106
+ props.createdFrom === 'feedback' && workOrder.location !== ''
106
107
  ? true
107
108
  : false
108
109
  "
109
- />
110
+ /> -->
110
111
 
111
112
  <v-textarea
112
113
  label="Work Order Description"
113
114
  variant="outlined"
114
115
  density="compact"
115
- v-model="localWorkOrder.description"
116
+ v-model="workOrder.description"
116
117
  class="mt-1"
117
118
  rows="4"
118
119
  clearable
@@ -135,10 +136,11 @@
135
136
  </template>
136
137
 
137
138
  <script setup lang="ts">
139
+ import useBuildingUnit from "../../composables/useBuildingUnit";
140
+
138
141
  const props = defineProps<{
139
142
  modelValue: boolean;
140
143
  createdFrom: string;
141
- workOrder: TWorkOrderCreate & { specificLocation?: string };
142
144
  isEditMode: boolean;
143
145
  loading: boolean;
144
146
  categories: { title: string; value: string }[];
@@ -150,25 +152,32 @@ const props = defineProps<{
150
152
  specificLocations?: string[];
151
153
  }>();
152
154
 
153
- const emit = defineEmits<{
154
- (e: "update:modelValue", value: boolean): void;
155
- (e: "update:workOrder", value: TWorkOrderCreate): void;
156
- (e: "close"): void;
157
- (e: "submit", payload: TWorkOrderCreate): void;
158
- (e: "fileAdded", file: File): void;
159
- (e: "fileDeleted", url: string): void;
160
- }>();
155
+ const workOrder = defineModel("workOrder", {
156
+ default: {
157
+ subject: "",
158
+ category: "",
159
+ highPriority: false,
160
+ unit: "",
161
+ location: "",
162
+ description: "",
163
+ attachments: [],
164
+ },
165
+ });
166
+
167
+ const emit = defineEmits([
168
+ "update:modelValue",
169
+ "update:workOrder",
170
+ "close",
171
+ "submit",
172
+ "fileAdded",
173
+ "fileDeleted",
174
+ ]);
161
175
 
162
176
  const dialog = computed({
163
177
  get: () => props.modelValue,
164
178
  set: (val) => emit("update:modelValue", val),
165
179
  });
166
180
 
167
- const localWorkOrder = computed({
168
- get: () => props.workOrder,
169
- set: (val) => emit("update:workOrder", val),
170
- });
171
-
172
181
  const localErroredImages = ref<string[]>(props.erroredImages || []);
173
182
 
174
183
  watch(
@@ -179,7 +188,7 @@ watch(
179
188
  );
180
189
 
181
190
  const displayedAttachments = computed(() => {
182
- return localWorkOrder.value.attachments || [];
191
+ return workOrder.value.attachments || [];
183
192
  });
184
193
 
185
194
  function handleFileAdded(file: File) {
@@ -202,17 +211,46 @@ function closeDialog() {
202
211
  }
203
212
 
204
213
  function submitWorkOrder() {
205
- emit("submit", { ...localWorkOrder.value });
214
+ emit("submit", { ...workOrder.value });
206
215
  }
207
216
 
208
- // expose location fields as fallback empty arrays if not passed
209
- // const blocks = computed(() => props.blocks || []);
210
- // const levels = computed(() => props.levels || []);
211
- // const units = computed(() => props.units || []);
217
+ const units = ref<Array<{ title: string; value: string }>>([]);
218
+
219
+ const { getAll } = useBuildingUnit();
220
+ const { data } = await useLazyAsyncData("get-building-units-by-status", () =>
221
+ getAll({ status: "active" })
222
+ );
223
+
224
+ watchEffect(() => {
225
+ if (data.value && data.value.items && data.value.items.length > 0) {
226
+ units.value = data.value.items.map((item: any) => {
227
+ return {
228
+ title: `${item.name} - ${item.level} - ${item.buildingName}`,
229
+ value: item._id,
230
+ };
231
+ });
232
+ }
233
+ });
234
+
235
+ const workOrderUnit = computed(() => {
236
+ return workOrder.value.unit;
237
+ });
238
+
239
+ watch(workOrderUnit, (val: string) => {
240
+ if (val) {
241
+ const unit = units.value.find((unit: any) => unit.value === val);
242
+ if (unit) {
243
+ workOrder.value.location = unit.title;
244
+ }
245
+ }
246
+ });
212
247
 
213
- const blocks = computed(() => ["Block A", "Block B", ...(props.blocks || [])]);
214
- const levels = computed(() => ["Level 1", "Level 2", ...(props.levels || [])]);
215
- const units = computed(() => ["Unit 101", "Unit 102", ...(props.units || [])]);
248
+ const { subjects } = useLocal();
216
249
 
217
- const specificLocations = computed(() => props.specificLocations || []);
250
+ const customFilter = (value: string, query: string, item: any) => {
251
+ const title = item?.raw?.title?.toLowerCase() || "";
252
+ const subtitle = item?.raw?.subtitle?.toLowerCase() || "";
253
+ const search = query.toLowerCase();
254
+ return title.includes(search) || subtitle.includes(search);
255
+ };
218
256
  </script>
@@ -28,7 +28,7 @@
28
28
  @click="showCreateDialog = true"
29
29
  class="text-capitalize"
30
30
  >
31
- Work Order
31
+ Create Work Order
32
32
  </v-btn>
33
33
  </template>
34
34
 
@@ -76,7 +76,7 @@
76
76
 
77
77
  <WorkOrderCreate
78
78
  v-model="showCreateDialog"
79
- :created-from="'workOrder'"
79
+ created-from="workOrder"
80
80
  :work-order="_workOrder"
81
81
  @update:work-order="(val: TWorkOrderCreate) => (_workOrder = val)"
82
82
  :is-edit-mode="isEditMode"
@@ -97,6 +97,7 @@
97
97
 
98
98
  <script setup lang="ts">
99
99
  import { useTheme } from "vuetify";
100
+ import useBuilding from "../../composables/useBuilding";
100
101
  const emit = defineEmits(["click:create", "update:pagination"]);
101
102
 
102
103
  const props = defineProps({
@@ -132,6 +133,10 @@ const props = defineProps({
132
133
  type: Boolean,
133
134
  default: false,
134
135
  },
136
+ category: {
137
+ type: String,
138
+ default: "",
139
+ },
135
140
  });
136
141
 
137
142
  const theme = useTheme().name;
@@ -192,11 +197,11 @@ const {
192
197
  data: getAllWorkOrderReq,
193
198
  refresh: getAllReqRefresh,
194
199
  status: getAllReqStatus,
195
- } = useLazyAsyncData("get-all-work-orders", () =>
200
+ } = useLazyAsyncData("get-all-work-orders-" + props.category, () =>
196
201
  _getWorkOrders({
197
202
  page: page.value,
198
- organization: route.params.org as string,
199
203
  site: route.params.site as string,
204
+ category: props.category,
200
205
  })
201
206
  );
202
207
 
@@ -272,20 +277,20 @@ function handleCloseDialog() {
272
277
  const serviceProviders = ref<
273
278
  Array<{ title: string; value: string; subtitle: string }>
274
279
  >([]);
275
- const { getAll: getAllServiceProvider } = useServiceProvider();
276
280
 
277
- const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
278
- getAllServiceProvider({
279
- siteId: useRoute().params.site as string,
280
- })
281
+ const { getBySiteAsServiceProvider } = useCustomerSite();
282
+
283
+ const { data: getAllReq } = useLazyAsyncData(
284
+ "get-by-site-as-service-provider",
285
+ () => getBySiteAsServiceProvider(useRoute().params.site as string)
281
286
  );
282
287
 
283
288
  watchEffect(() => {
284
289
  if (getAllReq.value) {
285
- serviceProviders.value = getAllReq.value.items.map((i: any) => ({
290
+ serviceProviders.value = getAllReq.value.map((i: any) => ({
286
291
  title: i.nature.replace(/_/g, " "),
287
- value: i.serviceProviderOrgId,
288
- subtitle: i.name,
292
+ subtitle: i.title,
293
+ value: i.nature
289
294
  }));
290
295
  }
291
296
  });
@@ -0,0 +1,250 @@
1
+ export default function useBuilding() {
2
+ const categories = ref([
3
+ {
4
+ title: "Academic Spaces",
5
+ value: "academic-spaces",
6
+ },
7
+ {
8
+ title: "Support Facilities",
9
+ value: "support-facilities",
10
+ },
11
+ {
12
+ title: "Activity & Recreation Areas",
13
+ value: "activity-recreation-areas",
14
+ },
15
+ {
16
+ title: "Utility & Sanitary",
17
+ value: "utility-sanitary",
18
+ },
19
+ {
20
+ title: "Dormitories & Lodging",
21
+ value: "dormitories-lodging",
22
+ },
23
+ ]);
24
+
25
+ const types = ref([
26
+ {
27
+ title: "Classroom",
28
+ value: "classroom",
29
+ category: "academic-spaces",
30
+ subtitle: "Standard teaching room",
31
+ },
32
+ {
33
+ title: "Laboratory",
34
+ value: "laboratory",
35
+ category: "academic-spaces",
36
+ subtitle: "Science, computer, or language labs",
37
+ },
38
+ {
39
+ title: "Library",
40
+ value: "library",
41
+ category: "academic-spaces",
42
+ subtitle: "Reading and study area",
43
+ },
44
+ {
45
+ title: "Faculty Room",
46
+ value: "faculty_room",
47
+ category: "academic-spaces",
48
+ subtitle: "Shared working space for teachers",
49
+ },
50
+ {
51
+ title: "Lecture Hall",
52
+ value: "lecture_hall",
53
+ category: "academic-spaces",
54
+ subtitle: "Large room for lectures",
55
+ },
56
+ {
57
+ title: "Tutorial Room",
58
+ value: "tutorial_room",
59
+ category: "academic-spaces",
60
+ subtitle: "Small group learning or mentoring",
61
+ },
62
+ {
63
+ title: "Exam Room",
64
+ value: "exam_room",
65
+ category: "academic-spaces",
66
+ subtitle: "Designated exam space",
67
+ },
68
+ {
69
+ title: "Office",
70
+ value: "office",
71
+ category: "support-facilities",
72
+ subtitle: "Admin, registrar, finance, principal's office",
73
+ },
74
+ {
75
+ title: "Clinic",
76
+ value: "clinic",
77
+ category: "support-facilities",
78
+ subtitle: "School nurse or medical emergency room",
79
+ },
80
+ {
81
+ title: "Storage",
82
+ value: "storage",
83
+ category: "support-facilities",
84
+ subtitle: "For site supplies, janitorial, or equipment",
85
+ },
86
+ {
87
+ title: "Server Room",
88
+ value: "server_room",
89
+ category: "support-facilities",
90
+ subtitle: "For IT infrastructure",
91
+ },
92
+ {
93
+ title: "Equipment Room",
94
+ value: "equipment_room",
95
+ category: "support-facilities",
96
+ subtitle: "PE or technical equipment",
97
+ },
98
+ {
99
+ title: "Gymnasium",
100
+ value: "gymnasium",
101
+ category: "activity-recreation-areas",
102
+ subtitle: "Indoor sports",
103
+ },
104
+ {
105
+ title: "Sports Area",
106
+ value: "sports_area",
107
+ category: "activity-recreation-areas",
108
+ subtitle: "Outdoor field or court",
109
+ },
110
+ {
111
+ title: "Music Room",
112
+ value: "music_room",
113
+ category: "activity-recreation-areas",
114
+ subtitle: "For music classes or practice",
115
+ },
116
+ {
117
+ title: "Art Room",
118
+ value: "art_room",
119
+ category: "activity-recreation-areas",
120
+ subtitle: "Visual arts room",
121
+ },
122
+ {
123
+ title: "Theater",
124
+ value: "theater",
125
+ category: "activity-recreation-areas",
126
+ subtitle: "Stage and performance space",
127
+ },
128
+ {
129
+ title: "Canteen",
130
+ value: "canteen",
131
+ category: "activity-recreation-areas",
132
+ subtitle: "Food service area",
133
+ },
134
+ {
135
+ title: "Toilet",
136
+ value: "toilet",
137
+ category: "utility-sanitary",
138
+ subtitle: "Restroom facilities",
139
+ },
140
+ {
141
+ title: "Shower Room",
142
+ value: "shower_room",
143
+ category: "utility-sanitary",
144
+ subtitle: "Common in dorms or gyms",
145
+ },
146
+ {
147
+ title: "Janitor Room",
148
+ value: "janitor_room",
149
+ category: "utility-sanitary",
150
+ subtitle: "Cleaning supplies, mop sinks",
151
+ },
152
+ {
153
+ title: "Mechanical Room",
154
+ value: "mechanical_room",
155
+ category: "utility-sanitary",
156
+ subtitle: "Electrical, HVAC, plumbing systems",
157
+ },
158
+ {
159
+ title: "Dorm Room",
160
+ value: "dorm_room",
161
+ category: "dormitories-lodging",
162
+ subtitle: "Sleeping quarters",
163
+ },
164
+ {
165
+ title: "Common Room",
166
+ value: "common_room",
167
+ category: "dormitories-lodging",
168
+ subtitle: "Lounge or social area",
169
+ },
170
+ {
171
+ title: "Laundry",
172
+ value: "laundry",
173
+ category: "dormitories-lodging",
174
+ subtitle: "Washing facilities",
175
+ },
176
+ ]);
177
+
178
+ function getAll({
179
+ page = 1,
180
+ search = "",
181
+ limit = 20,
182
+ status = "active",
183
+ site = "",
184
+ } = {}) {
185
+ return useNuxtApp().$api<Record<string, any>>("/api/buildings", {
186
+ method: "GET",
187
+ query: {
188
+ page,
189
+ search,
190
+ limit,
191
+ status,
192
+ site,
193
+ },
194
+ });
195
+ }
196
+
197
+ function getById(id = "") {
198
+ return useNuxtApp().$api<Record<string, any>>(`/api/buildings/id/${id}`, {
199
+ method: "GET",
200
+ });
201
+ }
202
+
203
+ function getByName(name = "") {
204
+ return useNuxtApp().$api(`/api/buildings/name/${name}`, {
205
+ method: "GET",
206
+ });
207
+ }
208
+
209
+ function createBuilding(data: TBuilding) {
210
+ return useNuxtApp().$api("/api/buildings", {
211
+ method: "POST",
212
+ body: data,
213
+ });
214
+ }
215
+
216
+ function updateBuildingField(id: string, field: string, value: string) {
217
+ return useNuxtApp().$api(`/api/buildings/${id}`, {
218
+ method: "PATCH",
219
+ body: { field, value },
220
+ });
221
+ }
222
+
223
+ function updateById(
224
+ id: string,
225
+ data: Pick<TBuilding, "name" | "block" | "levels">
226
+ ) {
227
+ return useNuxtApp().$api(`/api/buildings/id/${id}`, {
228
+ method: "PATCH",
229
+ body: data,
230
+ });
231
+ }
232
+
233
+ function deleteById(id: string) {
234
+ return useNuxtApp().$api(`/api/buildings/id/${id}`, {
235
+ method: "DELETE",
236
+ });
237
+ }
238
+
239
+ return {
240
+ categories,
241
+ types,
242
+ getAll,
243
+ getById,
244
+ getByName,
245
+ createBuilding,
246
+ updateBuildingField,
247
+ deleteById,
248
+ updateById,
249
+ };
250
+ }
@@ -0,0 +1,116 @@
1
+ export default function useBuildingUnit() {
2
+ const categories = ref([
3
+ {
4
+ title: "Management",
5
+ value: "management",
6
+ },
7
+ {
8
+ title: "Residential",
9
+ value: "residential",
10
+ },
11
+ {
12
+ title: "Commercial",
13
+ value: "commercial",
14
+ },
15
+ {
16
+ title: "Amenities",
17
+ value: "amenities",
18
+ },
19
+ ]);
20
+
21
+ function getAll({
22
+ page = 1,
23
+ search = "",
24
+ limit = 20,
25
+ status = "active",
26
+ site = "",
27
+ } = {}) {
28
+ return useNuxtApp().$api<Record<string, any>>("/api/building-units", {
29
+ method: "GET",
30
+ query: {
31
+ page,
32
+ search,
33
+ limit,
34
+ status,
35
+ site,
36
+ },
37
+ });
38
+ }
39
+
40
+ function getAllUnits({
41
+ page = 1,
42
+ search = "",
43
+ limit = 20,
44
+ status = "active",
45
+ site = "",
46
+ building = "",
47
+ } = {}) {
48
+ return useNuxtApp().$api<Record<string, any>>("/api/building-units", {
49
+ method: "GET",
50
+ query: {
51
+ page,
52
+ search,
53
+ limit,
54
+ status,
55
+ site,
56
+ building,
57
+ },
58
+ });
59
+ }
60
+
61
+ function getById(id = "") {
62
+ return useNuxtApp().$api<Record<string, any>>(
63
+ `/api/building-units/id/${id}`,
64
+ {
65
+ method: "GET",
66
+ }
67
+ );
68
+ }
69
+
70
+ function getByName(name = "") {
71
+ return useNuxtApp().$api(`/api/building-units/name/${name}`, {
72
+ method: "GET",
73
+ });
74
+ }
75
+
76
+ function add(data: { labels: string[]; unit: TBuildingUnit; qty?: number }) {
77
+ return useNuxtApp().$api("/api/building-units", {
78
+ method: "POST",
79
+ body: data,
80
+ });
81
+ }
82
+
83
+ type TUpdateOption = Pick<TBuildingUnit, "name" | "category" | "level">;
84
+
85
+ function updateById(id: string, value: TUpdateOption) {
86
+ return useNuxtApp().$api(`/api/building-units/id/${id}`, {
87
+ method: "PATCH",
88
+ body: value,
89
+ });
90
+ }
91
+
92
+ function deleteById(id: string) {
93
+ return useNuxtApp().$api(`/api/building-units/id/${id}`, {
94
+ method: "DELETE",
95
+ });
96
+ }
97
+
98
+ const region = ref({
99
+ _id: "",
100
+ name: "",
101
+ director: "",
102
+ directorName: "",
103
+ });
104
+
105
+ return {
106
+ categories,
107
+ region,
108
+ getAll,
109
+ getById,
110
+ getByName,
111
+ add,
112
+ updateById,
113
+ deleteById,
114
+ getAllUnits,
115
+ };
116
+ }