@7365admin1/layer-common 1.11.17 → 1.11.18
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 +6 -0
- package/components/Card/MemberInfoSummary.vue +5 -1
- package/components/MemberInformation.vue +92 -20
- package/components/PassInformation.vue +75 -30
- package/components/VehicleManagement.vue +107 -34
- package/components/VisitorForm.vue +72 -30
- package/components/VisitorManagement.vue +244 -29
- package/composables/useKey.ts +4 -0
- package/composables/useLocalAuth.ts +2 -4
- package/composables/useTemplateReusable.ts +5 -1
- package/composables/useVehicle.ts +3 -3
- package/package.json +1 -1
- package/types/vehicle.d.ts +2 -1
- package/types/visitor.d.ts +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
</v-col>
|
|
20
20
|
|
|
21
21
|
<v-col cols="12">
|
|
22
|
-
<v-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
<v-autocomplete v-model="memberForm.visitorPass" v-model:search="passInput"
|
|
23
|
+
:hide-no-data="false" class="mt-3" :items="passItemsFilteredFinal"
|
|
24
|
+
item-title="prefixAndName" item-value="_id" label="Pass (optional)" variant="outlined"
|
|
25
25
|
hide-details density="compact" persistent-hint small-chips>
|
|
26
26
|
<template v-slot:no-data>
|
|
27
27
|
<v-list-item density="compact">
|
|
@@ -33,12 +33,19 @@
|
|
|
33
33
|
<v-list-item v-else>No data available</v-list-item>
|
|
34
34
|
</v-list-item>
|
|
35
35
|
</template>
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
<template v-slot:chip="{ props, item }">
|
|
38
|
+
<v-chip v-if="memberForm.visitorPass" v-bind="props"
|
|
39
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
40
|
+
:text="item.raw?.prefixAndName"></v-chip>
|
|
41
|
+
</template>
|
|
42
|
+
</v-autocomplete>
|
|
37
43
|
</v-col>
|
|
38
44
|
|
|
39
45
|
<v-col cols="12">
|
|
40
46
|
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
41
|
-
|
|
47
|
+
<InputPhoneNumberV2 v-model="memberForm.contact" density="compact" default-country="SG"
|
|
48
|
+
hide-details />
|
|
42
49
|
</v-col>
|
|
43
50
|
|
|
44
51
|
<v-row class="pt-3" justify="space-between">
|
|
@@ -56,20 +63,41 @@
|
|
|
56
63
|
</v-card-text>
|
|
57
64
|
</v-card>
|
|
58
65
|
<v-divider class="my-3" />
|
|
59
|
-
<v-row v-if="
|
|
60
|
-
<template v-for="member, index in
|
|
61
|
-
<CardMemberInfoSummary :member="
|
|
66
|
+
<v-row v-if="committedMembers.length > 0" no-gutters class="w-100 mt-5 ga-3">
|
|
67
|
+
<template v-for="member, index in committedMembers" :key="member.nric">
|
|
68
|
+
<CardMemberInfoSummary :member="membersDisplayed[index]" @remove="handleRemoveMember(index)" />
|
|
62
69
|
</template>
|
|
63
70
|
</v-row>
|
|
64
71
|
</v-row>
|
|
65
72
|
</template>
|
|
66
73
|
|
|
67
74
|
<script setup lang="ts">
|
|
75
|
+
import type { PropType } from 'vue'
|
|
76
|
+
import usePassKey from '../composables/usePassKey'
|
|
77
|
+
import useUtils from '../composables/useUtils'
|
|
78
|
+
|
|
68
79
|
|
|
69
80
|
const props = defineProps({
|
|
81
|
+
type: {
|
|
82
|
+
type: String,
|
|
83
|
+
required: true
|
|
84
|
+
},
|
|
85
|
+
contractorType: {
|
|
86
|
+
type: String,
|
|
87
|
+
required: false
|
|
88
|
+
},
|
|
89
|
+
site: {
|
|
90
|
+
type: String,
|
|
91
|
+
required: true
|
|
92
|
+
},
|
|
93
|
+
selectedVisitorPass: {
|
|
94
|
+
type: Array as PropType<TPassKeyPayload[]>,
|
|
95
|
+
default: () => []
|
|
96
|
+
}
|
|
70
97
|
})
|
|
71
98
|
|
|
72
99
|
const { requiredRule } = useUtils()
|
|
100
|
+
const { getPassKeysByPageSearch } = usePassKey()
|
|
73
101
|
|
|
74
102
|
|
|
75
103
|
const memberForm = reactive<TMemberInfo>({
|
|
@@ -85,33 +113,77 @@ const processing = ref(false);
|
|
|
85
113
|
const errorMessage = ref('')
|
|
86
114
|
|
|
87
115
|
// pass
|
|
88
|
-
const pass = ref()
|
|
89
116
|
const passInput = ref('')
|
|
90
|
-
const passItems = ref([
|
|
117
|
+
const passItems = ref<TPassKey[]>([])
|
|
118
|
+
|
|
119
|
+
const members = defineModel<TMemberInfo[]>({ required: true, default: [] })
|
|
120
|
+
const committedMembers = ref<TMemberInfo[]>([])
|
|
121
|
+
|
|
122
|
+
// Always keep the model in sync: committed members + current form draft
|
|
123
|
+
watch(() => memberForm, (newForm) => {
|
|
124
|
+
if (memberForm.name && memberForm.nric && memberForm.contact) {
|
|
125
|
+
members.value = [...committedMembers.value, { ...newForm }]
|
|
126
|
+
} else {
|
|
127
|
+
members.value = [...committedMembers.value]
|
|
128
|
+
}
|
|
129
|
+
}, { deep: true })
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
const passTypesComputed = computed(() => {
|
|
133
|
+
if (props.type === 'contractor') {
|
|
134
|
+
if (props.contractorType === 'property-agent') {
|
|
135
|
+
return ["agent-pass"]
|
|
136
|
+
} else {
|
|
137
|
+
return ["contractor-pass"]
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
return ["visitor-pass"]
|
|
141
|
+
}
|
|
142
|
+
})
|
|
91
143
|
|
|
92
|
-
const
|
|
144
|
+
const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
|
|
145
|
+
return getPassKeysByPageSearch({
|
|
146
|
+
search: passInput.value,
|
|
147
|
+
page: 1,
|
|
148
|
+
limit: 500,
|
|
149
|
+
passTypes: passTypesComputed.value,
|
|
150
|
+
sites: [props.site],
|
|
151
|
+
statuses: ["Available"]
|
|
152
|
+
})
|
|
153
|
+
})
|
|
93
154
|
|
|
155
|
+
watch(passesData, (data: any) => {
|
|
156
|
+
passItems.value = Array.isArray(data?.items) ? data.items : []
|
|
157
|
+
})
|
|
94
158
|
|
|
95
|
-
|
|
159
|
+
const passItemsFilteredFinal = computed(() => {
|
|
160
|
+
return passItems.value.filter((item: TPassKey) => {
|
|
161
|
+
return !props.selectedVisitorPass.some((pass: TPassKeyPayload) => pass.keyId === item._id) && !committedMembers.value.some((member: TMemberInfo) => member.visitorPass === item._id)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const membersDisplayed = computed(() => {
|
|
166
|
+
return committedMembers.value.map((member: TMemberInfo) => {
|
|
167
|
+
return {
|
|
168
|
+
...member,
|
|
169
|
+
visitorPass: passItems.value.find((item: TPassKey) => item._id === member.visitorPass)?.prefixAndName || ""
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
})
|
|
96
173
|
|
|
97
|
-
}
|
|
98
174
|
|
|
99
|
-
function handleSelectPass(val: any) {
|
|
100
|
-
memberForm.visitorPass = val
|
|
101
|
-
}
|
|
102
175
|
|
|
103
176
|
function handleClearForm() {
|
|
104
177
|
formRef.value?.reset()
|
|
105
178
|
}
|
|
106
179
|
|
|
107
180
|
async function handleAddMember() {
|
|
108
|
-
|
|
181
|
+
committedMembers.value.push({ ...memberForm })
|
|
109
182
|
handleClearForm()
|
|
110
183
|
}
|
|
111
184
|
|
|
112
|
-
function handleRemoveMember(index: number){
|
|
113
|
-
|
|
114
|
-
members.value = filtered
|
|
185
|
+
function handleRemoveMember(index: number) {
|
|
186
|
+
committedMembers.value = committedMembers.value.filter((_, i) => i !== index)
|
|
115
187
|
}
|
|
116
188
|
|
|
117
189
|
|
|
@@ -4,27 +4,36 @@
|
|
|
4
4
|
<v-card-text>
|
|
5
5
|
<v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize" disabled
|
|
6
6
|
prepend-icon="mdi-qrcode" />
|
|
7
|
-
<v-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<template v-slot:
|
|
12
|
-
<v-
|
|
13
|
-
|
|
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>
|
|
7
|
+
<v-autocomplete v-model="selectedPass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
|
|
8
|
+
:items="passItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id" label="Pass"
|
|
9
|
+
variant="outlined" hide-details density="compact" persistent-hint small-chips>
|
|
10
|
+
|
|
11
|
+
<template v-slot:chip="{ props, item }">
|
|
12
|
+
<v-chip v-if="selectedPass" v-bind="props" prepend-icon="mdi-card-bulleted-outline"
|
|
13
|
+
:text="item.raw?.prefixAndName"></v-chip>
|
|
20
14
|
</template>
|
|
21
|
-
</v-
|
|
15
|
+
</v-autocomplete>
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
<template v-if="!props.hideKeys">
|
|
19
|
+
<v-autocomplete v-model="selectedKeys" v-model:search="keyInput" :hide-no-data="false" class="mt-3"
|
|
20
|
+
:items="keyItems" :rules="props.passRules" item-title="prefixAndName" item-value="_id"
|
|
21
|
+
label="Keys" multiple variant="outlined" hide-details density="compact" persistent-hint
|
|
22
|
+
small-chips>
|
|
23
|
+
|
|
24
|
+
<template v-slot:chip="{ props, item }">
|
|
25
|
+
<v-chip v-if="selectedKeys.length > 0" v-bind="props" prepend-icon="mdi-key"
|
|
26
|
+
:text="item.raw?.prefixAndName"></v-chip>
|
|
27
|
+
</template>
|
|
28
|
+
</v-autocomplete>
|
|
29
|
+
</template>
|
|
22
30
|
|
|
23
31
|
<v-divider class="my-4 w-100" />
|
|
24
|
-
|
|
32
|
+
|
|
25
33
|
|
|
26
34
|
<template v-if="selectedType">
|
|
27
|
-
<v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact"
|
|
35
|
+
<v-number-input v-model="count" variant="outlined" :min="1" :precision="0" density="compact"
|
|
36
|
+
:rules="countRules" />
|
|
28
37
|
</template>
|
|
29
38
|
</v-card-text>
|
|
30
39
|
</v-card>
|
|
@@ -35,6 +44,7 @@
|
|
|
35
44
|
import type { PropType } from 'vue'
|
|
36
45
|
import type { ValidationRule } from 'vuetify/lib/types.mjs'
|
|
37
46
|
import usePassKey from '../composables/usePassKey'
|
|
47
|
+
import useKey from '../composables/useKey'
|
|
38
48
|
|
|
39
49
|
const props = defineProps({
|
|
40
50
|
passRules: {
|
|
@@ -56,12 +66,21 @@ const props = defineProps({
|
|
|
56
66
|
contractorType: {
|
|
57
67
|
type: String,
|
|
58
68
|
default: ""
|
|
69
|
+
},
|
|
70
|
+
hideKeys: {
|
|
71
|
+
type: Boolean,
|
|
72
|
+
default: false
|
|
59
73
|
}
|
|
60
74
|
})
|
|
61
75
|
|
|
62
|
-
const pass =
|
|
76
|
+
const pass = defineModel<TPassKeyPayload[]>("pass", { default: [] })
|
|
77
|
+
const keys = defineModel<TPassKeyPayload[]>("keys", { default: [] })
|
|
78
|
+
const selectedPass = ref<string>('')
|
|
79
|
+
const selectedKeys = ref<string[]>([])
|
|
63
80
|
const passInput = ref('')
|
|
64
|
-
const
|
|
81
|
+
const keyInput = ref('')
|
|
82
|
+
const passItems = ref<TPassKey[]>([])
|
|
83
|
+
const keyItems = ref<any[]>([])
|
|
65
84
|
const selectedType = ref<'qr-pass' | 'nfc-card'>()
|
|
66
85
|
const count = ref(1)
|
|
67
86
|
|
|
@@ -79,15 +98,15 @@ const typeItems = [
|
|
|
79
98
|
]
|
|
80
99
|
|
|
81
100
|
const passTypesComputed = computed(() => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
if (props.type === 'contractor') {
|
|
102
|
+
if (props.contractorType === 'property-agent') {
|
|
103
|
+
return ["agent-pass"]
|
|
104
|
+
} else {
|
|
105
|
+
return ["contractor-pass"]
|
|
106
|
+
}
|
|
85
107
|
} else {
|
|
86
|
-
return ["
|
|
108
|
+
return ["visitor-pass"]
|
|
87
109
|
}
|
|
88
|
-
} else {
|
|
89
|
-
return ["visitor-pass"]
|
|
90
|
-
}
|
|
91
110
|
})
|
|
92
111
|
|
|
93
112
|
const { data: passesData, refresh: refreshPassesData, pending: fetchPassesPending } = await useLazyAsyncData('get-pass-keys', () => {
|
|
@@ -105,22 +124,48 @@ watch(passesData, (data: any) => {
|
|
|
105
124
|
passItems.value = data?.items || []
|
|
106
125
|
})
|
|
107
126
|
|
|
127
|
+
const { data: keysData, refresh: refreshKeysData, pending: fetchKeysPending } = await useLazyAsyncData('get-keys', () => {
|
|
128
|
+
return getPassKeysByPageSearch({
|
|
129
|
+
search: keyInput.value,
|
|
130
|
+
statuses: ["Available"],
|
|
131
|
+
passTypes: ['pass-key'],
|
|
132
|
+
page: 1,
|
|
133
|
+
limit: 500,
|
|
134
|
+
sites: [props.site],
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
watch(keysData, (data: any) => {
|
|
139
|
+
keyItems.value = data?.items || []
|
|
140
|
+
})
|
|
108
141
|
|
|
109
|
-
|
|
142
|
+
watch(selectedPass, (newVal) => {
|
|
143
|
+
pass.value = [{ keyId: newVal }]
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
watch(selectedKeys, (newVal) => {
|
|
147
|
+
keys.value = newVal.map(key => ({ keyId: key }))
|
|
148
|
+
})
|
|
110
149
|
|
|
111
|
-
}
|
|
112
150
|
|
|
113
|
-
function handleSelectPass() {
|
|
114
151
|
|
|
115
|
-
}
|
|
116
152
|
|
|
117
153
|
//prevent negative value;
|
|
118
154
|
watch(count, (newCount) => {
|
|
119
|
-
if(newCount < 1){
|
|
155
|
+
if (newCount < 1) {
|
|
120
156
|
count.value = 1
|
|
121
157
|
}
|
|
122
158
|
})
|
|
123
159
|
|
|
160
|
+
onMounted(() => {
|
|
161
|
+
if (pass.value.length > 0) {
|
|
162
|
+
selectedPass.value = pass.value[0].keyId
|
|
163
|
+
}
|
|
164
|
+
if (keys.value.length > 0) {
|
|
165
|
+
selectedKeys.value = keys.value.map(k => k.keyId)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
124
169
|
</script>
|
|
125
170
|
|
|
126
171
|
<style scoped></style>
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<v-row no-gutters class="px-5 py-1 d-flex align-center justify-end ga-3">
|
|
10
10
|
<v-text-field v-model="searchInput" density="compact" placeholder="Search" clearable max-width="300"
|
|
11
11
|
append-inner-icon="mdi-magnify" hide-details />
|
|
12
|
-
<v-select v-model="vehicleTypeFilter" density="compact" item-title="label" item-value="value"
|
|
13
|
-
placeholder="Filter by Type" clearable max-width="200" hide-details :items="typeOptions" />
|
|
12
|
+
<!-- <v-select v-model="vehicleTypeFilter" density="compact" item-title="label" item-value="value"
|
|
13
|
+
placeholder="Filter by Type" clearable max-width="200" hide-details :items="typeOptions" /> -->
|
|
14
14
|
</v-row>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
</v-dialog>
|
|
38
38
|
|
|
39
39
|
<v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
|
|
40
|
-
<VehicleForm :type="vehicleType" mode="add" :vehicle-data="selectedVehicleObject" @back="handleBackToSelection"
|
|
41
|
-
:org="org" :site="props.site" @close:all="handleCloseAll" />
|
|
40
|
+
<VehicleForm :type="vehicleType" mode="add" :vehicle-data="selectedVehicleObject" @back="handleBackToSelection"
|
|
41
|
+
@done="handleAddVehicleComplete" :org="org" :site="props.site" @close:all="handleCloseAll" />
|
|
42
42
|
</v-dialog>
|
|
43
43
|
|
|
44
44
|
<!-- <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
|
|
@@ -65,10 +65,23 @@
|
|
|
65
65
|
{{ formatVehicleStatus(value).label }}
|
|
66
66
|
</v-chip>
|
|
67
67
|
</template>
|
|
68
|
-
<template #item.action="{ item }">
|
|
69
|
-
<v-btn v-if="canDeleteVehicle && (
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
<template #item.action="{ item: plateNumberItem, value }">
|
|
69
|
+
<v-btn v-if="canDeleteVehicle && (plateNumberItem as TPlateNumber)?.status == 'active'"
|
|
70
|
+
text="Delete" color="error" flat size="x-small"
|
|
71
|
+
@click="handleDeleteVehicleAction(plateNumberItem as TPlateNumber)" />
|
|
72
|
+
<v-btn
|
|
73
|
+
v-if="props.app === 'property_management_agency' && (plateNumberItem as TPlateNumber)?.status == 'pending'"
|
|
74
|
+
text="Approve" color="success" flat size="x-small"
|
|
75
|
+
@click="handleApproveVehicle(plateNumberItem as TPlateNumber)" />
|
|
76
|
+
<v-btn v-if="(plateNumberItem as TPlateNumber)?.status == 'deleted'" text="Restore"
|
|
77
|
+
color="orange" flat size="x-small"
|
|
78
|
+
@click="handleRestoreVehicle(plateNumberItem as TPlateNumber)" />
|
|
79
|
+
<v-btn
|
|
80
|
+
v-if="((plateNumberItem as TPlateNumber)?.type === 'whitelist' || (plateNumberItem as TPlateNumber)?.type === 'blocklist') && plateNumberItem?.status == 'active'"
|
|
81
|
+
variant="outlined"
|
|
82
|
+
:text="(plateNumberItem as TPlateNumber)?.type === 'blocklist' ? 'Whitelist' : 'Blocklist'"
|
|
83
|
+
:color="(plateNumberItem as TPlateNumber)?.type === 'blocklist' ? 'primary' : 'red'" flat
|
|
84
|
+
size="x-small" class="ml-1" @click="handleUpdateType(plateNumberItem as TPlateNumber)" />
|
|
72
85
|
</template>
|
|
73
86
|
</v-data-table>
|
|
74
87
|
<v-btn text="Add Vehicle Number" class="mt-6 text-capitalize" prepend-icon="mdi-plus"
|
|
@@ -81,7 +94,7 @@
|
|
|
81
94
|
<v-col v-else-if="selectedVehicleObject[key]" cols="12">
|
|
82
95
|
<span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ formatValues(key,
|
|
83
96
|
selectedVehicleObject[key])
|
|
84
|
-
|
|
97
|
+
}}</span>
|
|
85
98
|
</v-col>
|
|
86
99
|
</template>
|
|
87
100
|
</v-row>
|
|
@@ -89,16 +102,24 @@
|
|
|
89
102
|
</DialogUpdateMoreAction>
|
|
90
103
|
</v-dialog>
|
|
91
104
|
<v-dialog v-model="dialog.deleteVehicle" persistent width="540">
|
|
92
|
-
<DialogDeleteConfirmation :loading="deletingVehicle"
|
|
105
|
+
<DialogDeleteConfirmation :loading="deletingVehicle"
|
|
106
|
+
:prompt-title="`Are you sure want to delete this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
93
107
|
@delete="submitDelete" @close="closeDeleteDialog" />
|
|
94
108
|
</v-dialog>
|
|
95
109
|
<v-dialog v-model="dialog.approveVehicle" persistent width="540">
|
|
96
|
-
<DialogReusablePrompt :loading="approvingVehicle"
|
|
97
|
-
|
|
110
|
+
<DialogReusablePrompt :loading="approvingVehicle"
|
|
111
|
+
:prompt-title="`Are you sure want to approve this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
112
|
+
@proceed="submitApprove" @close="dialog.approveVehicle = false" />
|
|
98
113
|
</v-dialog>
|
|
99
114
|
<v-dialog v-model="dialog.restoreVehicle" persistent width="540">
|
|
100
|
-
<DialogReusablePrompt :loading="restoringVehicle"
|
|
101
|
-
|
|
115
|
+
<DialogReusablePrompt :loading="restoringVehicle"
|
|
116
|
+
:prompt-title="`Are you sure want to restore this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
117
|
+
@proceed="submitRestore" @close="dialog.restoreVehicle = false" />
|
|
118
|
+
</v-dialog>
|
|
119
|
+
<v-dialog v-model="dialog.updateVehicleType" persistent width="540">
|
|
120
|
+
<DialogReusablePrompt :loading="updatingType"
|
|
121
|
+
:prompt-title="`Are you sure want to update the type of this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
122
|
+
@proceed="submitUpdateType" @close="dialog.updateVehicleType = false" />
|
|
102
123
|
</v-dialog>
|
|
103
124
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
104
125
|
</v-row>
|
|
@@ -147,7 +168,7 @@ const plateHeaders = [
|
|
|
147
168
|
|
|
148
169
|
|
|
149
170
|
const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
|
|
150
|
-
const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle } = useVehicle();
|
|
171
|
+
const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, updateVehicle } = useVehicle();
|
|
151
172
|
|
|
152
173
|
const items = ref<Array<Record<string, any>>>([]);
|
|
153
174
|
const page = ref(1);
|
|
@@ -160,6 +181,7 @@ const loading = ref(false);
|
|
|
160
181
|
const deletingVehicle = ref(false);
|
|
161
182
|
const approvingVehicle = ref(false);
|
|
162
183
|
const restoringVehicle = ref(false);
|
|
184
|
+
const updatingType = ref(false);
|
|
163
185
|
|
|
164
186
|
const selectedVehicleId = ref<string | null>(null)
|
|
165
187
|
const vehicleType = ref<TVehicleType | null>(null);
|
|
@@ -185,7 +207,8 @@ const dialog = reactive({
|
|
|
185
207
|
deleteVehicle: false,
|
|
186
208
|
showMoreActions: false,
|
|
187
209
|
approveVehicle: false,
|
|
188
|
-
restoreVehicle: false
|
|
210
|
+
restoreVehicle: false,
|
|
211
|
+
updateVehicleType: false
|
|
189
212
|
});
|
|
190
213
|
|
|
191
214
|
|
|
@@ -280,19 +303,24 @@ function handleEditVehicleAction() {
|
|
|
280
303
|
}
|
|
281
304
|
|
|
282
305
|
function handleDeleteVehicleAction(item: TPlateNumber) {
|
|
283
|
-
selectedPlateNumberObject.value = item || null;
|
|
306
|
+
selectedPlateNumberObject.value = item || null;
|
|
284
307
|
dialog.deleteVehicle = true;
|
|
285
308
|
}
|
|
286
309
|
|
|
287
310
|
function handleApproveVehicle(item: TPlateNumber) {
|
|
288
|
-
selectedPlateNumberObject.value = item || null;
|
|
311
|
+
selectedPlateNumberObject.value = item || null;
|
|
289
312
|
dialog.approveVehicle = true;
|
|
290
313
|
}
|
|
291
314
|
function handleRestoreVehicle(item: TPlateNumber) {
|
|
292
|
-
selectedPlateNumberObject.value = item || null;
|
|
315
|
+
selectedPlateNumberObject.value = item || null;
|
|
293
316
|
dialog.restoreVehicle = true;
|
|
294
317
|
}
|
|
295
318
|
|
|
319
|
+
function handleUpdateType(item: TPlateNumber) {
|
|
320
|
+
selectedPlateNumberObject.value = item || null;
|
|
321
|
+
dialog.updateVehicleType = true;
|
|
322
|
+
}
|
|
323
|
+
|
|
296
324
|
function handleSelectVehicleStatus(value: TVehicleType) {
|
|
297
325
|
vehicleType.value = value;
|
|
298
326
|
dialog.showSelection = false;
|
|
@@ -341,6 +369,7 @@ async function submitDelete() {
|
|
|
341
369
|
|
|
342
370
|
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
343
371
|
const type = selectedPlateNumberObject.value?.type as TVehicleType;
|
|
372
|
+
const recNo = selectedPlateNumberObject.value?.recNo;
|
|
344
373
|
if (!plateNumberId) {
|
|
345
374
|
showMessage("Invalid plate number selected for deletion.", "error");
|
|
346
375
|
return;
|
|
@@ -348,12 +377,11 @@ async function submitDelete() {
|
|
|
348
377
|
|
|
349
378
|
try {
|
|
350
379
|
deletingVehicle.value = true;
|
|
351
|
-
const res = await deleteVehicle({ site: props.site, id: plateNumberId as string,
|
|
380
|
+
const res = await deleteVehicle({ site: props.site, recno: recNo, id: plateNumberId as string, type }
|
|
352
381
|
);
|
|
353
382
|
dialog.deleteVehicle = false;
|
|
354
|
-
selectedVehicleId.value = null
|
|
355
383
|
showMessage(res.message, "success");
|
|
356
|
-
getVehiclesRefresh();
|
|
384
|
+
await getVehiclesRefresh();
|
|
357
385
|
} catch (error: any) {
|
|
358
386
|
console.error("Error deleting vehicle:", error);
|
|
359
387
|
message.value = error.response._data.message;
|
|
@@ -366,24 +394,21 @@ async function submitDelete() {
|
|
|
366
394
|
|
|
367
395
|
async function submitRestore() {
|
|
368
396
|
|
|
369
|
-
|
|
370
|
-
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
371
|
-
|
|
397
|
+
const vehicleId = selectedPlateNumberObject.value?._id;
|
|
372
398
|
|
|
373
|
-
if (!
|
|
374
|
-
showMessage("Invalid
|
|
399
|
+
if (!vehicleId) {
|
|
400
|
+
showMessage("Invalid vehicle Id selected for restoration.", "error");
|
|
375
401
|
return;
|
|
376
402
|
}
|
|
377
403
|
|
|
378
404
|
try {
|
|
379
|
-
restoringVehicle.value = true;
|
|
405
|
+
restoringVehicle.value = true;
|
|
380
406
|
// reactivate and restore will use the same endpoint, just with different payload
|
|
381
|
-
const res = await approveVehicle({ site: props.site, org: props.org, id:
|
|
407
|
+
const res = await approveVehicle({ site: props.site, org: props.org, id: vehicleId as string }
|
|
382
408
|
);
|
|
383
409
|
dialog.restoreVehicle = false;
|
|
384
|
-
selectedVehicleId.value = null
|
|
385
410
|
showMessage(res.message, "success");
|
|
386
|
-
getVehiclesRefresh();
|
|
411
|
+
await getVehiclesRefresh();
|
|
387
412
|
} catch (error: any) {
|
|
388
413
|
console.error("Error restoring vehicle:", error);
|
|
389
414
|
const errMessage = error?.response?._data?.message || "Failed to restore vehicle";
|
|
@@ -395,6 +420,55 @@ async function submitRestore() {
|
|
|
395
420
|
}
|
|
396
421
|
|
|
397
422
|
|
|
423
|
+
async function submitUpdateType() {
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
const vehicleId = selectedPlateNumberObject.value?._id;
|
|
427
|
+
const type = selectedPlateNumberObject.value?.type as TVehicleType;
|
|
428
|
+
const recNo = selectedPlateNumberObject.value?.recNo;
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
if (!type) {
|
|
432
|
+
showMessage("Invalid vehicle type.", "error");
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// if(!recNo){
|
|
437
|
+
// showMessage("Invalid vehicle record number.", "error");
|
|
438
|
+
// return;
|
|
439
|
+
// }
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
const payload = {
|
|
443
|
+
site: props.site,
|
|
444
|
+
plateNumber: selectedPlateNumberObject.value?.plateNumber as string,
|
|
445
|
+
type: type === "whitelist" ? "blocklist" : "whitelist" as TVehicleType,
|
|
446
|
+
// recno: recNo
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
if (!vehicleId) {
|
|
451
|
+
showMessage("Invalid vehicle Id selected for updating type.", "error");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
updatingType.value = true;
|
|
457
|
+
const res = await updateVehicle(vehicleId as string, payload);
|
|
458
|
+
dialog.updateVehicleType = false;
|
|
459
|
+
showMessage(res.message, "success");
|
|
460
|
+
await getVehiclesRefresh();
|
|
461
|
+
} catch (error: any) {
|
|
462
|
+
console.error("Error updating vehicle type:", error);
|
|
463
|
+
const errMessage = error?.response?._data?.message || "Failed to update vehicle type";
|
|
464
|
+
showMessage(errMessage, "error");
|
|
465
|
+
// message.value = error.response._data.message;
|
|
466
|
+
} finally {
|
|
467
|
+
updatingType.value = false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
|
|
398
472
|
async function submitApprove() {
|
|
399
473
|
|
|
400
474
|
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
@@ -404,14 +478,13 @@ async function submitApprove() {
|
|
|
404
478
|
}
|
|
405
479
|
|
|
406
480
|
try {
|
|
407
|
-
approvingVehicle.value = true;
|
|
481
|
+
approvingVehicle.value = true;
|
|
408
482
|
const res = await approveVehicle({ site: props.site, org: props.org, id: plateNumberId as string }
|
|
409
483
|
|
|
410
484
|
);
|
|
411
485
|
dialog.approveVehicle = false;
|
|
412
|
-
selectedVehicleId.value = null
|
|
413
486
|
showMessage(res.message, "success");
|
|
414
|
-
getVehiclesRefresh();
|
|
487
|
+
await getVehiclesRefresh();
|
|
415
488
|
} catch (error: any) {
|
|
416
489
|
console.error("Error approving vehicle:", error);
|
|
417
490
|
const errMessage = error?.response?._data?.message || "Failed to approve vehicle";
|