@iservice365/layer-common 1.2.0 → 1.3.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.
@@ -1,95 +1,33 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <TableMain
4
- :headers="headers"
5
- :items="items"
6
- :loading="getVisitorPending"
7
- :page="page"
8
- :pages="pages"
9
- :extension-height="110"
10
- :pageRange="pageRange"
11
- :canCreate="canAddVisitor"
12
- @refresh="getVisitorRefresh"
13
- @update:page="handleUpdatePage"
14
- createLabel="Add Visitor"
15
- show-header
16
- @row-click="handleRowClick"
17
- @create="dialog.showSelection = true"
18
- >
3
+ <TableMain :headers="headers" :items="items" :loading="getVisitorPending" :page="page" :pages="pages"
4
+ :extension-height="110" :offset="300" :pageRange="pageRange" :canCreate="canAddVisitor"
5
+ @refresh="getVisitorRefresh" @update:page="handleUpdatePage" createLabel="Add Visitor" show-header
6
+ @row-click="handleRowClick" @create="dialog.showSelection = true">
19
7
  <template #extension>
20
8
  <v-row no-gutters class="w-100 d-flex flex-column">
21
- <v-tabs
22
- v-model="activeTab"
23
- color="primary"
24
- :height="40"
25
- @update:model-value="toRoute"
26
- class="w-100"
27
- >
28
- <v-tab
29
- v-for="tab in tabOptions"
30
- :value="tab.type"
31
- :key="tab.type"
32
- class="text-capitalize"
33
- >
9
+ <v-tabs v-model="activeTab" color="primary" :height="40" @update:model-value="toRoute" class="w-100">
10
+ <v-tab v-for="tab in tabOptions" :value="tab.status" :key="tab.status" class="text-capitalize">
34
11
  {{ tab.name }}
35
12
  </v-tab>
36
13
  </v-tabs>
37
14
 
38
- <v-card
39
- class="w-100 px-3 d-flex align-center ga-5 py-2"
40
- flat
41
- :height="60"
42
- >
43
- <v-text-field
44
- v-model="searchInput"
45
- density="compact"
46
- placeholder="Search"
47
- clearable
48
- max-width="300"
49
- append-inner-icon="mdi-magnify"
50
- hide-details
51
- />
52
- <v-checkbox
53
- v-model="displayCheckedOutOnly"
54
- class="text-subtitle-2"
55
- hide-details
56
- >
15
+ <v-card class="w-100 px-3 d-flex align-center ga-5 py-2" flat :height="60">
16
+ <v-text-field v-model="searchInput" density="compact" placeholder="Search" clearable max-width="300"
17
+ append-inner-icon="mdi-magnify" hide-details />
18
+ <v-checkbox v-model="displayCheckedOutOnly" class="text-subtitle-2" hide-details>
57
19
  <template #label>
58
20
  <span class="text-caption">Not Checked Out</span>
59
21
  </template>
60
22
  </v-checkbox>
61
- <InputDateTimePicker
62
- v-model:utc="dateFrom"
63
- density="compact"
64
- hide-details
65
- />
66
- <InputDateTimePicker
67
- v-model:utc="dateTo"
68
- density="compact"
69
- hide-details
70
- />
71
- <v-select
72
- v-model="filterTypes"
73
- label="Filter by types"
74
- item-title="label"
75
- item-value="value"
76
- :items="visitorSelection"
77
- density="compact"
78
- clearable
79
- multiple
80
- max-width="200"
81
- hide-details
82
- >
23
+ <InputDateTimePicker v-model:utc="dateFrom" density="compact" hide-details />
24
+ <InputDateTimePicker v-model:utc="dateTo" density="compact" hide-details />
25
+ <v-select v-model="filterTypes" label="Filter by types" item-title="label" item-value="value"
26
+ :items="visitorSelection" density="compact" clearable multiple max-width="200" hide-details>
83
27
  <template v-slot:selection="{ item, index }">
84
28
  <div class="d-flex align-center text-caption text-nowrap">
85
- <v-chip
86
- v-if="index === 0"
87
- color="error"
88
- :text="filterTypeSelectionLabel()"
89
- size="x-small"
90
- variant="tonal"
91
- class="ml-2"
92
- />
29
+ <v-chip v-if="index === 0" color="error" :text="filterTypeSelectionLabel()" size="x-small"
30
+ variant="tonal" class="ml-2" />
93
31
  </div>
94
32
  </template>
95
33
  </v-select>
@@ -111,7 +49,7 @@
111
49
  <v-icon icon="mdi-user" size="15" />
112
50
  <span v-if="item.type === 'contractor'" class="text-capitalize">{{
113
51
  formatCamelCaseToWords(item.contractorType)
114
- }}</span>
52
+ }}</span>
115
53
  <span v-else class="text-capitalize">{{ formatType(item) }}</span>
116
54
  </span>
117
55
  <span class="d-flex align-center ga-2">
@@ -149,100 +87,57 @@
149
87
  <v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
150
88
  <span class="text-capitalize">{{
151
89
  UTCToLocalTIme(item.checkIn) || "-"
152
- }}</span>
90
+ }}</span>
153
91
  </span>
154
92
  <span class="d-flex align-center ga-2">
155
93
  <v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" v-if="item.checkOut" />
156
94
  <template v-if="item.checkOut">
157
95
  <span class="text-capitalize">{{
158
96
  UTCToLocalTIme(item.checkOut) || "_"
159
- }}</span>
97
+ }}</span>
160
98
  <span v-if="item?.manualCheckout">
161
- <TooltipInfo
162
- text="Manual Checkout"
163
- density="compact"
164
- size="x-small"
165
- />
99
+ <TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
166
100
  </span>
167
101
  </template>
168
102
  <span v-else>
169
- <v-btn
170
- size="x-small"
171
- class="text-capitalize"
172
- color="red"
173
- text="Checkout"
174
- :loading="loading.checkingOut && item?._id === selectedVisitorId"
175
- @click.stop="handleCheckout(item._id)"
176
- v-if="canCheckoutVisitor"
177
- />
103
+ <v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
104
+ :loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
105
+ v-if="canCheckoutVisitor" />
178
106
  </span>
179
107
  </span>
180
108
  </template>
181
109
  </TableMain>
182
110
 
183
111
  <v-dialog v-model="dialog.showSelection" width="450" persistent>
184
- <VisitorFormSelection
185
- @cancel="dialog.showSelection = false"
186
- @select="handleSelectVisitorType"
187
- />
112
+ <VisitorFormSelection @cancel="dialog.showSelection = false" @select="handleSelectVisitorType" />
188
113
  </v-dialog>
189
114
 
190
- <v-dialog
191
- v-model="dialog.addVisitor"
192
- v-if="activeVisitorFormType"
193
- width="450"
194
- persistent
195
- >
196
- <VisitorForm
197
- mode="add"
198
- :org="orgId"
199
- :site="siteId"
200
- :type="activeVisitorFormType"
201
- @back="handleClickBack"
202
- @done="handleVisitorFormDone"
203
- @done:more="handleVisitorFormCreateMore"
204
- />
115
+ <v-dialog v-model="dialog.addVisitor" v-if="activeVisitorFormType" width="450" persistent>
116
+ <VisitorForm mode="add" :org="orgId" :site="siteId" :type="activeVisitorFormType" @back="handleClickBack"
117
+ @done="handleVisitorFormDone" @done:more="handleVisitorFormCreateMore" />
205
118
  </v-dialog>
206
119
 
207
120
  <v-dialog v-model="dialog.viewVisitor" width="450" persistent>
208
- <VehicleUpdateMoreAction
209
- title="Preview"
210
- :can-update="canUpdateVisitor"
211
- :can-delete="canDeleteVisitor"
212
- @close="dialog.viewVisitor = false"
213
- edit-button-label="Edit Visitor"
214
- delete-button-label="Delete Visitor"
215
- @delete="handleDeleteVisitor"
216
- >
121
+ <VehicleUpdateMoreAction title="Preview" :can-update="canUpdateVisitor" :can-delete="canDeleteVisitor"
122
+ @close="dialog.viewVisitor = false" edit-button-label="Edit Visitor" delete-button-label="Delete Visitor"
123
+ @delete="handleDeleteVisitor">
217
124
  <template v-slot:content>
218
125
  <v-row no-gutters class="mb-4">
219
126
  <v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
220
- <span
221
- v-if="key === 'checkOut' && !selectedVisitorObject[key] && canCheckoutVisitor"
222
- class="d-flex align-center"
223
- >
127
+ <span v-if="
128
+ key === 'checkOut' &&
129
+ !selectedVisitorObject[key] &&
130
+ canCheckoutVisitor
131
+ " class="d-flex align-center">
224
132
  <strong>{{ label }}:</strong>
225
- <v-btn
226
- size="x-small"
227
- class="ml-3 text-capitalize"
228
- color="red"
229
- text="Checkout"
230
- :disabled="loading.checkingOut"
231
- @click="handleCheckout(selectedVisitorId as string)"
232
- />
133
+ <v-btn size="x-small" class="ml-3 text-capitalize" color="red" text="Checkout"
134
+ :disabled="loading.checkingOut" @click="handleCheckout(selectedVisitorId as string)" />
233
135
  </span>
234
136
 
235
- <span
236
- v-else-if="selectedVisitorObject[key]"
237
- class="d-flex ga-3 align-center"
238
- ><strong>{{ label }}:</strong>
137
+ <span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
138
+ }}:</strong>
239
139
  {{ formatValues(key, selectedVisitorObject[key]) }}
240
- <TooltipInfo
241
- v-if="key === 'checkOut'"
242
- text="Manual Checkout"
243
- density="compact"
244
- size="x-small"
245
- />
140
+ <TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
246
141
  </span>
247
142
  </v-col>
248
143
  </v-row>
@@ -251,12 +146,9 @@
251
146
  </v-dialog>
252
147
 
253
148
  <v-dialog v-model="dialog.deleteConfirmation" width="450" persistent>
254
- <CardDeleteConfirmation
255
- prompt-title="Are you sure want to delete this visitor?"
256
- :loading="loading.deletingVisitor"
257
- @close="dialog.deleteConfirmation = false"
258
- @delete="handleProceedDeleteVisitor"
259
- />
149
+ <CardDeleteConfirmation prompt-title="Are you sure want to delete this visitor?"
150
+ :loading="loading.deletingVisitor" @close="dialog.deleteConfirmation = false"
151
+ @delete="handleProceedDeleteVisitor" />
260
152
  </v-dialog>
261
153
 
262
154
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
@@ -309,26 +201,29 @@ const {
309
201
  const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
310
202
  useUtils();
311
203
  const { formatLocation } = useSecurityUtils();
312
- const { type: visitorType } = useRoute().query;
313
- const { org: orgId, site: siteId } = useRoute().params as {
204
+ // const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
205
+
206
+ const route = useRoute()
207
+ const router = useRouter()
208
+ const { org: orgId, site: siteId } = route.params as {
314
209
  org: string;
315
210
  site: string;
316
211
  };
317
- const routeName = useRoute().name;
212
+ const routeName = route.name;
318
213
 
319
214
  const items = ref<Array<Record<string, any>>>([]);
320
215
  const page = ref(1);
321
216
  const pages = ref(0);
322
217
  const pageRange = ref("-- - -- of --");
323
- const activeTab = ref(visitorType ?? "registered");
218
+ const activeTab = ref("registered");
324
219
  const activeVisitorFormType = ref<TVisitorType | null>(null);
325
220
  const selectedVisitorId = ref<string | null>(""); // selected visitor for viewing/actions
326
221
 
327
222
  //filter states
328
223
  const searchInput = ref("");
329
- const dateFrom = ref(getUTCDates().dateYesterday);
224
+ const dateFrom = ref("");
330
225
  const dateTo = ref("");
331
- const filterTypes = ref([]);
226
+ const filterTypes = ref<TVisitorType[]>([]);
332
227
  const displayCheckedOutOnly = ref<boolean>(false);
333
228
 
334
229
  const message = ref("");
@@ -349,8 +244,8 @@ const dialog = reactive({
349
244
  });
350
245
 
351
246
  const tabOptions = [
352
- { name: "Registered", type: "registered" },
353
- { name: "Unregistered", type: "unregistered" },
247
+ { name: "Registered", status: "registered" },
248
+ { name: "Unregistered", status: "unregistered" },
354
249
  ];
355
250
 
356
251
  const formatType = (item: any) =>
@@ -374,8 +269,8 @@ function filterTypeSelectionLabel() {
374
269
  return `${length} selected ${length === 1 ? "type" : "types"}` as string;
375
270
  }
376
271
 
377
- function toRoute(type: any) {
378
- const obj = tabOptions.find((x) => x.type === type);
272
+ function toRoute(status: any) {
273
+ const obj = tabOptions.find((x) => x.status === status);
379
274
  if (!obj) return;
380
275
  navigateTo({
381
276
  name: routeName,
@@ -383,7 +278,7 @@ function toRoute(type: any) {
383
278
  org: orgId,
384
279
  },
385
280
  query: {
386
- type: obj.type,
281
+ status: obj.status,
387
282
  },
388
283
  });
389
284
  }
@@ -393,20 +288,20 @@ const {
393
288
  refresh: getVisitorRefresh,
394
289
  pending: getVisitorPending,
395
290
  } = await useLazyAsyncData(
396
- `get-all-visitors-${visitorType}`,
291
+ `get-all-visitors-${activeTab.value}-${page.value}`
292
+ ,
397
293
  () =>
398
294
  getVisitors({
399
295
  page: page.value,
400
- org: orgId,
401
296
  site: siteId,
402
297
  search: searchInput.value,
403
298
  dateTo: dateTo.value,
404
299
  dateFrom: dateFrom.value,
405
300
  type: filterTypes.value.filter(Boolean).join(","),
406
- displayNoCheckOut: displayCheckedOutOnly.value,
301
+ status: activeTab.value as string,
407
302
  }),
408
303
  {
409
- watch: [page],
304
+ watch: [page, () => route.query],
410
305
  }
411
306
  );
412
307
 
@@ -419,9 +314,9 @@ watch(getVisitorReq, (newData: any) => {
419
314
  });
420
315
 
421
316
  const selectedVisitorObject = computed(() => {
422
- const obj = items.value.find((x) => x?._id === selectedVisitorId.value);
317
+ const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
423
318
  if (!obj) return {};
424
- const type = obj?.type;
319
+ const type = obj?.type as TVisitorType | undefined;
425
320
  if (!type) return {};
426
321
  let includedKeys: string[] = ["checkIn", "checkOut"];
427
322
  includedKeys.unshift(...(typeFieldMap[type] ?? []));
@@ -553,16 +448,42 @@ function getUTCDates() {
553
448
  return { dateYesterday, dateToday };
554
449
  }
555
450
 
556
- // filter debounce search
557
- const debounceSearch = debounce(getVisitorRefresh, 500);
451
+
452
+ const updateRouteQuery = debounce(
453
+ (search: string, from: string, to: string, types: string[], status) => {
454
+ router.replace({
455
+ query: {
456
+ ...route.query,
457
+ search: search || undefined,
458
+ dateFrom: from || undefined,
459
+ dateTo: to || undefined,
460
+ type: types.filter(Boolean).join(",") || undefined,
461
+ status: status || undefined,
462
+ },
463
+ });
464
+ },
465
+ 500 // wait 500ms after last input
466
+ );
558
467
 
559
468
  watch(
560
- [searchInput, dateTo, dateFrom, displayCheckedOutOnly, filterTypes],
561
- ([]) => {
562
- debounceSearch();
469
+ [searchInput, dateFrom, dateTo, filterTypes, activeTab],
470
+ ([search, from, to, types, status]) => {
471
+ updateRouteQuery(search, from, to, types, status)
472
+
563
473
  },
564
- { immediate: false, deep: true }
474
+ { deep: true }
565
475
  );
476
+
477
+
478
+
479
+ onMounted(() => {
480
+ activeTab.value = (route.query.status as string) || "unregistered"
481
+ searchInput.value = (route.query.search as string) || "";
482
+ dateFrom.value = (route.query.dateFrom as string) || "";
483
+ dateTo.value = (route.query.dateTo as string) || "";
484
+ filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(Boolean) as TVisitorType[];
485
+
486
+ })
566
487
  </script>
567
488
 
568
489
  <style scoped></style>
@@ -16,18 +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="workOrder.subject"
24
- class="mb-1"
25
- dense
28
+ class="mb-2"
29
+ :clearable="
30
+ props.createdFrom === 'feedback' && workOrder.subject !== ''
31
+ ? false
32
+ : true
33
+ "
26
34
  :readonly="
27
35
  props.createdFrom === 'feedback' && workOrder.subject !== ''
28
36
  ? true
29
37
  : false
30
38
  "
39
+ :custom-filter="customFilter"
31
40
  />
32
41
 
33
42
  <v-autocomplete
@@ -239,4 +248,13 @@ watch(workOrderUnit, (val: string) => {
239
248
  }
240
249
  }
241
250
  });
251
+
252
+ const { subjects } = useLocal();
253
+
254
+ const customFilter = (value: string, query: string, item: any) => {
255
+ const title = item?.raw?.title?.toLowerCase() || "";
256
+ const subtitle = item?.raw?.subtitle?.toLowerCase() || "";
257
+ const search = query.toLowerCase();
258
+ return title.includes(search) || subtitle.includes(search);
259
+ };
242
260
  </script>
@@ -133,6 +133,10 @@ const props = defineProps({
133
133
  type: Boolean,
134
134
  default: false,
135
135
  },
136
+ category: {
137
+ type: String,
138
+ default: "",
139
+ },
136
140
  });
137
141
 
138
142
  const theme = useTheme().name;
@@ -193,11 +197,11 @@ const {
193
197
  data: getAllWorkOrderReq,
194
198
  refresh: getAllReqRefresh,
195
199
  status: getAllReqStatus,
196
- } = useLazyAsyncData("get-all-work-orders", () =>
200
+ } = useLazyAsyncData("get-all-work-orders-" + props.category, () =>
197
201
  _getWorkOrders({
198
202
  page: page.value,
199
- organization: route.params.org as string,
200
203
  site: route.params.site as string,
204
+ category: props.category,
201
205
  })
202
206
  );
203
207
 
@@ -286,7 +290,7 @@ watchEffect(() => {
286
290
  serviceProviders.value = getAllReq.value.map((i: any) => ({
287
291
  title: i.nature.replace(/_/g, " "),
288
292
  subtitle: i.title,
289
- value: i._id.org,
293
+ value: i.nature
290
294
  }));
291
295
  }
292
296
  });
@@ -222,7 +222,7 @@ export default function useBuilding() {
222
222
 
223
223
  function updateById(
224
224
  id: string,
225
- data: Pick<TBuilding, "name" | "block" | "levels">
225
+ data: Pick<TBuilding, "name" | "block" | "levels" | "buildingFloorPlan">
226
226
  ) {
227
227
  return useNuxtApp().$api(`/api/buildings/id/${id}`, {
228
228
  method: "PATCH",
@@ -19,6 +19,12 @@ export default function(){
19
19
  });
20
20
  }
21
21
 
22
+ async function findPersonByNRIC(nric: string): Promise<null | Partial<TPeople>> {
23
+ return await $fetch<Record<string, any>>(`/api/people/nric/${nric}`, {
24
+ method: "GET",
25
+ });
26
+ }
27
+
22
28
  async function create(payload: Partial<TPeoplePayload>){
23
29
  return await useNuxtApp().$api<Record<string, any>>("/api/people", {
24
30
  method: "POST",
@@ -39,10 +45,12 @@ export default function(){
39
45
  });
40
46
  }
41
47
 
48
+
42
49
  return {
43
50
  create,
44
51
  getAll,
45
52
  updateById,
46
- deleteById
53
+ deleteById,
54
+ findPersonByNRIC
47
55
  }
48
56
  }