vasuzex 2.3.13 → 2.3.15
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 +129 -0
- package/framework/Database/Model.js +18 -5
- package/framework/Services/Media/MediaManager.js +213 -118
- package/frontend/react-ui/components/BreadCrumb/BreadCrumb.jsx +3 -3
- package/frontend/react-ui/components/DataTable/ActionDefaults.jsx +116 -2
- package/frontend/react-ui/components/DataTable/CellComponents/RowActionsCell.jsx +26 -5
- package/frontend/react-ui/components/DataTable/DataTable.jsx +168 -26
- package/frontend/react-ui/components/DataTable/Filters.jsx +80 -41
- package/frontend/react-ui/components/DataTable/MobileCardList.jsx +226 -0
- package/frontend/react-ui/components/DataTable/Pagination.jsx +120 -57
- package/frontend/react-ui/components/DataTable/TableBody.jsx +85 -24
- package/frontend/react-ui/components/DataTable/TableState.jsx +42 -13
- package/frontend/react-ui/hooks/index.js +1 -0
- package/frontend/react-ui/hooks/useMobileDetect.js +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,135 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Vasuzex will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.3.15] - 2026-04-20
|
|
6
|
+
|
|
7
|
+
### ✨ Added
|
|
8
|
+
|
|
9
|
+
#### DataTable — Mobile Card View (`vasuzex/react`)
|
|
10
|
+
|
|
11
|
+
- **New `mobileCardView` prop** — When `mobileCardView={true}`, DataTable renders `MobileCardList` instead of the standard table on viewports narrower than 640px (Tailwind `sm` breakpoint)
|
|
12
|
+
- **`MobileCardList` component** — Stacked card layout optimised for touch
|
|
13
|
+
- Column priority system: mark columns as `priority="primary"` (shown full-width, no label), `priority="secondary"` (compact label:value rows), or omit to auto-assign first 3 as primary and rest as secondary
|
|
14
|
+
- Supports full action set: view, edit, delete, hard-delete (🔥), and restore actions
|
|
15
|
+
- Skeleton loading state with `animate-pulse` placeholders
|
|
16
|
+
- Empty-state text support via `emptyText` prop
|
|
17
|
+
- **New `useMobileDetect` hook** — `window.matchMedia`-based viewport detection; no resize listener polling, reacts instantly, SSR-safe fallback
|
|
18
|
+
|
|
19
|
+
#### Pagination — Mobile Responsive Layout (`vasuzex/react`)
|
|
20
|
+
|
|
21
|
+
- **Mobile compact mode** (visible on `sm:hidden`): Prev / "Page X of Y" + jump-to-page input / Next
|
|
22
|
+
- Jump-to-page input: type a page number and press Enter (or blur) to navigate directly
|
|
23
|
+
- **Desktop unchanged**: full numbered page buttons with ellipsis remain as before
|
|
24
|
+
|
|
25
|
+
#### BreadCrumb — Responsive Props (`vasuzex/react`)
|
|
26
|
+
|
|
27
|
+
- Added `className` prop for external style overrides
|
|
28
|
+
- Responsive typography and spacing: `text-lg sm:text-xl`, `gap-2 sm:gap-3`, `mb-3 sm:mb-6`
|
|
29
|
+
|
|
30
|
+
### 🐛 Fixed
|
|
31
|
+
|
|
32
|
+
#### Filters — Responsive Layout (`vasuzex/react`)
|
|
33
|
+
|
|
34
|
+
- Moved refresh button inline with rows-per-page selector and showing info (no longer isolated in a separate row)
|
|
35
|
+
- Shortened label: "Rows:" instead of "Rows per page:" — saves horizontal space on small screens
|
|
36
|
+
- Compact showing info format: `1–10 of 50` instead of `Showing 1 to 10 of 50 items`
|
|
37
|
+
|
|
38
|
+
#### RowActionsCell — Dropdown Clipping Fix (`vasuzex/react`)
|
|
39
|
+
|
|
40
|
+
- **Root cause**: Dropdown was positioned `absolute` inside the table cell, causing it to be clipped by `overflow-x-auto` on the table container
|
|
41
|
+
- **Fix**: Dropdown now uses `position: fixed` anchored to the trigger button via `getBoundingClientRect()`, with `z-index: 9999` — never clipped by any overflow parent
|
|
42
|
+
- Fixed click-outside detection: separate `btnRef` and `dropdownRef` so clicking the button or inside the menu never incorrectly closes the dropdown
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## [2.3.14] - 2026-04-08
|
|
47
|
+
|
|
48
|
+
### 🐛 Fixed
|
|
49
|
+
|
|
50
|
+
#### DataTable — Content Loader (Skeleton) on Sort/Search/Filter (`vasuzex/react`)
|
|
51
|
+
|
|
52
|
+
**Root Causes & Fixes:**
|
|
53
|
+
|
|
54
|
+
1. **Browser Paint Timing Issue** — Skeleton loader not visible on sort/search (only on explicit refresh click)
|
|
55
|
+
- **Root cause**: React 18 `createRoot` + fast localhost API responses (2-10ms) meant the skeleton commit and data response both arrived within the same vsync frame (~16ms), so the browser only painted the final data state
|
|
56
|
+
- **Fix**: `useLayoutEffect` commits skeleton synchronously + `rAF → setTimeout(0)` defers XHR until AFTER browser paint
|
|
57
|
+
- **Technical flow**:
|
|
58
|
+
1. `useLayoutEffect` → skeleton committed to DOM (synchronously, before paint)
|
|
59
|
+
2. Passive effect queues `rAF` → scheduled pre-paint
|
|
60
|
+
3. `rAF` queues `setTimeout(0)` → scheduled as macro-task (after paint)
|
|
61
|
+
4. Browser **paints skeleton** ← now visible to user
|
|
62
|
+
5. `setTimeout(0)` fires → `fetchData()` → XHR opens → response arrives after skeleton is on screen
|
|
63
|
+
|
|
64
|
+
2. **API Client Error Interceptor Stripping Cancel Errors** — "No data found" briefly appeared on every sort/search
|
|
65
|
+
- **Root cause**: axios cancel errors have `err.code === 'ERR_CANCELED'` which DataTable checks — but error interceptor transformed ALL errors to plain `{ message, errors }` objects, stripping the code/name properties
|
|
66
|
+
- **Impact**: Cancelled requests hit the error path → `setData([])` + `setLoading(false)` → "No data found" flash, then data arrived
|
|
67
|
+
- **Fix**: Both api clients (`admin/web` and `business/web`) now check `if (axios.isCancel(error))` early and pass through untransformed
|
|
68
|
+
|
|
69
|
+
3. **Double-Fetch on Every Interaction** — Multiple concurrent requests on sort/search
|
|
70
|
+
- **Root cause**: `refreshSignal` and `refreshKey` effects had `fetchData` in deps. `fetchData` recreates on every sort/search (because its own deps change). Since every admin page passes `refreshSignal={refreshKey}`, BOTH the main fetch effect AND the refreshSignal effect fired on every interaction → 2+ concurrent requests
|
|
71
|
+
- **Fix**: Introduced `fetchDataRef` — holds always-current `fetchData` without needing it in secondary effect deps. Secondary effects now only fire when their actual trigger (`refreshSignal` value or `refreshKey` value) changes
|
|
72
|
+
|
|
73
|
+
4. **Tailwind CSS Classes Not Generated** — animate-pulse skeleton class missing from bundle
|
|
74
|
+
- **Root cause**: Admin web's `tailwind.config.js` only scanned `./src/**/*`, not the symlinked `vasuzex-v2` directory
|
|
75
|
+
- **Impact**: `animate-pulse` utility class not in admin web's CSS bundle (business web already had correct path)
|
|
76
|
+
- **Fix**: Added `../../../vasuzex-v2/frontend/react-ui/**/*.{js,jsx}` to Tailwind content array (matching business web pattern)
|
|
77
|
+
|
|
78
|
+
**Files Modified:**
|
|
79
|
+
- [`DataTable.jsx`](frontend/react-ui/components/DataTable/DataTable.jsx) — useLayoutEffect + rAF+setTimeout pattern + fetchDataRef
|
|
80
|
+
- `apps/admin/web/src/lib/api-client.js` — axios.isCancel check before error transformation
|
|
81
|
+
- `apps/business/web/src/lib/apiClient.js` — axios.isCancel check before error transformation
|
|
82
|
+
- `apps/admin/web/tailwind.config.js` — added vasuzex-v2 path to content array
|
|
83
|
+
|
|
84
|
+
**Result:** Skeleton loader now reliably shows on sort, search, filter, and every other data trigger, then data smoothly renders when ready. No "no data found" flash.
|
|
85
|
+
|
|
86
|
+
#### Model — Soft-Delete Restore Fix (`vasuzex/eloquent`)
|
|
87
|
+
|
|
88
|
+
- **Issue**: `restore()` method used `save()` which triggered model observers and allowed normal query scopes, causing inconsistent state
|
|
89
|
+
- **Fix**: Direct database update via `withTrashed()` query builder to bypass soft-delete scope
|
|
90
|
+
- **Changes**:
|
|
91
|
+
- Use `withTrashed().where(pk, id).update()` instead of `save()`
|
|
92
|
+
- Auto-update `updated_at` timestamp when timestamps enabled
|
|
93
|
+
- Calls `syncOriginal()` to update model cache state
|
|
94
|
+
- Fires `restored` model event after DB update completes
|
|
95
|
+
- **Impact**: Soft-deleted records now restore cleanly without triggering update observers
|
|
96
|
+
|
|
97
|
+
#### MediaManager — WebP/AVIF Format Negotiation (`vasuzex/services`)
|
|
98
|
+
|
|
99
|
+
- **Issue**: Media serving didn't support modern image formats (WebP, AVIF) or client content-type negotiation
|
|
100
|
+
- **Enhancements**:
|
|
101
|
+
1. **Format negotiation** — Query param `?format=webp|avif|jpeg|png` overrides client Accept header
|
|
102
|
+
2. **LRU in-memory cache** — Hot thumbnails cached in memory (200 entry limit) to avoid repeated filesystem hits
|
|
103
|
+
3. **Format-aware disk cache keys** — WebP and JPEG of same image cached separately
|
|
104
|
+
4. **ETag support** — Content MD5 hash for conditional requests (304 Not Modified)
|
|
105
|
+
5. **Immutable cache headers** — 1-year max-age via dedicated controller
|
|
106
|
+
6. **Direct format lookup** — Cache lookup by format (no extension loop)
|
|
107
|
+
- **Performance**: 5-50x faster for repeated hot thumbnail requests
|
|
108
|
+
- **Files Modified**: [`framework/Services/Media/MediaManager.js`](framework/Services/Media/MediaManager.js)
|
|
109
|
+
|
|
110
|
+
### ✨ Added
|
|
111
|
+
|
|
112
|
+
#### ActionDefaults — Hard Delete & Restore Actions (`vasuzex/react`)
|
|
113
|
+
|
|
114
|
+
- **Hard Delete Action** — Permanent delete with severe confirmation (Flame icon 🔥)
|
|
115
|
+
- Shows in trash-only mode for trashed records
|
|
116
|
+
- `DELETE ?hardDelete=true` query parameter
|
|
117
|
+
- `createHardDeleteClickHandler()` helper
|
|
118
|
+
- "Cannot be undone" warning in confirmation dialog
|
|
119
|
+
|
|
120
|
+
- **Restore Action** — Restore soft-deleted records (RotateCcw icon)
|
|
121
|
+
- Shows for trashed rows
|
|
122
|
+
- `PATCH {restoreUrl}` request
|
|
123
|
+
- `createRestoreClickHandler()` helper
|
|
124
|
+
- Smooth restore with toast notification
|
|
125
|
+
|
|
126
|
+
- **Custom Action Tooltip** — Auto-generate tooltip from label when title not provided
|
|
127
|
+
- Improves UX for custom actions without explicit title
|
|
128
|
+
|
|
129
|
+
**Files Modified:** [`frontend/react-ui/components/DataTable/ActionDefaults.jsx`](frontend/react-ui/components/DataTable/ActionDefaults.jsx)
|
|
130
|
+
|
|
131
|
+
**Impact:** Complete soft-delete/trash workflow now supported in DataTable — view, restore, or permanently delete with proper confirmations.
|
|
132
|
+
|
|
133
|
+
|
|
5
134
|
## [2.3.13] - 2026-04-05
|
|
6
135
|
|
|
7
136
|
### 🐛 Fixed
|
|
@@ -535,15 +535,28 @@ export class Model extends GuruORMModel {
|
|
|
535
535
|
return false;
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
this.
|
|
538
|
+
const deletedAtColumn = this.constructor.deletedAt;
|
|
539
|
+
const pk = this.constructor.primaryKey || 'id';
|
|
540
|
+
const updates = { [deletedAtColumn]: null };
|
|
541
|
+
|
|
542
|
+
// Add updated_at if timestamps are enabled
|
|
543
|
+
if (this.constructor.timestamps && this.constructor.updatedAt) {
|
|
544
|
+
updates[this.constructor.updatedAt] = new Date();
|
|
545
|
+
}
|
|
539
546
|
|
|
540
|
-
|
|
547
|
+
// Use withTrashed() to bypass the soft-delete scope — a trashed record isn't
|
|
548
|
+
// visible to the normal query (which adds WHERE deleted_at IS NULL).
|
|
549
|
+
await this.constructor.withTrashed().where(pk, this.getKey()).update(updates);
|
|
541
550
|
|
|
542
|
-
|
|
543
|
-
|
|
551
|
+
this.setAttribute(deletedAtColumn, null);
|
|
552
|
+
if (updates[this.constructor.updatedAt]) {
|
|
553
|
+
this.setAttribute(this.constructor.updatedAt, updates[this.constructor.updatedAt]);
|
|
544
554
|
}
|
|
555
|
+
this.syncOriginal();
|
|
545
556
|
|
|
546
|
-
|
|
557
|
+
await this.fireModelEvent('restored', false);
|
|
558
|
+
|
|
559
|
+
return true;
|
|
547
560
|
}
|
|
548
561
|
|
|
549
562
|
/**
|