@7365admin1/layer-common 3.2.2-staging.138 → 3.2.2-staging.139

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.
@@ -42,6 +42,19 @@
42
42
  {{ formatDateDDMMYYYYLocal(value) }}
43
43
  </template>
44
44
 
45
+ <!--
46
+ The column had no slot, so an OBJECT `invitedBy` printed its raw JSON
47
+ into the cell - `{ "_id": "u-1", "name": "Unit A-12-01" }`. Never seen,
48
+ because the table had never drawn a row. `types/overnight-parking.d.ts`
49
+ declares `{ _id, name }`; every OTHER collection in `core` stores
50
+ `invitedBy` as a plain id/name string, and the overnight parking model
51
+ has no such field at all - so both shapes are read rather than betting
52
+ on one, and an absent value says N/A instead of nothing.
53
+ -->
54
+ <template #item.invitedBy="{ value }">
55
+ {{ (typeof value === "object" ? value?.name : value) || "N/A" }}
56
+ </template>
57
+
45
58
  <!--
46
59
  The same four words, the same four meanings - approved/expired/
47
60
  rejected/pending map to ok/neutral/err/warn in `utils/status.ts`,
@@ -114,6 +127,15 @@
114
127
  </v-card-actions>
115
128
  </v-card>
116
129
  </v-dialog>
130
+
131
+ <!--
132
+ Approve/Reject had NO failure path at all: `confirmAction` was a
133
+ try/finally with no catch, so a 400/403/network failure left the dialog
134
+ open, the spinner off and not one word on screen - the guard cannot tell
135
+ a saved approval from a refused one. The shared `Snackbar` is what every
136
+ other screen in the layer uses to say so.
137
+ -->
138
+ <Snackbar v-model="messageSnackbar" :text="snackbarMessage" :color="messageColor" />
117
139
  </v-row>
118
140
  </template>
119
141
 
@@ -225,7 +247,10 @@ function formatStatus(status: TOvernightParkingRequest["status"]) {
225
247
  }
226
248
  }
227
249
 
228
- const { data: overnightParkingRequests, pending: isPendingOvernightParkingRequests, refresh: refreshOvernightParkingRequests } = await useLazyAsyncData<TOvernightParkingRequest[]>(() => getOvernightParkingRequests({ site: props.site, status: statusFilter.value }), { immediate: true })
250
+ // `page` was read by the pager and never sent to the API, so page 2 re-fetched
251
+ // page 1 - with the API's `limit` of 10, every request past the tenth was
252
+ // unreachable. It is part of the query now.
253
+ const { data: overnightParkingRequests, pending: isPendingOvernightParkingRequests, refresh: refreshOvernightParkingRequests } = await useLazyAsyncData<TOvernightParkingRequest[]>(() => getOvernightParkingRequests({ site: props.site, status: statusFilter.value, page: page.value }), { immediate: true })
229
254
 
230
255
 
231
256
  watch(overnightParkingRequests, (data: any) => {
@@ -257,6 +282,8 @@ function handleRowClick(data: { item?: TOvernightParkingRequest }) {
257
282
  }
258
283
 
259
284
  function handleUpdatePage(value: number) {
285
+ page.value = value;
286
+ refreshOvernightParkingRequests();
260
287
  emits("update:page", value);
261
288
  }
262
289
 
@@ -272,9 +299,21 @@ function closeActionDialog() {
272
299
  actionDialog.open = false
273
300
  }
274
301
 
302
+ const snackbarMessage = ref("")
303
+ const messageColor = ref("")
304
+ const messageSnackbar = ref(false)
305
+
306
+ function showMessage(msg: string, color: string) {
307
+ snackbarMessage.value = msg
308
+ messageColor.value = color
309
+ messageSnackbar.value = true
310
+ }
311
+
275
312
  async function confirmAction() {
276
313
  if (!actionDialog.requestId || !remarks.value.trim()) return
277
314
 
315
+ const verb = actionDialog.action === 'approved' ? 'approve' : 'reject'
316
+
278
317
  try {
279
318
  actionLoading.value = true
280
319
  await updateOvernightParkingRequest(actionDialog.requestId, {
@@ -282,8 +321,14 @@ async function confirmAction() {
282
321
  remarks: remarks.value.trim(),
283
322
  })
284
323
  actionDialog.open = false
324
+ // One refresh. The second, unawaited call fired the same GET a second time
325
+ // and could land BEFORE the first, putting the pre-update rows back.
285
326
  await refreshOvernightParkingRequests()
286
- refreshOvernightParkingRequests()
327
+ showMessage(`Request ${actionDialog.action}.`, "success")
328
+ } catch (error: any) {
329
+ // The dialog stays OPEN on failure, with the remarks still typed, so the
330
+ // action can be retried rather than silently lost.
331
+ showMessage(error?.data?.message || `Failed to ${verb} this request. Please try again.`, "error")
287
332
  } finally {
288
333
  actionLoading.value = false
289
334
  }
@@ -291,6 +336,9 @@ async function confirmAction() {
291
336
 
292
337
  watch([statusFilter], ([newStatus], [oldStatus]) => {
293
338
  if (newStatus === oldStatus) return
339
+ // A new filter is a new result set - staying on page 3 of the old one asks
340
+ // the API for a page that usually does not exist, and the table draws empty.
341
+ page.value = 1
294
342
  refreshOvernightParkingRequests()
295
343
  }, { immediate: false })
296
344
  </script>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.138",
5
+ "version": "3.2.2-staging.139",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",