@7365admin1/layer-common 1.11.14 → 1.11.16
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 +13 -0
- package/components/AreaMain.vue +1 -1
- package/components/AttendanceCheckInOutDialog.vue +1 -1
- package/components/EquipmentManagementMain.vue +5 -2
- package/components/MemberInformation.vue +121 -0
- package/components/OvernightParkingManagement.vue +48 -2
- package/components/PassInformation.vue +126 -0
- package/components/ScheduleTaskForm.vue +1 -1
- package/components/ScheduleTaskMain.vue +76 -2
- package/components/UnitMain.vue +1 -1
- package/components/VisitorForm.vue +3 -8
- package/components/VisitorManagement.vue +42 -14
- package/composables/useAreas.ts +3 -4
- package/composables/useEquipmentManagement.ts +22 -1
- package/composables/useScheduleTask.ts +12 -0
- package/composables/useStock.ts +1 -12
- package/composables/useUnits.ts +3 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.11.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 59db5ff: Update Layer common register
|
|
8
|
+
- acbb54d: Update Visitor Management
|
|
9
|
+
|
|
10
|
+
## 1.11.15
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 1f2fbb4: Update Layer-commmon pacakge
|
|
15
|
+
|
|
3
16
|
## 1.11.14
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/components/AreaMain.vue
CHANGED
|
@@ -570,7 +570,7 @@ async function handleDownloadExcel() {
|
|
|
570
570
|
return;
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
const excelBuffer = await exportAreas(props.site);
|
|
573
|
+
const excelBuffer = await exportAreas(props.site, props.serviceType);
|
|
574
574
|
|
|
575
575
|
if (!excelBuffer) {
|
|
576
576
|
showMessage("Failed to download areas.", "error");
|
|
@@ -249,6 +249,7 @@ import useEquipment from "../composables/useEquipment";
|
|
|
249
249
|
import useStock from "../composables/useStock";
|
|
250
250
|
import { useEquipmentPermission } from "../composables/useEquipmentPermission";
|
|
251
251
|
import useUtils from "../composables/useUtils";
|
|
252
|
+
import useEquipmentManagement from "../composables/useEquipmentManagement";
|
|
252
253
|
|
|
253
254
|
const props = defineProps({
|
|
254
255
|
orgId: { type: String, default: "" },
|
|
@@ -497,7 +498,8 @@ async function _deleteEquipment() {
|
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
const { createStock
|
|
501
|
+
const { createStock } = useStock();
|
|
502
|
+
const { checkOutItemSingle } = useEquipmentManagement();
|
|
501
503
|
|
|
502
504
|
const stockActionQuantity = ref<number>(0);
|
|
503
505
|
const stockActionRemarks = ref("");
|
|
@@ -535,9 +537,10 @@ async function _requestItem() {
|
|
|
535
537
|
|
|
536
538
|
const payload: TStockCreate = {
|
|
537
539
|
qty: stockActionQuantity.value,
|
|
540
|
+
serviceType: props.serviceType,
|
|
538
541
|
};
|
|
539
542
|
|
|
540
|
-
const response = await
|
|
543
|
+
const response = await checkOutItemSingle(props.site, id, payload);
|
|
541
544
|
showMessage(response?.message, "success");
|
|
542
545
|
dialogStockAction.value = false;
|
|
543
546
|
stockActionQuantity.value = 0;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="w-100">
|
|
3
|
+
<v-card class="w-100">
|
|
4
|
+
<v-card-text>
|
|
5
|
+
<v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
|
|
6
|
+
prepend-icon="mdi-qrcode" />
|
|
7
|
+
<v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
|
|
8
|
+
<v-row no-gutters class="pt-5 ga-2">
|
|
9
|
+
<v-col cols="12">
|
|
10
|
+
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
11
|
+
<v-text-field v-model.trim="memberForm.name" density="compact" hide-details
|
|
12
|
+
:rules="[requiredRule]" />
|
|
13
|
+
</v-col>
|
|
14
|
+
|
|
15
|
+
<v-col cols="12">
|
|
16
|
+
<InputLabel class="text-capitalize" title="NRIC" required />
|
|
17
|
+
<InputNRICNumber v-model.trim="memberForm.nric" density="compact" hide-details
|
|
18
|
+
:rules="[requiredRule]" />
|
|
19
|
+
</v-col>
|
|
20
|
+
|
|
21
|
+
<v-col cols="12">
|
|
22
|
+
<v-combobox v-model="pass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
|
|
23
|
+
@update:focused="handleFocusedPass" :items="passItems" item-value="value"
|
|
24
|
+
@update:model-value="handleSelectPass" label="Pass (optional)" variant="outlined"
|
|
25
|
+
hide-details density="compact" persistent-hint small-chips>
|
|
26
|
+
<template v-slot:no-data>
|
|
27
|
+
<v-list-item density="compact">
|
|
28
|
+
<v-list-item-title v-if="passInput">
|
|
29
|
+
No results matching "<strong>{{ passInput }}</strong>". This value will be
|
|
30
|
+
added as new
|
|
31
|
+
option.
|
|
32
|
+
</v-list-item-title>
|
|
33
|
+
<v-list-item v-else>No data available</v-list-item>
|
|
34
|
+
</v-list-item>
|
|
35
|
+
</template>
|
|
36
|
+
</v-combobox>
|
|
37
|
+
</v-col>
|
|
38
|
+
|
|
39
|
+
<v-col cols="12">
|
|
40
|
+
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
41
|
+
<InputPhoneNumberV2 v-model="memberForm.contact" density="compact" hide-details/>
|
|
42
|
+
</v-col>
|
|
43
|
+
|
|
44
|
+
<v-row class="pt-3" justify="space-between">
|
|
45
|
+
<v-col cols="6">
|
|
46
|
+
<v-btn text="Add Member" :disabled="!validForm" prepend-icon="mdi-plus"
|
|
47
|
+
color="primary-button" class="text-capitalize" @click="handleAddMember" />
|
|
48
|
+
</v-col>
|
|
49
|
+
<v-col cols="6" align="end">
|
|
50
|
+
<v-btn text="Clear Form" prepend-icon="mdi-refresh" color="primary-button"
|
|
51
|
+
class="text-capitalize" @click.stop="handleClearForm" />
|
|
52
|
+
</v-col>
|
|
53
|
+
</v-row>
|
|
54
|
+
</v-row>
|
|
55
|
+
</v-form>
|
|
56
|
+
</v-card-text>
|
|
57
|
+
</v-card>
|
|
58
|
+
<v-divider class="my-3" />
|
|
59
|
+
<v-row v-if="members.length > 0" no-gutters class="w-100 mt-5 ga-3">
|
|
60
|
+
<template v-for="member, index in members" :key="member.nric">
|
|
61
|
+
<CardMemberInfoSummary :member="member" @remove="handleRemoveMember(index)" />
|
|
62
|
+
</template>
|
|
63
|
+
</v-row>
|
|
64
|
+
</v-row>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
|
|
69
|
+
const props = defineProps({
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const { requiredRule } = useUtils()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const memberForm = reactive<TMemberInfo>({
|
|
76
|
+
name: "",
|
|
77
|
+
nric: "",
|
|
78
|
+
visitorPass: "",
|
|
79
|
+
contact: ""
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const validForm = ref(false)
|
|
83
|
+
const formRef = ref<HTMLFormElement>()
|
|
84
|
+
const processing = ref(false);
|
|
85
|
+
const errorMessage = ref('')
|
|
86
|
+
|
|
87
|
+
// pass
|
|
88
|
+
const pass = ref()
|
|
89
|
+
const passInput = ref('')
|
|
90
|
+
const passItems = ref(['Test'])
|
|
91
|
+
|
|
92
|
+
const members = defineModel<TMemberInfo[]>({required: true, default: []})
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
function handleFocusedPass() {
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function handleSelectPass(val: any) {
|
|
100
|
+
memberForm.visitorPass = val
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleClearForm() {
|
|
104
|
+
formRef.value?.reset()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function handleAddMember() {
|
|
108
|
+
members.value.push({...memberForm})
|
|
109
|
+
handleClearForm()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleRemoveMember(index: number){
|
|
113
|
+
const filtered = members.value.filter((item, qIndex) => qIndex !== index)
|
|
114
|
+
members.value = filtered
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<style scoped></style>
|
|
@@ -66,6 +66,22 @@
|
|
|
66
66
|
</template>
|
|
67
67
|
</TableMain>
|
|
68
68
|
|
|
69
|
+
<v-dialog v-model="viewDialog" width="450" persistent>
|
|
70
|
+
<VehicleUpdateMoreAction title="Preview" :can-update="false" :can-delete="false"
|
|
71
|
+
@close="viewDialog = false">
|
|
72
|
+
<template v-slot:content>
|
|
73
|
+
<v-row no-gutters class="mb-4">
|
|
74
|
+
<v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
|
|
75
|
+
<span v-if="selectedItem?.[key] !== undefined && selectedItem?.[key] !== null && selectedItem?.[key] !== ''" class="d-flex ga-3 align-center">
|
|
76
|
+
<strong>{{ label }}:</strong>
|
|
77
|
+
{{ formatItemValue(key, selectedItem[key]) }}
|
|
78
|
+
</span>
|
|
79
|
+
</v-col>
|
|
80
|
+
</v-row>
|
|
81
|
+
</template>
|
|
82
|
+
</VehicleUpdateMoreAction>
|
|
83
|
+
</v-dialog>
|
|
84
|
+
|
|
69
85
|
<v-dialog v-model="actionDialog.open" max-width="520" persistent>
|
|
70
86
|
<v-card>
|
|
71
87
|
<v-card-title class="text-subtitle-1 font-weight-bold pt-4 px-4">
|
|
@@ -109,7 +125,7 @@ const props = defineProps({
|
|
|
109
125
|
|
|
110
126
|
});
|
|
111
127
|
|
|
112
|
-
const emits = defineEmits(["refresh", "create", "
|
|
128
|
+
const emits = defineEmits(["refresh", "create", "update:page"]);
|
|
113
129
|
|
|
114
130
|
const searchInput = defineModel<string>("search", { default: "" });
|
|
115
131
|
|
|
@@ -124,6 +140,35 @@ const extensionHeight = ref(0)
|
|
|
124
140
|
const offset = ref(0)
|
|
125
141
|
const createLabel = "New Overnight Parking Request"
|
|
126
142
|
|
|
143
|
+
const viewDialog = ref(false)
|
|
144
|
+
const selectedItem = ref<TOvernightParkingRequest | null>(null)
|
|
145
|
+
|
|
146
|
+
const formattedFields = {
|
|
147
|
+
name: "Name",
|
|
148
|
+
email: "Email",
|
|
149
|
+
contact: "Contact",
|
|
150
|
+
isOvernightParking: "Overnight Parking",
|
|
151
|
+
numberOfPassengers: "No. of Passengers",
|
|
152
|
+
purposeOfVisit: "Purpose of Visit",
|
|
153
|
+
status: "Status",
|
|
154
|
+
remarks: "Remarks",
|
|
155
|
+
createdAt: "Date Requested",
|
|
156
|
+
} as const
|
|
157
|
+
|
|
158
|
+
function formatItemValue(key: string, value: any) {
|
|
159
|
+
if (value === undefined || value === null || value === '') return ''
|
|
160
|
+
switch (key) {
|
|
161
|
+
case 'createdAt':
|
|
162
|
+
case 'updatedAt':
|
|
163
|
+
return formatDateDDMMYYYYLocal(value)
|
|
164
|
+
case 'isOvernightParking':
|
|
165
|
+
return value ? 'Yes' : 'No'
|
|
166
|
+
case 'status':
|
|
167
|
+
return formatStatus(value as TOvernightParkingRequest['status']).label
|
|
168
|
+
}
|
|
169
|
+
return value
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
const remarks = ref('')
|
|
128
173
|
const actionLoading = ref(false)
|
|
129
174
|
const actionDialog = reactive<{
|
|
@@ -197,7 +242,8 @@ watch(overnightParkingRequests, (data: any) => {
|
|
|
197
242
|
// }
|
|
198
243
|
|
|
199
244
|
function handleRowClick(data: { item?: TOvernightParkingRequest }) {
|
|
200
|
-
|
|
245
|
+
selectedItem.value = data?.item ?? null
|
|
246
|
+
viewDialog.value = true
|
|
201
247
|
}
|
|
202
248
|
|
|
203
249
|
function handleUpdatePage(value: number) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="w-100">
|
|
3
|
+
<v-card class="w-100">
|
|
4
|
+
<v-card-text>
|
|
5
|
+
<v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
|
|
6
|
+
prepend-icon="mdi-qrcode" />
|
|
7
|
+
<v-combobox v-model="pass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
|
|
8
|
+
@update:focused="handleFocusedPass" :items="passItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id"
|
|
9
|
+
@update:model-value="handleSelectPass" label="Pass" variant="outlined" hide-details
|
|
10
|
+
density="compact" persistent-hint small-chips>
|
|
11
|
+
<template v-slot:no-data>
|
|
12
|
+
<v-list-item density="compact">
|
|
13
|
+
<v-list-item-title v-if="passInput">
|
|
14
|
+
No results matching "<strong>{{ passInput }}</strong>". This value will be
|
|
15
|
+
added as new
|
|
16
|
+
option.
|
|
17
|
+
</v-list-item-title>
|
|
18
|
+
<v-list-item v-else>No data available</v-list-item>
|
|
19
|
+
</v-list-item>
|
|
20
|
+
</template>
|
|
21
|
+
</v-combobox>
|
|
22
|
+
|
|
23
|
+
<v-divider class="my-4 w-100" />
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<template v-if="selectedType">
|
|
27
|
+
<v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact" :rules="countRules" />
|
|
28
|
+
</template>
|
|
29
|
+
</v-card-text>
|
|
30
|
+
</v-card>
|
|
31
|
+
</v-row>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import type { PropType } from 'vue'
|
|
36
|
+
import type { ValidationRule } from 'vuetify/lib/types.mjs'
|
|
37
|
+
import usePassKey from '../composables/usePassKey'
|
|
38
|
+
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
passRules: {
|
|
41
|
+
type: Array as PropType<ValidationRule[]>,
|
|
42
|
+
default: []
|
|
43
|
+
},
|
|
44
|
+
countRules: {
|
|
45
|
+
type: Array as PropType<ValidationRule[]>,
|
|
46
|
+
default: []
|
|
47
|
+
},
|
|
48
|
+
site: {
|
|
49
|
+
type: String,
|
|
50
|
+
required: true
|
|
51
|
+
},
|
|
52
|
+
type: {
|
|
53
|
+
type: String as PropType<TVisitorType>,
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
contractorType: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: ""
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const pass = ref("")
|
|
63
|
+
const passInput = ref('')
|
|
64
|
+
const passItems = ref([])
|
|
65
|
+
const selectedType = ref<'qr-pass' | 'nfc-card'>()
|
|
66
|
+
const count = ref(1)
|
|
67
|
+
|
|
68
|
+
const { getPassKeysByPageSearch } = usePassKey()
|
|
69
|
+
|
|
70
|
+
const typeItems = [
|
|
71
|
+
{
|
|
72
|
+
label: "QR Pass",
|
|
73
|
+
value: "qr-pass"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: "NFC Card",
|
|
77
|
+
value: "nfc-card"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
const passTypesComputed = computed(() => {
|
|
82
|
+
if(props.type === 'contractor'){
|
|
83
|
+
if(props.contractorType === 'property-agent'){
|
|
84
|
+
return ["agent-pass"]
|
|
85
|
+
} else {
|
|
86
|
+
return ["contractor-pass"]
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
return ["visitor-pass"]
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
|
|
94
|
+
return getPassKeysByPageSearch({
|
|
95
|
+
search: passInput.value,
|
|
96
|
+
page: 1,
|
|
97
|
+
limit: 500,
|
|
98
|
+
passTypes: passTypesComputed.value,
|
|
99
|
+
sites: [props.site],
|
|
100
|
+
statuses: ["Available"]
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
watch(passesData, (data: any) => {
|
|
105
|
+
passItems.value = data?.items || []
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
function handleFocusedPass() {
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleSelectPass() {
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//prevent negative value;
|
|
118
|
+
watch(count, (newCount) => {
|
|
119
|
+
if(newCount < 1){
|
|
120
|
+
count.value = 1
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<style scoped></style>
|
|
@@ -377,7 +377,7 @@ const submitTask = async () => {
|
|
|
377
377
|
(props.taskData as any)?._id || (props.taskData as any)?.id;
|
|
378
378
|
response = await updateScheduleTask(taskId, payload);
|
|
379
379
|
} else {
|
|
380
|
-
response = await createScheduleTask({ ...payload, site: props.site });
|
|
380
|
+
response = await createScheduleTask({ ...payload, site: props.site, serviceType: props.serviceType });
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
showMessage(
|
|
@@ -88,8 +88,10 @@
|
|
|
88
88
|
:title="selectedTask?.title || 'Schedule Task Actions'"
|
|
89
89
|
:canUpdate="canUpdateScheduleTask"
|
|
90
90
|
@close="dialogShowMoreActions = false"
|
|
91
|
-
:canDelete="
|
|
91
|
+
:canDelete="canDeleteScheduleTask"
|
|
92
|
+
:deleteButtonLabel="'Delete Schedule Task'"
|
|
92
93
|
@edit="onEditFromMoreAction"
|
|
94
|
+
@delete="onDeleteFromMoreAction"
|
|
93
95
|
:editButtonLabel="'Edit Schedule Task'"
|
|
94
96
|
>
|
|
95
97
|
<template #content>
|
|
@@ -198,6 +200,52 @@
|
|
|
198
200
|
</HygieneUpdateMoreAction>
|
|
199
201
|
</v-dialog>
|
|
200
202
|
|
|
203
|
+
<v-dialog v-model="dialogDeleteTask" max-width="450">
|
|
204
|
+
<v-card>
|
|
205
|
+
<v-toolbar>
|
|
206
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
207
|
+
<span class="font-weight-bold text-h6 text-capitalize">
|
|
208
|
+
Delete {{ selectedTask?.title || 'Schedule Task' }}
|
|
209
|
+
</span>
|
|
210
|
+
</v-row>
|
|
211
|
+
</v-toolbar>
|
|
212
|
+
<v-card-text>
|
|
213
|
+
<span class="text-subtitle-2">Are you sure you want to delete this schedule task?</span>
|
|
214
|
+
</v-card-text>
|
|
215
|
+
|
|
216
|
+
<v-toolbar class="pa-0" density="compact">
|
|
217
|
+
<v-row no-gutters>
|
|
218
|
+
<v-col cols="6" class="pa-0">
|
|
219
|
+
<v-btn
|
|
220
|
+
block
|
|
221
|
+
variant="text"
|
|
222
|
+
class="text-none"
|
|
223
|
+
size="large"
|
|
224
|
+
@click="dialogDeleteTask = false"
|
|
225
|
+
height="48"
|
|
226
|
+
>
|
|
227
|
+
Cancel
|
|
228
|
+
</v-btn>
|
|
229
|
+
</v-col>
|
|
230
|
+
|
|
231
|
+
<v-col cols="6" class="pa-0">
|
|
232
|
+
<v-btn
|
|
233
|
+
block
|
|
234
|
+
variant="flat"
|
|
235
|
+
color="black"
|
|
236
|
+
class="text-none font-weight-bold"
|
|
237
|
+
height="48"
|
|
238
|
+
:loading="submitting"
|
|
239
|
+
@click="_deleteScheduleTask"
|
|
240
|
+
>
|
|
241
|
+
Delete
|
|
242
|
+
</v-btn>
|
|
243
|
+
</v-col>
|
|
244
|
+
</v-row>
|
|
245
|
+
</v-toolbar>
|
|
246
|
+
</v-card>
|
|
247
|
+
</v-dialog>
|
|
248
|
+
|
|
201
249
|
<Snackbar
|
|
202
250
|
v-model="messageSnackbar"
|
|
203
251
|
:text="message"
|
|
@@ -219,7 +267,7 @@ const props = defineProps({
|
|
|
219
267
|
});
|
|
220
268
|
|
|
221
269
|
const { formatDate, debounce } = useUtils();
|
|
222
|
-
const { getScheduleTasks, getScheduleTaskById } = useScheduleTask();
|
|
270
|
+
const { getScheduleTasks, getScheduleTaskById, deleteScheduleTask } = useScheduleTask();
|
|
223
271
|
const {
|
|
224
272
|
canViewScheduleTasks,
|
|
225
273
|
canCreateScheduleTask,
|
|
@@ -349,4 +397,30 @@ const onEditFromMoreAction = async () => {
|
|
|
349
397
|
formMode.value = "edit";
|
|
350
398
|
showForm.value = true;
|
|
351
399
|
};
|
|
400
|
+
|
|
401
|
+
const onDeleteFromMoreAction = () => {
|
|
402
|
+
dialogShowMoreActions.value = false;
|
|
403
|
+
dialogDeleteTask.value = true;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
async function _deleteScheduleTask() {
|
|
407
|
+
if (!selectedTask.value) return;
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
submitting.value = true;
|
|
411
|
+
const id =
|
|
412
|
+
(selectedTask.value as any)._id || (selectedTask.value as any).id;
|
|
413
|
+
if (!id) throw new Error("Invalid schedule task id");
|
|
414
|
+
|
|
415
|
+
const response = await deleteScheduleTask(id);
|
|
416
|
+
dialogDeleteTask.value = false;
|
|
417
|
+
showMessage(response?.message, "success");
|
|
418
|
+
|
|
419
|
+
await getTaskRefresh();
|
|
420
|
+
} catch (error: any) {
|
|
421
|
+
showMessage(error?.data?.message, "error");
|
|
422
|
+
} finally {
|
|
423
|
+
submitting.value = false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
352
426
|
</script>
|
package/components/UnitMain.vue
CHANGED
|
@@ -317,7 +317,7 @@ async function handleDownloadExcel() {
|
|
|
317
317
|
return;
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
const excelBuffer = await downloadUnits(props.site);
|
|
320
|
+
const excelBuffer = await downloadUnits(props.site, props.serviceType);
|
|
321
321
|
|
|
322
322
|
if (!excelBuffer) {
|
|
323
323
|
showMessage("Failed to download units.", "error");
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
</v-col>
|
|
185
185
|
|
|
186
186
|
<v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
|
|
187
|
-
<PassInformation />
|
|
187
|
+
<PassInformation :site="prop.site" :type="prop.type" :contractor-type="visitor.contractorType" />
|
|
188
188
|
<EntryPassInformation
|
|
189
189
|
v-if="entryPassSettings?.data?.settings?.nfcPass"
|
|
190
190
|
v-model="passType"
|
|
@@ -866,7 +866,7 @@ async function submit() {
|
|
|
866
866
|
site: prop.site,
|
|
867
867
|
};
|
|
868
868
|
|
|
869
|
-
if (prop.mode === "add") {
|
|
869
|
+
if (prop.mode === "add" || prop.mode === "register") {
|
|
870
870
|
const allowedFields = typeFieldMap[prop.type];
|
|
871
871
|
for (const key of allowedFields as (keyof TVisitorPayload)[]) {
|
|
872
872
|
if (visitor[key] !== undefined) {
|
|
@@ -876,11 +876,6 @@ async function submit() {
|
|
|
876
876
|
};
|
|
877
877
|
}
|
|
878
878
|
}
|
|
879
|
-
} else if (prop.mode === 'register') {
|
|
880
|
-
payload = {
|
|
881
|
-
...payload,
|
|
882
|
-
status: "registered",
|
|
883
|
-
}
|
|
884
879
|
}
|
|
885
880
|
|
|
886
881
|
|
|
@@ -975,7 +970,7 @@ watch(
|
|
|
975
970
|
);
|
|
976
971
|
|
|
977
972
|
onMounted(() => {
|
|
978
|
-
contractorStep.value =
|
|
973
|
+
contractorStep.value = 2;
|
|
979
974
|
currentAutofillSource.value = null;
|
|
980
975
|
|
|
981
976
|
if (prop.mode === 'register' && prop.visitorData) {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</v-checkbox>
|
|
23
23
|
<InputDateTimePicker v-model:utc="dateFrom" density="compact" hide-details />
|
|
24
24
|
<InputDateTimePicker v-model:utc="dateTo" density="compact" hide-details />
|
|
25
|
-
<v-select v-if="activeTab
|
|
25
|
+
<v-select v-if="activeTab == 'registered'" v-model="filterTypes" label="Filter by types" item-title="label"
|
|
26
26
|
item-value="value" :items="visitorSelection" density="compact" clearable multiple max-width="200"
|
|
27
27
|
hide-details>
|
|
28
28
|
<template v-slot:selection="{ item, index }">
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<v-icon icon="mdi-user" size="15" />
|
|
51
51
|
<span v-if="item.type === 'contractor'" class="text-capitalize">{{
|
|
52
52
|
formatCamelCaseToWords(item.contractorType)
|
|
53
|
-
|
|
53
|
+
}}</span>
|
|
54
54
|
<span v-else class="text-capitalize">{{ formatType(item) }}</span>
|
|
55
55
|
</span>
|
|
56
56
|
<span class="d-flex align-center ga-2">
|
|
@@ -88,9 +88,10 @@
|
|
|
88
88
|
<v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
|
|
89
89
|
<span class="text-capitalize">{{
|
|
90
90
|
UTCToLocalTIme(item.checkIn) || "-"
|
|
91
|
-
|
|
91
|
+
}}</span>
|
|
92
92
|
<span>
|
|
93
|
-
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
|
|
93
|
+
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
|
|
94
|
+
@click.stop="handleViewImage(item.snapshotEntryImage)" />
|
|
94
95
|
</span>
|
|
95
96
|
</span>
|
|
96
97
|
<span class="d-flex align-center ga-2">
|
|
@@ -98,15 +99,16 @@
|
|
|
98
99
|
<template v-if="item.checkOut">
|
|
99
100
|
<span class="text-capitalize">{{
|
|
100
101
|
UTCToLocalTIme(item.checkOut) || "-"
|
|
101
|
-
|
|
102
|
+
}}</span>
|
|
102
103
|
<span>
|
|
103
|
-
<v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
|
|
104
|
+
<v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
|
|
105
|
+
@click.stop="handleViewImage(item.snapshotExitImage)" />
|
|
104
106
|
</span>
|
|
105
107
|
<span v-if="item?.manualCheckout">
|
|
106
108
|
<TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
|
|
107
109
|
</span>
|
|
108
110
|
</template>
|
|
109
|
-
<span v-else-if="!item?.checkOut && item?.status === 'registered'">
|
|
111
|
+
<span v-else-if="!item?.checkOut && (item?.status === 'registered' || item?.status === 'unregistered')">
|
|
110
112
|
<v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
|
|
111
113
|
:loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
|
|
112
114
|
v-if="canCheckoutVisitor" />
|
|
@@ -149,7 +151,7 @@
|
|
|
149
151
|
</span>
|
|
150
152
|
|
|
151
153
|
<span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
|
|
152
|
-
|
|
154
|
+
}}:</strong>
|
|
153
155
|
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
154
156
|
<TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
|
|
155
157
|
</span>
|
|
@@ -165,6 +167,18 @@
|
|
|
165
167
|
@delete="handleProceedDeleteVisitor" />
|
|
166
168
|
</v-dialog>
|
|
167
169
|
|
|
170
|
+
<v-dialog v-model="dialog.snapshotImage" max-width="700">
|
|
171
|
+
<v-card>
|
|
172
|
+
<v-card-title class="d-flex justify-space-between align-center">
|
|
173
|
+
<span>Snapshot</span>
|
|
174
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.snapshotImage = false" />
|
|
175
|
+
</v-card-title>
|
|
176
|
+
<v-card-text class="pa-2 d-flex justify-center">
|
|
177
|
+
<v-img :src="snapshotImageUrl" max-height="600" contain />
|
|
178
|
+
</v-card-text>
|
|
179
|
+
</v-card>
|
|
180
|
+
</v-dialog>
|
|
181
|
+
|
|
168
182
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
169
183
|
</v-row>
|
|
170
184
|
</template>
|
|
@@ -207,7 +221,7 @@ const {
|
|
|
207
221
|
const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
|
|
208
222
|
useUtils();
|
|
209
223
|
const { formatLocation } = useSecurityUtils();
|
|
210
|
-
const {
|
|
224
|
+
const { getFileUrlAnpr } = useFile();
|
|
211
225
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
212
226
|
|
|
213
227
|
const route = useRoute()
|
|
@@ -249,8 +263,11 @@ const dialog = reactive({
|
|
|
249
263
|
showForm: false,
|
|
250
264
|
viewVisitor: false,
|
|
251
265
|
deleteConfirmation: false,
|
|
266
|
+
snapshotImage: false,
|
|
252
267
|
});
|
|
253
268
|
|
|
269
|
+
const snapshotImageUrl = ref("");
|
|
270
|
+
|
|
254
271
|
const headers = computed(() => [
|
|
255
272
|
{ title: "Name", value: "name" },
|
|
256
273
|
{ title: "Type/Company", value: "type-company" },
|
|
@@ -265,6 +282,7 @@ const tabOptions = [
|
|
|
265
282
|
{ name: "Registered", value: "registered" },
|
|
266
283
|
{ name: "Unregistered", value: "unregistered" },
|
|
267
284
|
{ name: "Guests", value: "guests" },
|
|
285
|
+
{ name: "Resident Transactions", value: "resident-transactions" },
|
|
268
286
|
];
|
|
269
287
|
|
|
270
288
|
const selectedVisitorDataObject = computed(() => {
|
|
@@ -333,11 +351,21 @@ const {
|
|
|
333
351
|
checkedOut: displayNotCheckedOut.value ? false : undefined
|
|
334
352
|
}
|
|
335
353
|
|
|
336
|
-
if (activeTab.value !== "guests") {
|
|
337
|
-
|
|
338
|
-
} else if (activeTab.value === "guests") {
|
|
354
|
+
// if (activeTab.value !== "guests") {
|
|
355
|
+
// params.status = activeTab.value
|
|
356
|
+
// } else if (activeTab.value === "guests") {
|
|
357
|
+
// params.type = "guest"
|
|
358
|
+
// params.status = "pending"
|
|
359
|
+
// }
|
|
360
|
+
|
|
361
|
+
if(activeTab.value === "guests"){
|
|
339
362
|
params.type = "guest"
|
|
340
363
|
params.status = "pending"
|
|
364
|
+
} else if (activeTab.value === "resident-transactions") {
|
|
365
|
+
params.status = "registered"
|
|
366
|
+
params.type = "resident,tenant"
|
|
367
|
+
} else {
|
|
368
|
+
params.status = activeTab.value
|
|
341
369
|
}
|
|
342
370
|
|
|
343
371
|
return await getVisitors(params)
|
|
@@ -447,8 +475,8 @@ function handleRegistrationUnregisteredVisitor(item: Partial<TVisitor>) {
|
|
|
447
475
|
|
|
448
476
|
function handleViewImage(imageId: string) {
|
|
449
477
|
const imageEndpoint = `${siteId}/${imageId}`;
|
|
450
|
-
|
|
451
|
-
|
|
478
|
+
snapshotImageUrl.value = getFileUrlAnpr(imageEndpoint);
|
|
479
|
+
dialog.snapshotImage = true;
|
|
452
480
|
}
|
|
453
481
|
|
|
454
482
|
|
package/composables/useAreas.ts
CHANGED
|
@@ -74,22 +74,21 @@ export default function useAreas() {
|
|
|
74
74
|
function uploadAreas(file: File, site: string, serviceType?: string) {
|
|
75
75
|
const formData = new FormData();
|
|
76
76
|
formData.append("file", file);
|
|
77
|
-
if (serviceType) {
|
|
78
|
-
formData.append("serviceType", serviceType);
|
|
79
|
-
}
|
|
80
77
|
return useNuxtApp().$api<Record<string, any>>(
|
|
81
78
|
`/api/hygiene-areas/site/${site}/upload`,
|
|
82
79
|
{
|
|
83
80
|
method: "POST",
|
|
84
81
|
body: formData,
|
|
82
|
+
query: { serviceType },
|
|
85
83
|
},
|
|
86
84
|
);
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
async function exportAreas(site: string) {
|
|
87
|
+
async function exportAreas(site: string, serviceType: string) {
|
|
90
88
|
return useNuxtApp().$api<Blob>(`/api/hygiene-areas/site/${site}/download`, {
|
|
91
89
|
method: "GET",
|
|
92
90
|
responseType: "blob",
|
|
91
|
+
query: { serviceType },
|
|
93
92
|
});
|
|
94
93
|
}
|
|
95
94
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export default function useEquipmentManagement() {
|
|
2
|
-
function getSupplies({
|
|
2
|
+
function getSupplies({
|
|
3
|
+
page = 1,
|
|
4
|
+
search = "",
|
|
5
|
+
limit = 10,
|
|
6
|
+
site = "",
|
|
7
|
+
serviceType = "",
|
|
8
|
+
} = {}) {
|
|
3
9
|
return useNuxtApp().$api<Record<string, any>>(
|
|
4
10
|
`/api/hygiene-supplies/site/${site}`,
|
|
5
11
|
{
|
|
@@ -53,11 +59,26 @@ export default function useEquipmentManagement() {
|
|
|
53
59
|
);
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
function checkOutItemSingle(site: string, id: string, payload: TStockCreate) {
|
|
63
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
64
|
+
`/api/hygiene-checkout-items/site/${site}/supply/${id}`,
|
|
65
|
+
{
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: {
|
|
68
|
+
qty: payload.qty,
|
|
69
|
+
serviceType: payload.serviceType,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
return {
|
|
57
76
|
getSupplies,
|
|
58
77
|
getSupplyById,
|
|
59
78
|
createSupply,
|
|
60
79
|
updateSupply,
|
|
61
80
|
deleteSupply,
|
|
81
|
+
checkOutItemSingle,
|
|
82
|
+
|
|
62
83
|
};
|
|
63
84
|
}
|
|
@@ -37,6 +37,7 @@ export default function useScheduleTask() {
|
|
|
37
37
|
description?: string;
|
|
38
38
|
areas: { name: string; value: string }[];
|
|
39
39
|
site: string;
|
|
40
|
+
serviceType: string;
|
|
40
41
|
}) {
|
|
41
42
|
return useNuxtApp().$api<Record<string, any>>(
|
|
42
43
|
`/api/hygiene-schedule-tasks/site/${payload.site}`,
|
|
@@ -48,6 +49,7 @@ export default function useScheduleTask() {
|
|
|
48
49
|
dates: payload.dates,
|
|
49
50
|
description: payload.description,
|
|
50
51
|
areas: payload.areas,
|
|
52
|
+
serviceType: payload.serviceType,
|
|
51
53
|
},
|
|
52
54
|
},
|
|
53
55
|
);
|
|
@@ -78,10 +80,20 @@ export default function useScheduleTask() {
|
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
function deleteScheduleTask(id: string) {
|
|
84
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
85
|
+
`/api/hygiene-schedule-tasks/id/${id}`,
|
|
86
|
+
{
|
|
87
|
+
method: "DELETE",
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
return {
|
|
82
93
|
getScheduleTasks,
|
|
83
94
|
createScheduleTask,
|
|
84
95
|
updateScheduleTask,
|
|
85
96
|
getScheduleTaskById,
|
|
97
|
+
deleteScheduleTask,
|
|
86
98
|
};
|
|
87
99
|
}
|
package/composables/useStock.ts
CHANGED
|
@@ -27,21 +27,10 @@ export default function useStock() {
|
|
|
27
27
|
);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
return useNuxtApp().$api<Record<string, any>>(
|
|
32
|
-
`/api/hygiene-request-items/site/${site}/supply/${id}`,
|
|
33
|
-
{
|
|
34
|
-
method: "POST",
|
|
35
|
-
body: {
|
|
36
|
-
qty: payload.qty,
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
}
|
|
30
|
+
|
|
41
31
|
|
|
42
32
|
return {
|
|
43
33
|
createStock,
|
|
44
34
|
getStockBySupply,
|
|
45
|
-
requestItem,
|
|
46
35
|
};
|
|
47
36
|
}
|
package/composables/useUnits.ts
CHANGED
|
@@ -57,23 +57,22 @@ export default function useUnits() {
|
|
|
57
57
|
function uploadUnits(file: File, site: string, serviceType?: string) {
|
|
58
58
|
const formData = new FormData();
|
|
59
59
|
formData.append("file", file);
|
|
60
|
-
if (serviceType) {
|
|
61
|
-
formData.append("serviceType", serviceType);
|
|
62
|
-
}
|
|
63
60
|
|
|
64
61
|
return useNuxtApp().$api<Record<string, any>>(
|
|
65
62
|
`/api/hygiene-units/site/${site}/upload`,
|
|
66
63
|
{
|
|
67
64
|
method: "POST",
|
|
68
65
|
body: formData,
|
|
66
|
+
query: { serviceType },
|
|
69
67
|
}
|
|
70
68
|
);
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
function downloadUnits(site: string) {
|
|
71
|
+
function downloadUnits(site: string, serviceType: string) {
|
|
74
72
|
return useNuxtApp().$api<Blob>(`/api/hygiene-units/site/${site}/download`, {
|
|
75
73
|
method: "GET",
|
|
76
74
|
responseType: "blob",
|
|
75
|
+
query: { serviceType },
|
|
77
76
|
});
|
|
78
77
|
}
|
|
79
78
|
|