@7365admin1/layer-common 1.11.19 → 1.11.20
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/AreaChecklistHistoryMain.vue +29 -1
- package/components/CleaningScheduleMain.vue +2 -2
- package/components/Input/DateTimePicker.vue +45 -9
- package/components/OvernightParkingAvailability.vue +19 -18
- package/components/ScheduleAreaMain.vue +6 -6
- package/components/VisitorManagement.vue +25 -7
- package/composables/useFeedback.ts +1 -1
- package/composables/useSiteSettings.ts +30 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,22 @@
|
|
|
14
14
|
@refresh="getAreaChecklistHistoryRefresh"
|
|
15
15
|
@row-click="handleRowClick"
|
|
16
16
|
>
|
|
17
|
+
<template #actions>
|
|
18
|
+
<v-row class="w-100" align="center" no-gutters>
|
|
19
|
+
<v-col cols="auto">
|
|
20
|
+
<v-btn
|
|
21
|
+
variant="text"
|
|
22
|
+
color="primary"
|
|
23
|
+
class="text-none"
|
|
24
|
+
@click="back"
|
|
25
|
+
>
|
|
26
|
+
<v-icon left>mdi-arrow-left</v-icon>
|
|
27
|
+
Back
|
|
28
|
+
</v-btn>
|
|
29
|
+
</v-col>
|
|
30
|
+
</v-row>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
17
33
|
<template #extension>
|
|
18
34
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
19
35
|
<v-card
|
|
@@ -84,7 +100,7 @@ const props = defineProps({
|
|
|
84
100
|
scheduleRoute: { type: String, default: "cleaning-schedule" },
|
|
85
101
|
});
|
|
86
102
|
|
|
87
|
-
const { formatDate } = useUtils();
|
|
103
|
+
const { formatDate, back, debounce } = useUtils();
|
|
88
104
|
|
|
89
105
|
const loading = ref(false);
|
|
90
106
|
const message = ref("");
|
|
@@ -160,6 +176,18 @@ watchEffect(() => {
|
|
|
160
176
|
}
|
|
161
177
|
});
|
|
162
178
|
|
|
179
|
+
const debouncedSearchRefresh = debounce(() => {
|
|
180
|
+
const wasPage = page.value;
|
|
181
|
+
page.value = 1;
|
|
182
|
+
if (wasPage === 1) {
|
|
183
|
+
getAreaChecklistHistoryRefresh();
|
|
184
|
+
}
|
|
185
|
+
}, 450);
|
|
186
|
+
|
|
187
|
+
watch(searchInput, () => {
|
|
188
|
+
debouncedSearchRefresh();
|
|
189
|
+
});
|
|
190
|
+
|
|
163
191
|
function handleRowClick(data: any) {
|
|
164
192
|
const item = data?.item ?? data;
|
|
165
193
|
const id = item?._id || item?.id || item?.areaId;
|
|
@@ -116,7 +116,7 @@ const props = defineProps({
|
|
|
116
116
|
|
|
117
117
|
const startDate = ref("");
|
|
118
118
|
const endDate = ref("");
|
|
119
|
-
const status = ref<TScheduleAreaStatus>("
|
|
119
|
+
const status = ref<TScheduleAreaStatus>("all");
|
|
120
120
|
const statusOptions = [
|
|
121
121
|
{ title: "All", value: "all" },
|
|
122
122
|
{ title: "Open", value: "open" },
|
|
@@ -191,7 +191,7 @@ const {
|
|
|
191
191
|
site: props.site,
|
|
192
192
|
startDate: startDate.value,
|
|
193
193
|
endDate: endDate.value,
|
|
194
|
-
status: status.value === "
|
|
194
|
+
status: status.value === "all" ? undefined : status.value,
|
|
195
195
|
serviceType: props.serviceType,
|
|
196
196
|
}),
|
|
197
197
|
{
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="d-flex flex-column">
|
|
3
3
|
<v-text-field v-bind="$attrs" ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly" autocomplete="off"
|
|
4
|
-
:placeholder="
|
|
4
|
+
:placeholder="inputPlaceholder" :rules="rules" style="z-index: 10" @click="openDatePicker">
|
|
5
5
|
<template #append-inner>
|
|
6
6
|
<v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
|
|
7
7
|
</template>
|
|
8
8
|
</v-text-field>
|
|
9
9
|
<div class="w-100 d-flex align-end ga-3 hidden-input">
|
|
10
|
-
<input ref="dateInput" type="
|
|
10
|
+
<input ref="dateInput" :type="inputType" v-model="dateTime" />
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
@@ -23,6 +23,10 @@ const prop = defineProps({
|
|
|
23
23
|
placeholder: {
|
|
24
24
|
type: String,
|
|
25
25
|
default: 'DD/MM/YYYY, HH:MM AM/PM'
|
|
26
|
+
},
|
|
27
|
+
dateOnly: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
26
30
|
}
|
|
27
31
|
})
|
|
28
32
|
|
|
@@ -31,6 +35,10 @@ const dateTime = defineModel<string | null>({ default: null }) //2025-10-10T13:0
|
|
|
31
35
|
const dateTimeUTC = defineModel<string | null>('utc', { default: null }) // UTC format
|
|
32
36
|
|
|
33
37
|
const dateTimeFormattedReadOnly = ref<string | null>(null)
|
|
38
|
+
const inputType = computed(() => (prop.dateOnly ? 'date' : 'datetime-local'))
|
|
39
|
+
const inputPlaceholder = computed(() => (
|
|
40
|
+
prop.placeholder || (prop.dateOnly ? 'MM/DD/YYYY' : 'DD/MM/YYYY, HH:MM AM/PM')
|
|
41
|
+
))
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
|
|
@@ -55,11 +63,16 @@ function convertToReadableFormat(dateStr: string): string {
|
|
|
55
63
|
if (!dateStr) return "";
|
|
56
64
|
|
|
57
65
|
const date = new Date(dateStr)
|
|
66
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
58
67
|
|
|
59
|
-
const day = String(date.getDate()).padStart(2, '0')
|
|
60
68
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
69
|
+
const day = String(date.getDate()).padStart(2, '0')
|
|
61
70
|
const year = date.getFullYear()
|
|
62
71
|
|
|
72
|
+
if (prop.dateOnly) {
|
|
73
|
+
return `${month}/${day}/${year}`
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
let hours = date.getHours()
|
|
64
77
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
65
78
|
|
|
@@ -72,17 +85,36 @@ function convertToReadableFormat(dateStr: string): string {
|
|
|
72
85
|
return `${day}/${month}/${year}, ${formattedTime}`
|
|
73
86
|
}
|
|
74
87
|
|
|
88
|
+
function toDateOnlyInputValue(dateStr: string): string {
|
|
89
|
+
if (!dateStr) return ''
|
|
90
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr
|
|
91
|
+
const date = new Date(dateStr)
|
|
92
|
+
if (Number.isNaN(date.getTime())) return ''
|
|
93
|
+
const year = date.getFullYear()
|
|
94
|
+
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
95
|
+
const day = String(date.getDate()).padStart(2, '0')
|
|
96
|
+
return `${year}-${month}-${day}`
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
function handleInitialDate(){
|
|
76
100
|
const dateDefault = dateTime.value
|
|
77
101
|
const dateUTC = dateTimeUTC.value
|
|
78
102
|
if(dateDefault){
|
|
79
103
|
dateTimeFormattedReadOnly.value = convertToReadableFormat(dateDefault)
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
if (prop.dateOnly) {
|
|
105
|
+
dateTimeUTC.value = toDateOnlyInputValue(dateDefault)
|
|
106
|
+
} else {
|
|
107
|
+
const localDate = new Date(dateDefault)
|
|
108
|
+
dateTimeUTC.value = localDate.toISOString()
|
|
109
|
+
}
|
|
82
110
|
} else if (dateUTC){
|
|
83
111
|
dateTimeFormattedReadOnly.value = convertToReadableFormat(dateUTC)
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
if (prop.dateOnly) {
|
|
113
|
+
dateTime.value = toDateOnlyInputValue(dateUTC)
|
|
114
|
+
} else {
|
|
115
|
+
const localDate = new Date(dateUTC)
|
|
116
|
+
dateTime.value = formatDateISO8601(localDate)
|
|
117
|
+
}
|
|
86
118
|
} else {
|
|
87
119
|
dateTimeFormattedReadOnly.value = null
|
|
88
120
|
}
|
|
@@ -98,8 +130,12 @@ watch(dateTime, (dateVal) => {
|
|
|
98
130
|
}
|
|
99
131
|
|
|
100
132
|
dateTimeFormattedReadOnly.value = convertToReadableFormat(dateVal)
|
|
101
|
-
|
|
102
|
-
|
|
133
|
+
if (prop.dateOnly) {
|
|
134
|
+
dateTimeUTC.value = toDateOnlyInputValue(dateVal)
|
|
135
|
+
} else {
|
|
136
|
+
const localDate = new Date(dateVal)
|
|
137
|
+
dateTimeUTC.value = localDate.toISOString()
|
|
138
|
+
}
|
|
103
139
|
|
|
104
140
|
}, { immediate: false })
|
|
105
141
|
|
|
@@ -111,7 +111,8 @@ const isHydrating = ref(true)
|
|
|
111
111
|
|
|
112
112
|
const { requiredRule } = useUtils()
|
|
113
113
|
const { generateTimeSlots, generateTimeSlotsFromStart } = useFacilityUtils()
|
|
114
|
-
const {
|
|
114
|
+
const { currentUser } = useLocalAuth()
|
|
115
|
+
const { getOvernightParkingAvailabilityV2, createOrUpdateOvernightParkingAvailabilityV2 } = useSiteSettings()
|
|
115
116
|
|
|
116
117
|
type TDay = Exclude<keyof TOvernightParkingAvailability, 'autoApproveOvernightParking'>
|
|
117
118
|
type TOvernightParkingSlot = TOvernightParkingDay & { day: TDay }
|
|
@@ -139,9 +140,11 @@ const applyDialog = reactive({
|
|
|
139
140
|
|
|
140
141
|
|
|
141
142
|
|
|
142
|
-
const
|
|
143
|
+
const userType = computed(() => currentUser.value?.type ?? "site")
|
|
144
|
+
const updatedByValue = computed(() => currentUser.value?._id ?? currentUser.value?.email ?? "system")
|
|
145
|
+
const { data: availabilityDataReq, refresh: refreshAvailability } = await useLazyAsyncData<Record<string, any>>(
|
|
143
146
|
`overnight-parking-availability-${props.site}`,
|
|
144
|
-
() =>
|
|
147
|
+
() => getOvernightParkingAvailabilityV2(props.site, userType.value)
|
|
145
148
|
)
|
|
146
149
|
|
|
147
150
|
watch(availabilityDataReq, (data) => {
|
|
@@ -152,15 +155,16 @@ watch(availabilityDataReq, (data) => {
|
|
|
152
155
|
slot.isEnabled = data?.[day]?.isEnabled ?? false
|
|
153
156
|
slot.startTime = data?.[day]?.startTime ?? null
|
|
154
157
|
slot.endTime = data?.[day]?.endTime ?? null
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
isHydrating.value = false
|
|
157
|
-
}, 500)
|
|
158
158
|
})
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
autoApproveOvernightParking.value =
|
|
160
|
+
data?.autoApproveOvernightParking === true || data?.isAutoApproved === true
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
isHydrating.value = false
|
|
163
|
+
}, 500)
|
|
161
164
|
}, { immediate: true })
|
|
162
165
|
|
|
163
166
|
|
|
167
|
+
|
|
164
168
|
const overnightParkingSlotsArray = ref<TOvernightParkingSlot[]>(
|
|
165
169
|
orderedDays.map((day) => ({
|
|
166
170
|
day,
|
|
@@ -240,23 +244,20 @@ async function handleSave(action: 'toggle' | 'save') {
|
|
|
240
244
|
|
|
241
245
|
let payload = {}
|
|
242
246
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
payload = {
|
|
249
|
-
autoApproveOvernightParking: autoApproveOvernightParking.value,
|
|
250
|
-
}
|
|
247
|
+
payload = {
|
|
248
|
+
...model.value,
|
|
249
|
+
autoApproveOvernightParking: autoApproveOvernightParking.value,
|
|
250
|
+
isAutoApproved: autoApproveOvernightParking.value,
|
|
251
|
+
updatedBy: updatedByValue.value,
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
try {
|
|
254
255
|
loading.updating = true
|
|
255
|
-
await
|
|
256
|
+
await createOrUpdateOvernightParkingAvailabilityV2(props.site, payload, userType.value)
|
|
257
|
+
refreshAvailability()
|
|
256
258
|
message.value = 'Overnight parking settings updated successfully.'
|
|
257
259
|
messageColor.value = 'success'
|
|
258
260
|
messageSnackbar.value = true
|
|
259
|
-
refreshAvailability()
|
|
260
261
|
} catch (error) {
|
|
261
262
|
message.value = 'Failed to update overnight parking settings. Please try again.'
|
|
262
263
|
messageColor.value = 'error'
|
|
@@ -208,12 +208,12 @@ const isScheduleClosed = computed(
|
|
|
208
208
|
|
|
209
209
|
const items = ref<Array<Record<string, any>>>([]);
|
|
210
210
|
|
|
211
|
-
const statusFilter = ref<TScheduleAreaStatus>("
|
|
211
|
+
const statusFilter = ref<TScheduleAreaStatus>("all");
|
|
212
212
|
const statusOptions = [
|
|
213
|
-
{ title: "All", value: "
|
|
214
|
-
{ title: "Open", value: "
|
|
215
|
-
{ title: "Ongoing", value: "
|
|
216
|
-
{ title: "Completed", value: "
|
|
213
|
+
{ title: "All", value: "all" },
|
|
214
|
+
{ title: "Open", value: "open" },
|
|
215
|
+
{ title: "Ongoing", value: "ongoing" },
|
|
216
|
+
{ title: "Completed", value: "completed" },
|
|
217
217
|
];
|
|
218
218
|
const areaTypeFilter = ref<TAreaType>("all");
|
|
219
219
|
const typeOptions = [
|
|
@@ -235,7 +235,7 @@ const {
|
|
|
235
235
|
getScheduleAreas({
|
|
236
236
|
page: page.value,
|
|
237
237
|
scheduleAreaId: props.scheduleAreaId,
|
|
238
|
-
status: statusFilter.value === "
|
|
238
|
+
status: statusFilter.value === "all" ? undefined : statusFilter.value,
|
|
239
239
|
type: areaTypeFilter.value === "all" ? undefined : areaTypeFilter.value,
|
|
240
240
|
serviceType: props.serviceType,
|
|
241
241
|
}),
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
<span class="text-caption">Not Checked Out</span>
|
|
21
21
|
</template>
|
|
22
22
|
</v-checkbox>
|
|
23
|
-
<
|
|
24
|
-
<
|
|
23
|
+
<InputDatePicker v-model="dateFrom" density="compact" hide-details />
|
|
24
|
+
<InputDatePicker v-model="dateTo" density="compact" hide-details />
|
|
25
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>
|
|
@@ -418,6 +418,19 @@ const tabOptions = [
|
|
|
418
418
|
{ name: "Resident Transactions", value: "resident-transactions" },
|
|
419
419
|
];
|
|
420
420
|
|
|
421
|
+
function normalizeDateOnly(value: string) {
|
|
422
|
+
if (!value) return "";
|
|
423
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
424
|
+
|
|
425
|
+
const parsedDate = new Date(value);
|
|
426
|
+
if (Number.isNaN(parsedDate.getTime())) return "";
|
|
427
|
+
|
|
428
|
+
const year = parsedDate.getFullYear();
|
|
429
|
+
const month = String(parsedDate.getMonth() + 1).padStart(2, "0");
|
|
430
|
+
const day = String(parsedDate.getDate()).padStart(2, "0");
|
|
431
|
+
return `${year}-${month}-${day}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
421
434
|
const selectedVisitorDataObject = computed(() => {
|
|
422
435
|
return items.value.find((x: any) => x?._id === selectedVisitorId.value) || {};
|
|
423
436
|
});
|
|
@@ -486,8 +499,8 @@ const {
|
|
|
486
499
|
page: page.value,
|
|
487
500
|
site: siteId,
|
|
488
501
|
search: searchInput.value,
|
|
489
|
-
dateTo: dateTo.value,
|
|
490
|
-
dateFrom: dateFrom.value,
|
|
502
|
+
dateTo: normalizeDateOnly(dateTo.value),
|
|
503
|
+
dateFrom: normalizeDateOnly(dateFrom.value),
|
|
491
504
|
type: (filterTypes.value.length === 0 && activeTab.value === 'registered' ? "contractor,delivery,walk-in,pick-up,drop-off,guest" : filterTypes.value.filter(Boolean).join(",")),
|
|
492
505
|
checkedOut: displayNotCheckedOut.value ? false : undefined
|
|
493
506
|
}
|
|
@@ -805,8 +818,13 @@ const updateRouteQuery = debounce(
|
|
|
805
818
|
watch(
|
|
806
819
|
[searchInput, dateFrom, dateTo, filterTypes, activeTab, displayNotCheckedOut],
|
|
807
820
|
([search, from, to, types, tab, checkedOut]) => {
|
|
821
|
+
const normalizedFrom = normalizeDateOnly(from);
|
|
822
|
+
const normalizedTo = normalizeDateOnly(to);
|
|
823
|
+
dateFrom.value = normalizedFrom;
|
|
824
|
+
dateTo.value = normalizedTo;
|
|
825
|
+
|
|
808
826
|
// updateRouteQuery(search, from, to, types, status, checkedOut)
|
|
809
|
-
updateRouteQuery({ search, from, to, types, tab, checkedOut })
|
|
827
|
+
updateRouteQuery({ search, from: normalizedFrom, to: normalizedTo, types, tab, checkedOut })
|
|
810
828
|
getVisitorRefresh();
|
|
811
829
|
},
|
|
812
830
|
{ deep: true }
|
|
@@ -817,8 +835,8 @@ watch(
|
|
|
817
835
|
onMounted(() => {
|
|
818
836
|
activeTab.value = (route.query.tab as string) || "unregistered"
|
|
819
837
|
searchInput.value = (route.query.search as string) || "";
|
|
820
|
-
dateFrom.value = (route.query.dateFrom as string) || "";
|
|
821
|
-
dateTo.value = (route.query.dateTo as string) || "";
|
|
838
|
+
dateFrom.value = normalizeDateOnly((route.query.dateFrom as string) || "");
|
|
839
|
+
dateTo.value = normalizeDateOnly((route.query.dateTo as string) || "");
|
|
822
840
|
filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(Boolean) as TVisitorType[];
|
|
823
841
|
displayNotCheckedOut.value = (route.query.checkedOut as string) == 'true' || false
|
|
824
842
|
})
|
|
@@ -140,6 +140,33 @@ export default function () {
|
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
async function getOvernightParkingAvailabilityV2(
|
|
144
|
+
siteId: string,
|
|
145
|
+
userType = "site"
|
|
146
|
+
) {
|
|
147
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
148
|
+
`/api/overnight-parking-approval-settings2/v1`,
|
|
149
|
+
{
|
|
150
|
+
method: "PATCH",
|
|
151
|
+
body: JSON.stringify({ site: siteId, userType }),
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function createOrUpdateOvernightParkingAvailabilityV2(
|
|
157
|
+
siteId: string,
|
|
158
|
+
payload: Record<string, any>,
|
|
159
|
+
userType = "site"
|
|
160
|
+
) {
|
|
161
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
162
|
+
`/api/overnight-parking-approval-settings2/v1`,
|
|
163
|
+
{
|
|
164
|
+
method: "POST",
|
|
165
|
+
body: JSON.stringify({ site: siteId, userType, ...payload }),
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
143
170
|
return {
|
|
144
171
|
getSiteById,
|
|
145
172
|
getSiteLevels,
|
|
@@ -153,6 +180,8 @@ export default function () {
|
|
|
153
180
|
updateSitebyId,
|
|
154
181
|
updateSiteInformation,
|
|
155
182
|
getOvernightParkingAvailability,
|
|
156
|
-
updateOvernightParkingAvailability
|
|
183
|
+
updateOvernightParkingAvailability,
|
|
184
|
+
getOvernightParkingAvailabilityV2,
|
|
185
|
+
createOrUpdateOvernightParkingAvailabilityV2,
|
|
157
186
|
};
|
|
158
187
|
}
|