@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.
- package/CHANGELOG.md +21 -0
- package/components/CameraForm.vue +264 -0
- package/components/CameraMain.vue +352 -0
- package/components/Card/DeleteConfirmation.vue +51 -0
- package/components/Card/MemberInfoSummary.vue +44 -0
- package/components/Chat/Information.vue +28 -11
- package/components/Dialog/DeleteConfirmation.vue +51 -0
- package/components/Dialog/UpdateMoreAction.vue +99 -0
- package/components/Feedback/Form.vue +17 -3
- package/components/FeedbackDetail.vue +0 -11
- package/components/FeedbackMain.vue +21 -11
- package/components/Input/DateTimePicker.vue +10 -5
- package/components/Input/File.vue +1 -1
- package/components/Input/FileV2.vue +106 -63
- package/components/Input/InputPhoneNumberV2.vue +114 -0
- package/components/Input/NRICNumber.vue +41 -0
- package/components/Input/PhoneNumber.vue +1 -0
- package/components/Input/VehicleNumber.vue +49 -0
- package/components/NumberSettingField.vue +107 -0
- package/components/PeopleForm.vue +452 -0
- package/components/TableMain.vue +2 -1
- package/components/VehicleUpdateMoreAction.vue +84 -0
- package/components/VisitorForm.vue +712 -0
- package/components/VisitorFormSelection.vue +53 -0
- package/components/VisitorManagement.vue +569 -0
- package/components/WorkOrder/Create.vue +87 -49
- package/components/WorkOrder/Main.vue +17 -12
- package/composables/useBuilding.ts +250 -0
- package/composables/useBuildingUnit.ts +116 -0
- package/composables/useFeedback.ts +3 -3
- package/composables/useFile.ts +7 -9
- package/composables/useLocal.ts +67 -0
- package/composables/usePeople.ts +48 -0
- package/composables/useSecurityUtils.ts +18 -0
- package/composables/useSiteSettings.ts +111 -0
- package/composables/useUtils.ts +30 -1
- package/composables/useVisitor.ts +80 -0
- package/composables/useWorkOrder.ts +3 -3
- package/package.json +1 -1
- package/plugins/vuetify.ts +6 -1
- package/types/building.d.ts +19 -0
- package/types/camera.d.ts +31 -0
- package/types/people.d.ts +22 -0
- package/types/select.d.ts +4 -0
- package/types/site.d.ts +10 -7
- package/types/visitor.d.ts +42 -0
- package/utils/phoneMasks.ts +1703 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
+
Add Visitor
|
|
7
|
+
</span>
|
|
8
|
+
</v-row>
|
|
9
|
+
</v-toolbar>
|
|
10
|
+
|
|
11
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5">
|
|
12
|
+
<span class="text-subtitle-2 w-100 font-weight-medium d-flex justify-center mb-3">Please Select Visitor Type</span>
|
|
13
|
+
<template v-for="item in selection" :key="item.value">
|
|
14
|
+
<v-btn color="primary-button" block variant="flat" rounded="md" size="48" :text="item.label" @click="select(item.value)" class="my-2 text-capitalize text-subtitle-2" />
|
|
15
|
+
</template>
|
|
16
|
+
</v-card-text>
|
|
17
|
+
|
|
18
|
+
<v-toolbar density="compact">
|
|
19
|
+
<v-row no-gutters>
|
|
20
|
+
<v-col cols="12">
|
|
21
|
+
<v-btn
|
|
22
|
+
tile
|
|
23
|
+
block
|
|
24
|
+
variant="text"
|
|
25
|
+
class="text-none"
|
|
26
|
+
size="48"
|
|
27
|
+
@click="cancel"
|
|
28
|
+
>
|
|
29
|
+
Cancel
|
|
30
|
+
</v-btn>
|
|
31
|
+
</v-col>
|
|
32
|
+
</v-row>
|
|
33
|
+
</v-toolbar>
|
|
34
|
+
</v-card>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
const prop = defineProps({
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(['cancel', 'select']);
|
|
42
|
+
const { visitorSelection: selection} = useVisitor();
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function cancel() {
|
|
47
|
+
emit("cancel");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function select(value: string) {
|
|
51
|
+
emit("select", value);
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
<template>
|
|
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
|
+
>
|
|
19
|
+
<template #extension>
|
|
20
|
+
<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.status"
|
|
31
|
+
:key="tab.status"
|
|
32
|
+
class="text-capitalize"
|
|
33
|
+
>
|
|
34
|
+
{{ tab.name }}
|
|
35
|
+
</v-tab>
|
|
36
|
+
</v-tabs>
|
|
37
|
+
|
|
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
|
+
>
|
|
57
|
+
<template #label>
|
|
58
|
+
<span class="text-caption">Not Checked Out</span>
|
|
59
|
+
</template>
|
|
60
|
+
</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
|
+
>
|
|
83
|
+
<template v-slot:selection="{ item, index }">
|
|
84
|
+
<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
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
</v-select>
|
|
96
|
+
</v-card>
|
|
97
|
+
</v-row>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<template v-slot:item.name="{ item }">
|
|
101
|
+
<span class="d-flex align-center ga-2">
|
|
102
|
+
<span>
|
|
103
|
+
<AvatarMain :name="item?.name" :size="20" :id="item?._id" />
|
|
104
|
+
</span>
|
|
105
|
+
<span class="text-capitalize">{{ item?.name }}</span>
|
|
106
|
+
</span>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<template v-slot:item.type-company="{ item }">
|
|
110
|
+
<span class="d-flex align-center ga-2">
|
|
111
|
+
<v-icon icon="mdi-user" size="15" />
|
|
112
|
+
<span v-if="item.type === 'contractor'" class="text-capitalize">{{
|
|
113
|
+
formatCamelCaseToWords(item.contractorType)
|
|
114
|
+
}}</span>
|
|
115
|
+
<span v-else class="text-capitalize">{{ formatType(item) }}</span>
|
|
116
|
+
</span>
|
|
117
|
+
<span class="d-flex align-center ga-2">
|
|
118
|
+
<v-icon icon="mdi-domain" size="15" />
|
|
119
|
+
<span class="text-capitalize">{{ item?.company || "N/A" }}</span>
|
|
120
|
+
</span>
|
|
121
|
+
</template>
|
|
122
|
+
|
|
123
|
+
<template v-slot:item.location="{ item }">
|
|
124
|
+
<span class="d-flex align-center ga-2">
|
|
125
|
+
<v-icon icon="mdi-storefront-outline" size="15" />
|
|
126
|
+
<span class="text-capitalize">{{
|
|
127
|
+
formatLocation({
|
|
128
|
+
block: item.block,
|
|
129
|
+
level: item.level,
|
|
130
|
+
unit: item.unitName,
|
|
131
|
+
})
|
|
132
|
+
}}</span>
|
|
133
|
+
</span>
|
|
134
|
+
</template>
|
|
135
|
+
|
|
136
|
+
<template v-slot:item.contact-vehicleNumber="{ item }">
|
|
137
|
+
<span class="d-flex align-center ga-2">
|
|
138
|
+
<v-icon icon="mdi-phone" size="15" />
|
|
139
|
+
<span class="text-capitalize">{{ item?.contact || "N/A" }}</span>
|
|
140
|
+
</span>
|
|
141
|
+
<span class="d-flex align-center ga-2">
|
|
142
|
+
<v-icon icon="mdi-car-back" size="15" />
|
|
143
|
+
<span class="text-capitalize">{{ item?.plateNumber || "N/A" }}</span>
|
|
144
|
+
</span>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<template v-slot:item.checkin-out="{ item }">
|
|
148
|
+
<span class="d-flex align-center ga-2">
|
|
149
|
+
<v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
|
|
150
|
+
<span class="text-capitalize">{{
|
|
151
|
+
UTCToLocalTIme(item.checkIn) || "-"
|
|
152
|
+
}}</span>
|
|
153
|
+
</span>
|
|
154
|
+
<span class="d-flex align-center ga-2">
|
|
155
|
+
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" v-if="item.checkOut" />
|
|
156
|
+
<template v-if="item.checkOut">
|
|
157
|
+
<span class="text-capitalize">{{
|
|
158
|
+
UTCToLocalTIme(item.checkOut) || "_"
|
|
159
|
+
}}</span>
|
|
160
|
+
<span v-if="item?.manualCheckout">
|
|
161
|
+
<TooltipInfo
|
|
162
|
+
text="Manual Checkout"
|
|
163
|
+
density="compact"
|
|
164
|
+
size="x-small"
|
|
165
|
+
/>
|
|
166
|
+
</span>
|
|
167
|
+
</template>
|
|
168
|
+
<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
|
+
/>
|
|
178
|
+
</span>
|
|
179
|
+
</span>
|
|
180
|
+
</template>
|
|
181
|
+
</TableMain>
|
|
182
|
+
|
|
183
|
+
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
184
|
+
<VisitorFormSelection
|
|
185
|
+
@cancel="dialog.showSelection = false"
|
|
186
|
+
@select="handleSelectVisitorType"
|
|
187
|
+
/>
|
|
188
|
+
</v-dialog>
|
|
189
|
+
|
|
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
|
+
/>
|
|
205
|
+
</v-dialog>
|
|
206
|
+
|
|
207
|
+
<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
|
+
>
|
|
217
|
+
<template v-slot:content>
|
|
218
|
+
<v-row no-gutters class="mb-4">
|
|
219
|
+
<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
|
+
>
|
|
224
|
+
<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
|
+
/>
|
|
233
|
+
</span>
|
|
234
|
+
|
|
235
|
+
<span
|
|
236
|
+
v-else-if="selectedVisitorObject[key]"
|
|
237
|
+
class="d-flex ga-3 align-center"
|
|
238
|
+
><strong>{{ label }}:</strong>
|
|
239
|
+
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
240
|
+
<TooltipInfo
|
|
241
|
+
v-if="key === 'checkOut'"
|
|
242
|
+
text="Manual Checkout"
|
|
243
|
+
density="compact"
|
|
244
|
+
size="x-small"
|
|
245
|
+
/>
|
|
246
|
+
</span>
|
|
247
|
+
</v-col>
|
|
248
|
+
</v-row>
|
|
249
|
+
</template>
|
|
250
|
+
</VehicleUpdateMoreAction>
|
|
251
|
+
</v-dialog>
|
|
252
|
+
|
|
253
|
+
<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
|
+
/>
|
|
260
|
+
</v-dialog>
|
|
261
|
+
|
|
262
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
263
|
+
</v-row>
|
|
264
|
+
</template>
|
|
265
|
+
|
|
266
|
+
<script setup lang="ts">
|
|
267
|
+
definePageMeta({
|
|
268
|
+
middleware: ["01-auth", "02-org"],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const props = defineProps({
|
|
272
|
+
canAddVisitor: {
|
|
273
|
+
type: Boolean,
|
|
274
|
+
default: true,
|
|
275
|
+
},
|
|
276
|
+
canViewVisitor: {
|
|
277
|
+
type: Boolean,
|
|
278
|
+
default: true,
|
|
279
|
+
},
|
|
280
|
+
canUpdateVisitor: {
|
|
281
|
+
type: Boolean,
|
|
282
|
+
default: true,
|
|
283
|
+
},
|
|
284
|
+
canDeleteVisitor: {
|
|
285
|
+
type: Boolean,
|
|
286
|
+
default: true,
|
|
287
|
+
},
|
|
288
|
+
canCheckoutVisitor: {
|
|
289
|
+
type: Boolean,
|
|
290
|
+
default: true,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const headers = [
|
|
295
|
+
{ title: "Name", value: "name" },
|
|
296
|
+
{ title: "Type/Company", value: "type-company" },
|
|
297
|
+
{ title: "Location", value: "location" },
|
|
298
|
+
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" },
|
|
299
|
+
{ title: "Check In/Out", value: "checkin-out" },
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const {
|
|
303
|
+
getVisitors,
|
|
304
|
+
visitorSelection,
|
|
305
|
+
typeFieldMap,
|
|
306
|
+
deleteVisitor,
|
|
307
|
+
updateVisitor,
|
|
308
|
+
} = useVisitor();
|
|
309
|
+
const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
|
|
310
|
+
useUtils();
|
|
311
|
+
const { formatLocation } = useSecurityUtils();
|
|
312
|
+
const { status: visitorStatus } = useRoute().query;
|
|
313
|
+
const { org: orgId, site: siteId } = useRoute().params as {
|
|
314
|
+
org: string;
|
|
315
|
+
site: string;
|
|
316
|
+
};
|
|
317
|
+
const routeName = useRoute().name;
|
|
318
|
+
|
|
319
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
320
|
+
const page = ref(1);
|
|
321
|
+
const pages = ref(0);
|
|
322
|
+
const pageRange = ref("-- - -- of --");
|
|
323
|
+
const activeTab = ref(visitorStatus ?? "registered");
|
|
324
|
+
const activeVisitorFormType = ref<TVisitorType | null>(null);
|
|
325
|
+
const selectedVisitorId = ref<string | null>(""); // selected visitor for viewing/actions
|
|
326
|
+
|
|
327
|
+
//filter states
|
|
328
|
+
const searchInput = ref("");
|
|
329
|
+
const dateFrom = ref("");
|
|
330
|
+
const dateTo = ref("");
|
|
331
|
+
const filterTypes = ref([]);
|
|
332
|
+
const displayCheckedOutOnly = ref<boolean>(false);
|
|
333
|
+
|
|
334
|
+
const message = ref("");
|
|
335
|
+
const messageColor = ref("");
|
|
336
|
+
const messageSnackbar = ref(false);
|
|
337
|
+
|
|
338
|
+
const loading = reactive({
|
|
339
|
+
deletingVisitor: false,
|
|
340
|
+
fetchingVisitors: false,
|
|
341
|
+
checkingOut: false,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const dialog = reactive({
|
|
345
|
+
showSelection: false,
|
|
346
|
+
addVisitor: false,
|
|
347
|
+
viewVisitor: false,
|
|
348
|
+
deleteConfirmation: false,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const tabOptions = [
|
|
352
|
+
{ name: "Registered", status: "registered" },
|
|
353
|
+
{ name: "Unregistered", status: "unregistered" },
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const formatType = (item: any) =>
|
|
357
|
+
(item.deliveryType ? item.deliveryType + "-" : "") + item.type;
|
|
358
|
+
|
|
359
|
+
const formattedFields = {
|
|
360
|
+
name: "Name",
|
|
361
|
+
nric: "NRIC",
|
|
362
|
+
contact: "Phone Number",
|
|
363
|
+
plateNumber: "Vehicle Number",
|
|
364
|
+
block: "Block",
|
|
365
|
+
level: "Level",
|
|
366
|
+
unitName: "Unit",
|
|
367
|
+
checkIn: "Check In",
|
|
368
|
+
checkOut: "Check Out",
|
|
369
|
+
remarks: "Remarks",
|
|
370
|
+
} as const;
|
|
371
|
+
|
|
372
|
+
function filterTypeSelectionLabel() {
|
|
373
|
+
const length = filterTypes.value.length;
|
|
374
|
+
return `${length} selected ${length === 1 ? "type" : "types"}` as string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function toRoute(status: any) {
|
|
378
|
+
const obj = tabOptions.find((x) => x.status === status);
|
|
379
|
+
if (!obj) return;
|
|
380
|
+
navigateTo({
|
|
381
|
+
name: routeName,
|
|
382
|
+
params: {
|
|
383
|
+
org: orgId,
|
|
384
|
+
},
|
|
385
|
+
query: {
|
|
386
|
+
status: obj.status,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const {
|
|
392
|
+
data: getVisitorReq,
|
|
393
|
+
refresh: getVisitorRefresh,
|
|
394
|
+
pending: getVisitorPending,
|
|
395
|
+
} = await useLazyAsyncData(
|
|
396
|
+
`get-all-visitors-${visitorStatus}`,
|
|
397
|
+
() =>
|
|
398
|
+
getVisitors({
|
|
399
|
+
page: page.value,
|
|
400
|
+
org: orgId,
|
|
401
|
+
site: siteId,
|
|
402
|
+
search: searchInput.value,
|
|
403
|
+
dateTo: dateTo.value,
|
|
404
|
+
dateFrom: dateFrom.value,
|
|
405
|
+
type: filterTypes.value.filter(Boolean).join(","),
|
|
406
|
+
displayNoCheckOut: displayCheckedOutOnly.value,
|
|
407
|
+
status: activeTab.value as string
|
|
408
|
+
}),
|
|
409
|
+
{
|
|
410
|
+
watch: [page, activeTab],
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
watch(getVisitorReq, (newData: any) => {
|
|
415
|
+
if (newData) {
|
|
416
|
+
items.value = newData.items ?? [];
|
|
417
|
+
pages.value = newData.pages ?? 0;
|
|
418
|
+
pageRange.value = newData?.pageRange ?? "-- - -- of --";
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const selectedVisitorObject = computed(() => {
|
|
423
|
+
const obj = items.value.find((x) => x?._id === selectedVisitorId.value);
|
|
424
|
+
if (!obj) return {};
|
|
425
|
+
const type = obj?.type;
|
|
426
|
+
if (!type) return {};
|
|
427
|
+
let includedKeys: string[] = ["checkIn", "checkOut"];
|
|
428
|
+
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
429
|
+
return Object.fromEntries(
|
|
430
|
+
Object.entries(obj).filter(([key]) => includedKeys.includes(key))
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
function formatValues(key: string, value: any) {
|
|
435
|
+
if (!value) return "";
|
|
436
|
+
switch (key) {
|
|
437
|
+
case "unit":
|
|
438
|
+
return value?.name;
|
|
439
|
+
case "checkIn":
|
|
440
|
+
return formatDate(value);
|
|
441
|
+
case "checkOut":
|
|
442
|
+
return formatDate(value);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function handleRowClick(data: any) {
|
|
449
|
+
selectedVisitorId.value = data?.item?._id;
|
|
450
|
+
dialog.viewVisitor = true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function handleUpdatePage(newPageNum: number) {
|
|
454
|
+
page.value = newPageNum;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function handleSelectVisitorType(type: TVisitorType) {
|
|
458
|
+
dialog.showSelection = false;
|
|
459
|
+
dialog.addVisitor = true;
|
|
460
|
+
activeVisitorFormType.value = type;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function handleClickBack() {
|
|
464
|
+
dialog.showSelection = true;
|
|
465
|
+
dialog.addVisitor = false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function handleVisitorFormDone() {
|
|
469
|
+
getVisitorRefresh();
|
|
470
|
+
dialog.showSelection = false;
|
|
471
|
+
dialog.addVisitor = false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function handleVisitorFormCreateMore() {
|
|
475
|
+
getVisitorRefresh();
|
|
476
|
+
dialog.showSelection = true;
|
|
477
|
+
dialog.addVisitor = false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function handleDeleteVisitor() {
|
|
481
|
+
dialog.deleteConfirmation = true;
|
|
482
|
+
dialog.viewVisitor = false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function showMessage(msg: string, color: string) {
|
|
486
|
+
message.value = msg;
|
|
487
|
+
messageColor.value = color;
|
|
488
|
+
messageSnackbar.value = true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function handleProceedDeleteVisitor() {
|
|
492
|
+
try {
|
|
493
|
+
loading.deletingVisitor = true;
|
|
494
|
+
const userId = selectedVisitorId.value;
|
|
495
|
+
const res = await deleteVisitor(userId as string);
|
|
496
|
+
if (res) {
|
|
497
|
+
showMessage("Visitor successfully deleted!", "info");
|
|
498
|
+
await getVisitorRefresh();
|
|
499
|
+
dialog.deleteConfirmation = false;
|
|
500
|
+
}
|
|
501
|
+
} catch (error: any) {
|
|
502
|
+
const errorMessage = error?.response?._data?.message;
|
|
503
|
+
console.log("[ERROR]", error);
|
|
504
|
+
showMessage(
|
|
505
|
+
errorMessage || "Something went wrong. Please try again later.",
|
|
506
|
+
"error"
|
|
507
|
+
);
|
|
508
|
+
} finally {
|
|
509
|
+
loading.deletingVisitor = false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function handleCheckout(userId: string) {
|
|
514
|
+
if (!userId) {
|
|
515
|
+
showMessage("Invalid userId", "error");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
selectedVisitorId.value = userId;
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
loading.checkingOut = true;
|
|
522
|
+
const res = await updateVisitor(userId as string, {
|
|
523
|
+
checkOut: new Date().toISOString(),
|
|
524
|
+
});
|
|
525
|
+
if (res) {
|
|
526
|
+
showMessage("Visitor successfully checked-out!", "info");
|
|
527
|
+
await getVisitorRefresh();
|
|
528
|
+
dialog.viewVisitor = false;
|
|
529
|
+
}
|
|
530
|
+
} catch (error: any) {
|
|
531
|
+
const errorMessage = error?.response?._data?.message;
|
|
532
|
+
console.log("[ERROR]", error);
|
|
533
|
+
showMessage(
|
|
534
|
+
errorMessage || "Something went wrong. Please try again later.",
|
|
535
|
+
"error"
|
|
536
|
+
);
|
|
537
|
+
} finally {
|
|
538
|
+
loading.checkingOut = false;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// get dates in ISO String (UTC Time)
|
|
543
|
+
function getUTCDates() {
|
|
544
|
+
const today = new Date();
|
|
545
|
+
const yesterday = new Date();
|
|
546
|
+
yesterday.setUTCDate(today.getUTCDate() - 1);
|
|
547
|
+
|
|
548
|
+
const dateFrom = ref(yesterday.toISOString()); // yesterday in UTC
|
|
549
|
+
const dateTo = ref(today.toISOString()); // today in UTC
|
|
550
|
+
|
|
551
|
+
const dateYesterday = yesterday.toISOString();
|
|
552
|
+
const dateToday = today.toISOString();
|
|
553
|
+
|
|
554
|
+
return { dateYesterday, dateToday };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// filter debounce search
|
|
558
|
+
const debounceSearch = debounce(getVisitorRefresh, 500);
|
|
559
|
+
|
|
560
|
+
watch(
|
|
561
|
+
[searchInput, dateTo, dateFrom, displayCheckedOutOnly, filterTypes],
|
|
562
|
+
([]) => {
|
|
563
|
+
debounceSearch();
|
|
564
|
+
},
|
|
565
|
+
{ immediate: false, deep: true }
|
|
566
|
+
);
|
|
567
|
+
</script>
|
|
568
|
+
|
|
569
|
+
<style scoped></style>
|