@iservice365/layer-common 1.1.0 → 1.2.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 (46) hide show
  1. package/CHANGELOG.md +6 -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 +5 -1
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +111 -63
  15. package/components/Input/InputPhoneNumberV2.vue +115 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +41 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +420 -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 +568 -0
  26. package/components/WorkOrder/Create.vue +70 -46
  27. package/components/WorkOrder/Main.vue +11 -10
  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 +79 -0
  38. package/package.json +1 -1
  39. package/plugins/vuetify.ts +6 -1
  40. package/types/building.d.ts +19 -0
  41. package/types/camera.d.ts +31 -0
  42. package/types/people.d.ts +22 -0
  43. package/types/select.d.ts +4 -0
  44. package/types/site.d.ts +10 -7
  45. package/types/visitor.d.ts +42 -0
  46. package/utils/phoneMasks.ts +1703 -0
@@ -20,18 +20,18 @@
20
20
  label="Subject"
21
21
  variant="outlined"
22
22
  density="compact"
23
- v-model="localWorkOrder.subject"
23
+ v-model="workOrder.subject"
24
24
  class="mb-1"
25
25
  dense
26
26
  :readonly="
27
- props.createdFrom === 'feedback' && localWorkOrder.subject !== ''
27
+ props.createdFrom === 'feedback' && workOrder.subject !== ''
28
28
  ? true
29
29
  : false
30
30
  "
31
31
  />
32
32
 
33
33
  <v-autocomplete
34
- v-model="localWorkOrder.category"
34
+ v-model="workOrder.category"
35
35
  :items="categories"
36
36
  item-title="title"
37
37
  item-value="value"
@@ -41,22 +41,18 @@
41
41
  density="compact"
42
42
  class="mb-2"
43
43
  :clearable="
44
- props.createdFrom === 'feedback' && localWorkOrder.category !== ''
44
+ props.createdFrom === 'feedback' && workOrder.category !== ''
45
45
  ? false
46
46
  : true
47
47
  "
48
48
  :readonly="
49
- props.createdFrom === 'feedback' && localWorkOrder.category !== ''
49
+ props.createdFrom === 'feedback' && workOrder.category !== ''
50
50
  ? true
51
51
  : false
52
52
  "
53
53
  />
54
54
 
55
- <v-checkbox
56
- v-model="localWorkOrder.highPriority"
57
- density="compact"
58
- class="ma-0"
59
- >
55
+ <v-checkbox v-model="workOrder.highPriority" density="compact" class="ma-0">
60
56
  <template #label>
61
57
  <span class="text-caption">
62
58
  Set work order as High Priority (Optional)
@@ -64,55 +60,55 @@
64
60
  </template>
65
61
  </v-checkbox>
66
62
 
67
- <v-autocomplete
63
+ <!-- <v-autocomplete
68
64
  label="Block"
69
65
  variant="outlined"
70
66
  density="compact"
71
- v-model="localWorkOrder.block"
67
+ v-model="workOrder.block"
72
68
  :items="blocks"
73
69
  class="mt-1"
74
70
  clearable
75
- />
71
+ /> -->
76
72
 
77
- <v-autocomplete
73
+ <!-- <v-autocomplete
78
74
  label="Level"
79
75
  variant="outlined"
80
76
  density="compact"
81
- v-model="localWorkOrder.level"
77
+ v-model="workOrder.level"
82
78
  :items="levels"
83
79
  class="mt-1"
84
80
  clearable
85
- />
81
+ /> -->
86
82
 
87
83
  <v-autocomplete
88
84
  label="Unit"
89
85
  variant="outlined"
90
86
  density="compact"
91
- v-model="localWorkOrder.unit"
87
+ v-model="workOrder.unit"
92
88
  :items="units"
93
89
  class="mt-1"
94
90
  clearable
95
91
  />
96
92
 
97
- <v-text-field
93
+ <!-- <v-text-field
98
94
  label="Specific Location"
99
95
  variant="outlined"
100
96
  density="compact"
101
- v-model="localWorkOrder.location"
97
+ v-model="workOrder.location"
102
98
  class="mt-1"
103
99
  dense
104
100
  :readonly="
105
- props.createdFrom === 'feedback' && localWorkOrder.location !== ''
101
+ props.createdFrom === 'feedback' && workOrder.location !== ''
106
102
  ? true
107
103
  : false
108
104
  "
109
- />
105
+ /> -->
110
106
 
111
107
  <v-textarea
112
108
  label="Work Order Description"
113
109
  variant="outlined"
114
110
  density="compact"
115
- v-model="localWorkOrder.description"
111
+ v-model="workOrder.description"
116
112
  class="mt-1"
117
113
  rows="4"
118
114
  clearable
@@ -135,10 +131,11 @@
135
131
  </template>
136
132
 
137
133
  <script setup lang="ts">
134
+ import useBuildingUnit from "../../composables/useBuildingUnit";
135
+
138
136
  const props = defineProps<{
139
137
  modelValue: boolean;
140
138
  createdFrom: string;
141
- workOrder: TWorkOrderCreate & { specificLocation?: string };
142
139
  isEditMode: boolean;
143
140
  loading: boolean;
144
141
  categories: { title: string; value: string }[];
@@ -150,25 +147,32 @@ const props = defineProps<{
150
147
  specificLocations?: string[];
151
148
  }>();
152
149
 
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
- }>();
150
+ const workOrder = defineModel("workOrder", {
151
+ default: {
152
+ subject: "",
153
+ category: "",
154
+ highPriority: false,
155
+ unit: "",
156
+ location: "",
157
+ description: "",
158
+ attachments: [],
159
+ },
160
+ });
161
+
162
+ const emit = defineEmits([
163
+ "update:modelValue",
164
+ "update:workOrder",
165
+ "close",
166
+ "submit",
167
+ "fileAdded",
168
+ "fileDeleted",
169
+ ]);
161
170
 
162
171
  const dialog = computed({
163
172
  get: () => props.modelValue,
164
173
  set: (val) => emit("update:modelValue", val),
165
174
  });
166
175
 
167
- const localWorkOrder = computed({
168
- get: () => props.workOrder,
169
- set: (val) => emit("update:workOrder", val),
170
- });
171
-
172
176
  const localErroredImages = ref<string[]>(props.erroredImages || []);
173
177
 
174
178
  watch(
@@ -179,7 +183,7 @@ watch(
179
183
  );
180
184
 
181
185
  const displayedAttachments = computed(() => {
182
- return localWorkOrder.value.attachments || [];
186
+ return workOrder.value.attachments || [];
183
187
  });
184
188
 
185
189
  function handleFileAdded(file: File) {
@@ -202,17 +206,37 @@ function closeDialog() {
202
206
  }
203
207
 
204
208
  function submitWorkOrder() {
205
- emit("submit", { ...localWorkOrder.value });
209
+ emit("submit", { ...workOrder.value });
206
210
  }
207
211
 
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 || []);
212
+ const units = ref<Array<{ title: string; value: string }>>([]);
213
+
214
+ const { getAll } = useBuildingUnit();
215
+ const { data } = await useLazyAsyncData("get-building-units-by-status", () =>
216
+ getAll({ status: "active" })
217
+ );
218
+
219
+ watchEffect(() => {
220
+ if (data.value && data.value.items && data.value.items.length > 0) {
221
+ units.value = data.value.items.map((item: any) => {
222
+ return {
223
+ title: `${item.name} - ${item.level} - ${item.buildingName}`,
224
+ value: item._id,
225
+ };
226
+ });
227
+ }
228
+ });
212
229
 
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 || [])]);
230
+ const workOrderUnit = computed(() => {
231
+ return workOrder.value.unit;
232
+ });
216
233
 
217
- const specificLocations = computed(() => props.specificLocations || []);
234
+ watch(workOrderUnit, (val: string) => {
235
+ if (val) {
236
+ const unit = units.value.find((unit: any) => unit.value === val);
237
+ if (unit) {
238
+ workOrder.value.location = unit.title;
239
+ }
240
+ }
241
+ });
218
242
  </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({
@@ -272,20 +273,20 @@ function handleCloseDialog() {
272
273
  const serviceProviders = ref<
273
274
  Array<{ title: string; value: string; subtitle: string }>
274
275
  >([]);
275
- const { getAll: getAllServiceProvider } = useServiceProvider();
276
276
 
277
- const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
278
- getAllServiceProvider({
279
- siteId: useRoute().params.site as string,
280
- })
277
+ const { getBySiteAsServiceProvider } = useCustomerSite();
278
+
279
+ const { data: getAllReq } = useLazyAsyncData(
280
+ "get-by-site-as-service-provider",
281
+ () => getBySiteAsServiceProvider(useRoute().params.site as string)
281
282
  );
282
283
 
283
284
  watchEffect(() => {
284
285
  if (getAllReq.value) {
285
- serviceProviders.value = getAllReq.value.items.map((i: any) => ({
286
+ serviceProviders.value = getAllReq.value.map((i: any) => ({
286
287
  title: i.nature.replace(/_/g, " "),
287
- value: i.serviceProviderOrgId,
288
- subtitle: i.name,
288
+ subtitle: i.title,
289
+ value: i._id.org,
289
290
  }));
290
291
  }
291
292
  });
@@ -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
+ }
@@ -34,19 +34,19 @@ export default function useFeedback() {
34
34
 
35
35
  async function getFeedbacks({
36
36
  page = 1,
37
- organization = "",
38
37
  site = "",
39
38
  status = "to-do",
40
39
  search = "",
41
40
  limit = 10,
42
41
  from = "",
42
+ category = "",
43
43
  } = {}) {
44
44
  try {
45
45
  return useNuxtApp().$api<Record<string, any>>(
46
- `/api/feedbacks/organization/${organization}/site/${site}/status/${status}`,
46
+ `/api/feedbacks/site/${site}/status/${status}`,
47
47
  {
48
48
  method: "GET",
49
- query: { page, search, limit, from },
49
+ query: { page, search, limit, from, category },
50
50
  }
51
51
  );
52
52
  } catch (err) {