@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 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
- :headers="headers"
5
- :items="items"
6
- :loading="loading"
7
- :page="page"
8
- :pages="pages"
9
- :pageRange="pageRange"
10
- :createLabel="createLabel"
11
- :show-header="true"
12
- :extension-height="extensionHeight"
13
- :offset="offset"
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 #item.status="{ value }">
26
- <v-chip
27
- class="text-capitalize"
28
- :color="formatStatus(value).color"
29
- variant="flat"
30
- pill
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 #item.start="{ value }">
37
- {{ formatTimeValue(value) }}
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.end="{ value }">
41
- {{ formatTimeValue(value) }}
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-btn v-if="item.status === 'pending'" color="success" density="compact">Approve</v-btn>
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: "Plate Number", value: "plateNumber" },
89
- { title: "Date Requested", value: "dateTime" },
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
- const date = new Date(value);
115
- if (Number.isNaN(date.getTime())) return String(value);
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
- return date.toLocaleTimeString("en-US", {
118
- hour: "2-digit",
119
- minute: "2-digit",
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
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.11.4",
5
+ "version": "1.11.6",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -17,11 +17,22 @@ declare type TCustomerCreate = Pick<
17
17
  declare type TCustomerSite = {
18
18
  name: string;
19
19
  site?: string;
20
- siteOrg: string;
21
- siteOrgName: string;
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?: string;
27
- name?: string | null;
28
- plateNumber?: string | null;
29
- dateRequested?: string | Date | null;
30
- status?: TOvernightParkingRequestStatus | null;
31
- start?: string | Date | null;
32
- end?: string | Date | null;
33
- remarks?: string | null;
34
- [key: string]: unknown;
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
  };