vasuzex 2.3.12 → 2.3.14
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 +117 -0
- package/README.md +505 -514
- package/framework/Database/Model.js +18 -5
- package/framework/Services/Media/MediaManager.js +213 -118
- package/frontend/react-ui/components/DataTable/ActionDefaults.jsx +116 -2
- package/frontend/react-ui/components/DataTable/DataTable.jsx +157 -22
- package/frontend/react-ui/components/DataTable/Filters.jsx +99 -56
- package/frontend/react-ui/components/DataTable/TableBody.jsx +85 -24
- package/frontend/react-ui/components/DataTable/TableState.jsx +42 -13
- package/jsconfig.json +1 -0
- package/package.json +1 -1
|
@@ -5,15 +5,19 @@ import {
|
|
|
5
5
|
applyActionDefaults,
|
|
6
6
|
createViewClickHandler,
|
|
7
7
|
createDeleteClickHandler,
|
|
8
|
+
createHardDeleteClickHandler,
|
|
9
|
+
createRestoreClickHandler,
|
|
10
|
+
ACTION_DEFAULTS,
|
|
8
11
|
} from "./ActionDefaults.jsx";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* TableBody Component - Production Ready
|
|
12
|
-
*
|
|
13
|
-
* Table body with data rows, column rendering, and action buttons
|
|
14
|
-
* Handles Switch component for status toggle
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* Table body with data rows, column rendering, and action buttons.
|
|
17
|
+
* Handles Switch component for status toggle.
|
|
18
|
+
* Trash-aware: when trashMode='only', delete becomes hardDelete with warning;
|
|
19
|
+
* restore button shown automatically for trashed rows.
|
|
20
|
+
*
|
|
17
21
|
* @module components/DataTable/TableBody
|
|
18
22
|
*/
|
|
19
23
|
export function TableBody({
|
|
@@ -27,14 +31,26 @@ export function TableBody({
|
|
|
27
31
|
resourceName,
|
|
28
32
|
resourceIdField = "id",
|
|
29
33
|
onRefresh,
|
|
34
|
+
// Trash support: 'without' | 'with' | 'only'
|
|
35
|
+
trashMode,
|
|
36
|
+
// URL for the restore endpoint (e.g. "/products/:id/restore")
|
|
37
|
+
restoreUrl,
|
|
30
38
|
}) {
|
|
31
39
|
if (loading) {
|
|
40
|
+
const skeletonRows = Array.from({ length: 5 });
|
|
41
|
+
const colCount = columns.length + (actions ? 1 : 0);
|
|
32
42
|
return (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
<>
|
|
44
|
+
{skeletonRows.map((_, rowIdx) => (
|
|
45
|
+
<tr key={rowIdx} className="border-b border-gray-200 dark:border-gray-700 animate-pulse">
|
|
46
|
+
{Array.from({ length: colCount }).map((_, colIdx) => (
|
|
47
|
+
<td key={colIdx} className="px-6 py-4">
|
|
48
|
+
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4" />
|
|
49
|
+
</td>
|
|
50
|
+
))}
|
|
51
|
+
</tr>
|
|
52
|
+
))}
|
|
53
|
+
</>
|
|
38
54
|
);
|
|
39
55
|
}
|
|
40
56
|
|
|
@@ -83,6 +99,11 @@ export function TableBody({
|
|
|
83
99
|
return null;
|
|
84
100
|
}
|
|
85
101
|
|
|
102
|
+
// In trash-only mode: skip edit/switch/status-toggle actions
|
|
103
|
+
if (trashMode === 'only' && ['edit', 'switch'].includes(userAction.name)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
// Apply defaults based on action name
|
|
87
108
|
const action = applyActionDefaults(userAction, resourceName, resourceIdField);
|
|
88
109
|
|
|
@@ -114,22 +135,46 @@ export function TableBody({
|
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
// Handle delete action
|
|
138
|
+
// Handle delete action:
|
|
139
|
+
// — in trash-only mode, OR when row is already soft-deleted (has deleted_at) → hard-delete with permanent-delete confirmation
|
|
140
|
+
// — normal mode → soft-delete with standard confirmation
|
|
118
141
|
if (action.type === "button" && action.name === "delete") {
|
|
119
142
|
const deleteAction = action;
|
|
120
143
|
if (deleteAction.deleteUrl && !userAction.onClick) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
deleteAction.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
144
|
+
const isAlreadyTrashed = !!row.deleted_at;
|
|
145
|
+
if (trashMode === 'only' || isAlreadyTrashed) {
|
|
146
|
+
// Override to hardDelete
|
|
147
|
+
deleteAction.onClick = createHardDeleteClickHandler(
|
|
148
|
+
api,
|
|
149
|
+
deleteAction.deleteUrl,
|
|
150
|
+
deleteAction.confirmMessage || "This will permanently remove the record from the database. This action cannot be undone.",
|
|
151
|
+
resourceIdField,
|
|
152
|
+
{
|
|
153
|
+
confirmTitle: "Permanently Delete?",
|
|
154
|
+
confirmButtonText: "Yes, permanently delete!",
|
|
155
|
+
successMessage: deleteAction.successMessage || "Permanently deleted",
|
|
156
|
+
onRefresh,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
// Apply hardDelete styling
|
|
160
|
+
if (!userAction.className) {
|
|
161
|
+
deleteAction.className = ACTION_DEFAULTS.hardDelete.extraClass;
|
|
162
|
+
}
|
|
163
|
+
deleteAction.title = "Permanently Delete";
|
|
164
|
+
} else {
|
|
165
|
+
deleteAction.onClick = createDeleteClickHandler(
|
|
166
|
+
api,
|
|
167
|
+
deleteAction.deleteUrl,
|
|
168
|
+
deleteAction.confirmMessage || "Are you sure you want to delete this item?",
|
|
169
|
+
resourceIdField,
|
|
170
|
+
{
|
|
171
|
+
confirmTitle: deleteAction.confirmTitle,
|
|
172
|
+
confirmButtonText: deleteAction.confirmButtonText,
|
|
173
|
+
successMessage: deleteAction.successMessage,
|
|
174
|
+
onRefresh,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
}
|
|
133
178
|
}
|
|
134
179
|
}
|
|
135
180
|
|
|
@@ -138,7 +183,7 @@ export function TableBody({
|
|
|
138
183
|
const className =
|
|
139
184
|
typeof action.className === "function"
|
|
140
185
|
? action.className(row)
|
|
141
|
-
: action.className || "";
|
|
186
|
+
: action.className || action.extraClass || "";
|
|
142
187
|
const title =
|
|
143
188
|
typeof action.title === "function"
|
|
144
189
|
? action.title(row)
|
|
@@ -183,6 +228,22 @@ export function TableBody({
|
|
|
183
228
|
|
|
184
229
|
return null;
|
|
185
230
|
})}
|
|
231
|
+
|
|
232
|
+
{/* Auto-inject Restore button when trashMode is active and row is trashed */}
|
|
233
|
+
{restoreUrl && (trashMode === 'only' || (trashMode === 'with' && row.deleted_at)) && (() => {
|
|
234
|
+
const handler = createRestoreClickHandler(api, restoreUrl, resourceIdField, { onRefresh });
|
|
235
|
+
const Icon = ACTION_DEFAULTS.restore.icon;
|
|
236
|
+
return (
|
|
237
|
+
<button
|
|
238
|
+
key="auto-restore"
|
|
239
|
+
onClick={() => handler(row)}
|
|
240
|
+
className={ACTION_DEFAULTS.restore.extraClass}
|
|
241
|
+
title="Restore"
|
|
242
|
+
>
|
|
243
|
+
<Icon className="h-4 w-4" />
|
|
244
|
+
</button>
|
|
245
|
+
);
|
|
246
|
+
})()}
|
|
186
247
|
</div>
|
|
187
248
|
</td>
|
|
188
249
|
)}
|
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Skeleton pulse block – light-grey bar that animates.
|
|
5
|
+
* Width can be "full", "3/4", "1/2", "1/3" or a fixed px value.
|
|
6
|
+
*/
|
|
7
|
+
const SkeletonCell = ({ width = "3/4", height = "h-4" }) => {
|
|
8
|
+
const widthClass =
|
|
9
|
+
width === "full" ? "w-full" :
|
|
10
|
+
width === "3/4" ? "w-3/4" :
|
|
11
|
+
width === "1/2" ? "w-1/2" :
|
|
12
|
+
width === "1/3" ? "w-1/3" :
|
|
13
|
+
width === "1/4" ? "w-1/4" : width;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className={`${height} ${widthClass} rounded bg-gray-200 dark:bg-gray-700 animate-pulse`}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A single skeleton row matching the number of visible columns + optional action column.
|
|
24
|
+
* Alternates bar widths so the shimmer does not look monotonous.
|
|
25
|
+
*/
|
|
26
|
+
const WIDTHS = ["3/4", "1/2", "full", "1/3", "3/4", "1/2"];
|
|
27
|
+
|
|
28
|
+
const SkeletonRow = ({ colSpan }) => (
|
|
29
|
+
<tr className="border-b border-gray-200 dark:border-gray-700">
|
|
30
|
+
{Array.from({ length: colSpan }).map((_, i) => (
|
|
31
|
+
<td key={i} className="px-6 py-4">
|
|
32
|
+
<SkeletonCell width={WIDTHS[i % WIDTHS.length]} />
|
|
33
|
+
</td>
|
|
34
|
+
))}
|
|
35
|
+
</tr>
|
|
36
|
+
);
|
|
37
|
+
|
|
3
38
|
/**
|
|
4
39
|
* TableState Component
|
|
5
|
-
* Handles loading and empty states for DataTable
|
|
40
|
+
* Handles loading (skeleton rows) and empty states for DataTable
|
|
6
41
|
*/
|
|
7
|
-
export const TableState = ({ loading, empty, colSpan, emptyText }) => {
|
|
42
|
+
export const TableState = ({ loading, empty, colSpan, emptyText, skeletonRows = 6 }) => {
|
|
8
43
|
if (loading) {
|
|
9
44
|
return (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
16
|
-
</svg>
|
|
17
|
-
<span>Loading data...</span>
|
|
18
|
-
</div>
|
|
19
|
-
</td>
|
|
20
|
-
</tr>
|
|
45
|
+
<>
|
|
46
|
+
{Array.from({ length: skeletonRows }).map((_, i) => (
|
|
47
|
+
<SkeletonRow key={i} colSpan={colSpan} />
|
|
48
|
+
))}
|
|
49
|
+
</>
|
|
21
50
|
);
|
|
22
51
|
}
|
|
23
52
|
if (empty) {
|
package/jsconfig.json
CHANGED