@7365admin1/layer-common 1.11.4 → 1.11.6
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/OvernightParkingAvailability.vue +9 -4
- package/components/OvernightParkingManagement.vue +156 -46
- package/composables/useOvernightParking.ts +78 -0
- package/package.json +1 -1
- package/types/customer.d.ts +13 -2
- package/types/overnight-parking.d.ts +17 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.11.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c63a3a7: Update Package Version
|
|
8
|
+
|
|
9
|
+
## 1.11.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 2f23422: Update Version
|
|
14
|
+
- 80c27e9: Update Package Version 03/25/2026
|
|
15
|
+
|
|
3
16
|
## 1.11.4
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -107,6 +107,7 @@ const model = ref<TOvernightParkingAvailability>({
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
const autoApproveOvernightParking = ref(false)
|
|
110
|
+
const isHydrating = ref(true)
|
|
110
111
|
|
|
111
112
|
const { requiredRule } = useUtils()
|
|
112
113
|
const { generateTimeSlots, generateTimeSlotsFromStart } = useFacilityUtils()
|
|
@@ -151,6 +152,9 @@ watch(availabilityDataReq, (data) => {
|
|
|
151
152
|
slot.isEnabled = data?.[day]?.isEnabled ?? false
|
|
152
153
|
slot.startTime = data?.[day]?.startTime ?? null
|
|
153
154
|
slot.endTime = data?.[day]?.endTime ?? null
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
isHydrating.value = false
|
|
157
|
+
}, 500)
|
|
154
158
|
})
|
|
155
159
|
|
|
156
160
|
autoApproveOvernightParking.value = data?.autoApproveOvernightParking === true
|
|
@@ -221,12 +225,12 @@ const isValid = computed(() => {
|
|
|
221
225
|
})
|
|
222
226
|
|
|
223
227
|
watch(autoApproveOvernightParking, (newValue, oldValue) => {
|
|
224
|
-
if (oldValue === newValue) return
|
|
228
|
+
if (oldValue === newValue || isHydrating.value) return
|
|
225
229
|
handleSave('toggle')
|
|
226
|
-
})
|
|
230
|
+
}, { immediate: false })
|
|
227
231
|
|
|
228
232
|
|
|
229
|
-
function handleSave(action: 'toggle' | 'save') {
|
|
233
|
+
async function handleSave(action: 'toggle' | 'save') {
|
|
230
234
|
if (!isValid.value && action === 'save') {
|
|
231
235
|
message.value = 'Please fill in all required fields.'
|
|
232
236
|
messageColor.value = 'error'
|
|
@@ -248,10 +252,11 @@ function handleSave(action: 'toggle' | 'save') {
|
|
|
248
252
|
|
|
249
253
|
try {
|
|
250
254
|
loading.updating = true
|
|
251
|
-
updateOvernightParkingAvailability(props.site, payload)
|
|
255
|
+
await updateOvernightParkingAvailability(props.site, payload)
|
|
252
256
|
message.value = 'Overnight parking settings updated successfully.'
|
|
253
257
|
messageColor.value = 'success'
|
|
254
258
|
messageSnackbar.value = true
|
|
259
|
+
refreshAvailability()
|
|
255
260
|
} catch (error) {
|
|
256
261
|
message.value = 'Failed to update overnight parking settings. Please try again.'
|
|
257
262
|
messageColor.value = 'error'
|
|
@@ -1,58 +1,98 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
<TableMain
|
|
4
|
-
:
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
v-model:search="searchInput"
|
|
15
|
-
@refresh="emits('refresh')"
|
|
16
|
-
@create="emits('create')"
|
|
17
|
-
@row-click="handleRowClick"
|
|
18
|
-
@update:page="handleUpdatePage"
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
<template #item.dateRequested="{ value }">
|
|
22
|
-
{{ formatDateDDMMYYYYLocal(value) }}
|
|
3
|
+
<TableMain :headers="headers" :items="items" :loading="loading" :page="page" :pages="pages" :pageRange="pageRange"
|
|
4
|
+
:createLabel="createLabel" :show-header="true" :extension-height="extensionHeight" :offset="offset"
|
|
5
|
+
v-model:search="searchInput" @refresh="emits('refresh')" @create="emits('create')" @row-click="handleRowClick"
|
|
6
|
+
@update:page="handleUpdatePage">
|
|
7
|
+
|
|
8
|
+
<template #extension>
|
|
9
|
+
<v-row no-gutters class="w-100 d-flex align-center justify-end ga-4 pa-2">
|
|
10
|
+
<v-select v-model="statusFilter" label="Filter by types" item-title="label" item-value="value"
|
|
11
|
+
:items="filterOptions" density="compact" clearable max-width="200" hide-details>
|
|
12
|
+
</v-select>
|
|
13
|
+
</v-row>
|
|
23
14
|
</template>
|
|
24
15
|
|
|
25
|
-
<template
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
>
|
|
32
|
-
{{ formatStatus(value).label }}
|
|
33
|
-
</v-chip>
|
|
16
|
+
<template v-slot:item.name="{ item }">
|
|
17
|
+
<span class="d-flex align-center ga-2">
|
|
18
|
+
<span>
|
|
19
|
+
<AvatarMain :name="item?.name" :size="20" :id="item?._id" />
|
|
20
|
+
</span>
|
|
21
|
+
<span class="text-capitalize">{{ item?.name }}</span>
|
|
22
|
+
</span>
|
|
34
23
|
</template>
|
|
35
24
|
|
|
36
|
-
<template
|
|
37
|
-
|
|
25
|
+
<template v-slot:item.emailContact="{ item }">
|
|
26
|
+
<span class="d-flex align-center ga-2">
|
|
27
|
+
<v-icon icon="mdi-email" size="15" />
|
|
28
|
+
<span class="text-capitalize">{{ item?.email || "N/A" }}</span>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="d-flex align-center ga-2">
|
|
31
|
+
<v-icon icon="mdi-phone" size="15" />
|
|
32
|
+
<span class="text-capitalize">{{ item?.contact || "N/A" }}</span>
|
|
33
|
+
</span>
|
|
38
34
|
</template>
|
|
39
35
|
|
|
40
|
-
<template #item.
|
|
41
|
-
{{
|
|
36
|
+
<template #item.createdAt="{ value }">
|
|
37
|
+
{{ formatDateDDMMYYYYLocal(value) }}
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template #item.status="{ value }">
|
|
41
|
+
<v-chip class="text-capitalize" :color="formatStatus(value).color" density="compact" variant="flat" pill>
|
|
42
|
+
{{ formatStatus(value).label }}
|
|
43
|
+
</v-chip>
|
|
42
44
|
</template>
|
|
43
45
|
|
|
44
46
|
<template #item.action="{ item }">
|
|
45
|
-
<v-
|
|
47
|
+
<v-menu v-if="item?.status === 'pending'" activator="parent" transition="scale-transition">
|
|
48
|
+
<template #activator="{ props }">
|
|
49
|
+
<v-btn variant="flat" density="compact" icon="mdi-dots-vertical" v-bind="props"></v-btn>
|
|
50
|
+
</template>
|
|
51
|
+
<v-list>
|
|
52
|
+
<v-list-item @click="openActionDialog(item, 'approved')">
|
|
53
|
+
<v-list-item-title>
|
|
54
|
+
<v-icon class="text-success mr-2 text-subtitle-1">mdi-check</v-icon>Approve</v-list-item-title>
|
|
55
|
+
</v-list-item>
|
|
56
|
+
<v-list-item @click="openActionDialog(item, 'rejected')">
|
|
57
|
+
<v-list-item-title>
|
|
58
|
+
<v-icon class="text-error mr-2 text-subtitle-1">mdi-close</v-icon>Reject</v-list-item-title>
|
|
59
|
+
</v-list-item>
|
|
60
|
+
</v-list>
|
|
61
|
+
</v-menu>
|
|
46
62
|
</template>
|
|
47
63
|
|
|
48
64
|
<template v-if="$slots.footer" #footer>
|
|
49
65
|
<slot name="footer" />
|
|
50
66
|
</template>
|
|
51
67
|
</TableMain>
|
|
68
|
+
|
|
69
|
+
<v-dialog v-model="actionDialog.open" max-width="520" persistent>
|
|
70
|
+
<v-card>
|
|
71
|
+
<v-card-title class="text-subtitle-1 font-weight-bold pt-4 px-4">
|
|
72
|
+
{{ actionDialog.action === 'approved' ? 'Approve Request' : 'Reject Request' }}
|
|
73
|
+
</v-card-title>
|
|
74
|
+
<v-card-text class="px-4 pb-2">
|
|
75
|
+
<p class="text-caption text-medium-emphasis mb-3">
|
|
76
|
+
Add remarks before {{ actionDialog.action === 'approved' ? 'approving' : 'rejecting' }} this request.
|
|
77
|
+
</p>
|
|
78
|
+
<v-textarea v-model="remarks" label="Remarks" rows="3" auto-grow :rules="[requiredRule]"
|
|
79
|
+
:disabled="actionLoading" />
|
|
80
|
+
</v-card-text>
|
|
81
|
+
<v-card-actions class="px-4 pb-4">
|
|
82
|
+
<v-spacer />
|
|
83
|
+
<v-btn variant="text" :disabled="actionLoading" @click="closeActionDialog">Cancel</v-btn>
|
|
84
|
+
<v-btn color="primary" variant="flat" :loading="actionLoading" :disabled="!remarks.trim()"
|
|
85
|
+
@click="confirmAction">
|
|
86
|
+
Confirm
|
|
87
|
+
</v-btn>
|
|
88
|
+
</v-card-actions>
|
|
89
|
+
</v-card>
|
|
90
|
+
</v-dialog>
|
|
52
91
|
</v-row>
|
|
53
92
|
</template>
|
|
54
93
|
|
|
55
94
|
<script setup lang="ts">
|
|
95
|
+
import useOvernightParking from "../composables/useOvernightParking";
|
|
56
96
|
import useUtils from "../composables/useUtils";
|
|
57
97
|
|
|
58
98
|
const props = defineProps({
|
|
@@ -73,7 +113,8 @@ const emits = defineEmits(["refresh", "create", "row-click", "update:page"]);
|
|
|
73
113
|
|
|
74
114
|
const searchInput = defineModel<string>("search", { default: "" });
|
|
75
115
|
|
|
76
|
-
const { standardFormatDate, formatDateDDMMYYYYLocal } = useUtils();
|
|
116
|
+
const { requiredRule, standardFormatDate, formatDateDDMMYYYYLocal } = useUtils();
|
|
117
|
+
const { getOvernightParkingRequests, updateOvernightParkingRequest } = useOvernightParking();
|
|
77
118
|
|
|
78
119
|
const items = ref<TOvernightParkingRequest[]>([])
|
|
79
120
|
const page = ref(1)
|
|
@@ -83,15 +124,37 @@ const extensionHeight = ref(0)
|
|
|
83
124
|
const offset = ref(0)
|
|
84
125
|
const createLabel = "New Overnight Parking Request"
|
|
85
126
|
|
|
127
|
+
const remarks = ref('')
|
|
128
|
+
const actionLoading = ref(false)
|
|
129
|
+
const actionDialog = reactive<{
|
|
130
|
+
open: boolean
|
|
131
|
+
requestId: string
|
|
132
|
+
action: 'approved' | 'rejected'
|
|
133
|
+
}>({
|
|
134
|
+
open: false,
|
|
135
|
+
requestId: '',
|
|
136
|
+
action: 'approved',
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const statusFilter = ref<TOvernightParkingRequestStatus>("pending")
|
|
140
|
+
|
|
141
|
+
const filterOptions: { label: string; value: TOvernightParkingRequestStatus }[] = [
|
|
142
|
+
{ label: "Pending", value: "pending" },
|
|
143
|
+
{ label: "Approved", value: "approved" },
|
|
144
|
+
{ label: "Rejected", value: "rejected" },
|
|
145
|
+
{ label: "Expired", value: "expired" },
|
|
146
|
+
]
|
|
147
|
+
|
|
86
148
|
const headers = [
|
|
87
149
|
{ title: "Name", value: "name" },
|
|
88
|
-
{ title: "
|
|
89
|
-
{ title: "Date Requested", value: "
|
|
150
|
+
{ title: "Email/Contact", value: "emailContact" },
|
|
151
|
+
{ title: "Date Requested", value: "createdAt" },
|
|
90
152
|
{ title: "Invited By", value: "invitedBy" },
|
|
91
153
|
{ title: "Status", value: "status" },
|
|
92
154
|
{ title: "Action", value: "action" },
|
|
93
155
|
];
|
|
94
156
|
|
|
157
|
+
|
|
95
158
|
function formatStatus(status: TOvernightParkingRequest["status"]) {
|
|
96
159
|
switch (String(status || "").toLowerCase()) {
|
|
97
160
|
case "approved":
|
|
@@ -107,18 +170,31 @@ function formatStatus(status: TOvernightParkingRequest["status"]) {
|
|
|
107
170
|
}
|
|
108
171
|
}
|
|
109
172
|
|
|
173
|
+
const { data: overnightParkingRequests, pending: isPendingOvernightParkingRequests, refresh: refreshOvernightParkingRequests } = await useLazyAsyncData<TOvernightParkingRequest[]>(() => getOvernightParkingRequests({ site: props.site, status: statusFilter.value }), { immediate: true })
|
|
110
174
|
|
|
111
|
-
function formatTimeValue(value: TOvernightParkingRequest["start"] | TOvernightParkingRequest["end"]) {
|
|
112
|
-
if (!value) return "N/A";
|
|
113
175
|
|
|
114
|
-
|
|
115
|
-
|
|
176
|
+
watch(overnightParkingRequests, (data: any) => {
|
|
177
|
+
// items.value = Array.isArray(data?.items)
|
|
178
|
+
// ? data.items
|
|
179
|
+
// : Array.isArray(data)
|
|
180
|
+
// ? data
|
|
181
|
+
// : []
|
|
182
|
+
pages.value = data?.pages ?? 0;
|
|
183
|
+
pageRange.value = data?.pageRange ?? "-- - -- of --"
|
|
184
|
+
}, { immediate: true })
|
|
116
185
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
186
|
+
|
|
187
|
+
// function formatTimeValue(value: TOvernightParkingRequest["start"] | TOvernightParkingRequest["end"]) {
|
|
188
|
+
// if (!value) return "N/A";
|
|
189
|
+
|
|
190
|
+
// const date = new Date(value);
|
|
191
|
+
// if (Number.isNaN(date.getTime())) return String(value);
|
|
192
|
+
|
|
193
|
+
// return date.toLocaleTimeString("en-US", {
|
|
194
|
+
// hour: "2-digit",
|
|
195
|
+
// minute: "2-digit",
|
|
196
|
+
// });
|
|
197
|
+
// }
|
|
122
198
|
|
|
123
199
|
function handleRowClick(data: { item?: TOvernightParkingRequest }) {
|
|
124
200
|
emits("row-click", data);
|
|
@@ -127,4 +203,38 @@ function handleRowClick(data: { item?: TOvernightParkingRequest }) {
|
|
|
127
203
|
function handleUpdatePage(value: number) {
|
|
128
204
|
emits("update:page", value);
|
|
129
205
|
}
|
|
206
|
+
|
|
207
|
+
function openActionDialog(item: TOvernightParkingRequest, action: 'approved' | 'rejected') {
|
|
208
|
+
actionDialog.requestId = item?._id || ''
|
|
209
|
+
actionDialog.action = action
|
|
210
|
+
remarks.value = ''
|
|
211
|
+
actionDialog.open = true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function closeActionDialog() {
|
|
215
|
+
if (actionLoading.value) return
|
|
216
|
+
actionDialog.open = false
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function confirmAction() {
|
|
220
|
+
if (!actionDialog.requestId || !remarks.value.trim()) return
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
actionLoading.value = true
|
|
224
|
+
await updateOvernightParkingRequest(actionDialog.requestId, {
|
|
225
|
+
status: actionDialog.action,
|
|
226
|
+
remarks: remarks.value.trim(),
|
|
227
|
+
})
|
|
228
|
+
actionDialog.open = false
|
|
229
|
+
await refreshOvernightParkingRequests()
|
|
230
|
+
refreshOvernightParkingRequests()
|
|
231
|
+
} finally {
|
|
232
|
+
actionLoading.value = false
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
watch([statusFilter], ([newStatus], [oldStatus]) => {
|
|
237
|
+
if (newStatus === oldStatus) return
|
|
238
|
+
refreshOvernightParkingRequests()
|
|
239
|
+
}, { immediate: false })
|
|
130
240
|
</script>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
|
|
3
|
+
type GetOvernightParkingRequestsParams = {
|
|
4
|
+
page?: number
|
|
5
|
+
limit?: number
|
|
6
|
+
order?: "asc" | "desc"
|
|
7
|
+
site?: string
|
|
8
|
+
status?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getOvernightParkingRequests({
|
|
12
|
+
page = 1,
|
|
13
|
+
limit = 10,
|
|
14
|
+
order = "desc",
|
|
15
|
+
site = "",
|
|
16
|
+
status = "",
|
|
17
|
+
}: GetOvernightParkingRequestsParams = {}) {
|
|
18
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
19
|
+
"/api/overnight-parking-requests",
|
|
20
|
+
{
|
|
21
|
+
method: "GET",
|
|
22
|
+
query: {
|
|
23
|
+
page,
|
|
24
|
+
limit,
|
|
25
|
+
order,
|
|
26
|
+
site,
|
|
27
|
+
status,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getOvernightParkingRequestById(_id: string) {
|
|
34
|
+
return await useNuxtApp().$api<TOvernightParkingRequest>(
|
|
35
|
+
`/api/overnight-parking-requests/id/${_id}`,
|
|
36
|
+
{
|
|
37
|
+
method: "GET",
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function createOvernightParkingRequest(payload: Partial<TOvernightParkingRequest>) {
|
|
43
|
+
return await useNuxtApp().$api<TOvernightParkingRequest>(
|
|
44
|
+
"/api/overnight-parking-requests",
|
|
45
|
+
{
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: payload,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function updateOvernightParkingRequest(_id: string, payload: Partial<TOvernightParkingRequest>) {
|
|
53
|
+
return await useNuxtApp().$api<TOvernightParkingRequest>(
|
|
54
|
+
`/api/overnight-parking-requests/id/${_id}`,
|
|
55
|
+
{
|
|
56
|
+
method: "PUT",
|
|
57
|
+
body: payload,
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function deleteOvernightParkingRequest(_id: string) {
|
|
63
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
64
|
+
`/api/overnight-parking-requests/id/${_id}`,
|
|
65
|
+
{
|
|
66
|
+
method: "DELETE",
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
getOvernightParkingRequests,
|
|
73
|
+
getOvernightParkingRequestById,
|
|
74
|
+
createOvernightParkingRequest,
|
|
75
|
+
updateOvernightParkingRequest,
|
|
76
|
+
deleteOvernightParkingRequest,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/package.json
CHANGED
package/types/customer.d.ts
CHANGED
|
@@ -17,11 +17,22 @@ declare type TCustomerCreate = Pick<
|
|
|
17
17
|
declare type TCustomerSite = {
|
|
18
18
|
name: string;
|
|
19
19
|
site?: string;
|
|
20
|
-
siteOrg
|
|
21
|
-
siteOrgName
|
|
20
|
+
siteOrg?: string;
|
|
21
|
+
siteOrgName?: string;
|
|
22
22
|
org: string;
|
|
23
23
|
status?: string;
|
|
24
24
|
createdAt?: string;
|
|
25
25
|
updatedAt?: string;
|
|
26
26
|
deletedAt?: string;
|
|
27
|
+
category?: TSiteCategory | string;
|
|
28
|
+
address: {
|
|
29
|
+
line1: string;
|
|
30
|
+
line2: string;
|
|
31
|
+
city: string;
|
|
32
|
+
state: string;
|
|
33
|
+
country: string;
|
|
34
|
+
postalCode: string;
|
|
35
|
+
}
|
|
27
36
|
};
|
|
37
|
+
|
|
38
|
+
declare type TSiteCategory = "residential" | "commercial" | "industrial" | "institutional" | "mixed_development" | "infrastructure" | "hospitality"
|
|
@@ -23,13 +23,21 @@ declare type TOvernightParkingRequestStatus =
|
|
|
23
23
|
| (string & {});
|
|
24
24
|
|
|
25
25
|
declare type TOvernightParkingRequest = {
|
|
26
|
-
_id?:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
_id?: ObjectId;
|
|
27
|
+
site: string | ObjectId;
|
|
28
|
+
name: string;
|
|
29
|
+
contact: string;
|
|
30
|
+
email: string;
|
|
31
|
+
isOvernightParking?: boolean;
|
|
32
|
+
numberOfPassengers?: number;
|
|
33
|
+
purposeOfVisit?: string;
|
|
34
|
+
status?: OvernightParkingRequestStatus;
|
|
35
|
+
invitedBy: {
|
|
36
|
+
_id: string | ObjectId;
|
|
37
|
+
name: string;
|
|
38
|
+
}
|
|
39
|
+
createdAt?: string | Date;
|
|
40
|
+
updatedAt?: string | Date;
|
|
41
|
+
deletedAt?: string | Date;
|
|
42
|
+
remarks?: string;
|
|
35
43
|
};
|