vasuzex 2.3.5 → 2.3.7
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.
|
@@ -26,6 +26,7 @@ import { Link } from 'react-router-dom';
|
|
|
26
26
|
* @param {Function} props.onReject - Reject action handler (approval workflows)
|
|
27
27
|
* @param {Function} props.onViewHistory - View approval history handler
|
|
28
28
|
* @param {boolean} props.hasApproval - Whether to show approval actions (default: false)
|
|
29
|
+
* @param {Array} props.customActions - Extra menu items: [{ label, icon, onClick, className, condition, separator }]
|
|
29
30
|
* @returns {JSX.Element}
|
|
30
31
|
*/
|
|
31
32
|
export function RowActionsCell({
|
|
@@ -39,6 +40,7 @@ export function RowActionsCell({
|
|
|
39
40
|
onReject,
|
|
40
41
|
onViewHistory,
|
|
41
42
|
hasApproval = false,
|
|
43
|
+
customActions = [],
|
|
42
44
|
}) {
|
|
43
45
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
44
46
|
const menuRef = useRef(null);
|
|
@@ -157,6 +159,33 @@ export function RowActionsCell({
|
|
|
157
159
|
</>
|
|
158
160
|
)}
|
|
159
161
|
|
|
162
|
+
{/* Custom Actions */}
|
|
163
|
+
{customActions.length > 0 && (
|
|
164
|
+
<>
|
|
165
|
+
{customActions.map((action, idx) => {
|
|
166
|
+
if (action.condition && !action.condition(row)) return null;
|
|
167
|
+
return (
|
|
168
|
+
<React.Fragment key={idx}>
|
|
169
|
+
{action.separator && (
|
|
170
|
+
<div className="border-t border-gray-200 dark:border-gray-700 my-1" />
|
|
171
|
+
)}
|
|
172
|
+
<button
|
|
173
|
+
onClick={() => {
|
|
174
|
+
action.onClick(row);
|
|
175
|
+
setIsMenuOpen(false);
|
|
176
|
+
}}
|
|
177
|
+
className={`w-full flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 ${action.className || 'text-gray-700 dark:text-gray-300'}`}
|
|
178
|
+
>
|
|
179
|
+
{action.icon && <action.icon className="h-4 w-4 shrink-0" />}
|
|
180
|
+
<span>{action.label}</span>
|
|
181
|
+
</button>
|
|
182
|
+
</React.Fragment>
|
|
183
|
+
);
|
|
184
|
+
})}
|
|
185
|
+
<div className="border-t border-gray-200 dark:border-gray-700 my-1" />
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
|
|
160
189
|
{/* Toggle Status */}
|
|
161
190
|
{onToggle && (
|
|
162
191
|
<button
|
|
@@ -210,6 +239,14 @@ RowActionsCell.propTypes = {
|
|
|
210
239
|
onReject: PropTypes.func,
|
|
211
240
|
onViewHistory: PropTypes.func,
|
|
212
241
|
hasApproval: PropTypes.bool,
|
|
242
|
+
customActions: PropTypes.arrayOf(PropTypes.shape({
|
|
243
|
+
label: PropTypes.string.isRequired,
|
|
244
|
+
icon: PropTypes.elementType,
|
|
245
|
+
onClick: PropTypes.func.isRequired,
|
|
246
|
+
className: PropTypes.string,
|
|
247
|
+
condition: PropTypes.func,
|
|
248
|
+
separator: PropTypes.bool,
|
|
249
|
+
})),
|
|
213
250
|
};
|
|
214
251
|
|
|
215
252
|
export default RowActionsCell;
|
|
@@ -59,6 +59,10 @@ try {
|
|
|
59
59
|
* @param {string} props.emptyText - Text to show when no data
|
|
60
60
|
* @param {boolean} props.persistState - Enable URL state persistence (default: true)
|
|
61
61
|
*/
|
|
62
|
+
|
|
63
|
+
// URL params that DataTable owns — all other params (e.g. trashed) are preserved as-is
|
|
64
|
+
const DT_PARAMS = ['page', 'sortBy', 'sortOrder', 'search', 'statusFilter', 'limit'];
|
|
65
|
+
|
|
62
66
|
export function DataTable(props) {
|
|
63
67
|
// Internal refresh key for self-refresh
|
|
64
68
|
const [refreshKey, setRefreshKey] = React.useState(0);
|
|
@@ -179,39 +183,43 @@ export function DataTable(props) {
|
|
|
179
183
|
const updateURL = React.useCallback((state) => {
|
|
180
184
|
if (!persistState || typeof window === 'undefined') return;
|
|
181
185
|
|
|
182
|
-
|
|
186
|
+
// Always read from window.location.search (current, live URL) not from
|
|
187
|
+
// the searchParams closure — the closure can be one render behind when
|
|
188
|
+
// the parent just called setSearchParams in the same batch.
|
|
189
|
+
const current = new URLSearchParams(window.location.search);
|
|
190
|
+
|
|
191
|
+
// Remove DataTable-managed params
|
|
192
|
+
DT_PARAMS.forEach(k => current.delete(k));
|
|
193
|
+
for (const key of [...current.keys()]) {
|
|
194
|
+
if (key.startsWith('columnSearch[')) current.delete(key);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Re-add non-default values to keep URL clean
|
|
198
|
+
if (state.page !== 1) current.set('page', state.page);
|
|
199
|
+
if (state.sortBy) current.set('sortBy', state.sortBy);
|
|
200
|
+
if (state.sortOrder !== initialSortOrder) current.set('sortOrder', state.sortOrder);
|
|
201
|
+
if (state.search) current.set('search', state.search);
|
|
202
|
+
if (state.statusFilter !== 'all') current.set('statusFilter', state.statusFilter);
|
|
203
|
+
if (state.limit !== (initialLimit || 10)) current.set('limit', state.limit);
|
|
183
204
|
|
|
184
|
-
const params = new URLSearchParams();
|
|
185
|
-
|
|
186
|
-
// Only add non-default values to keep URL clean
|
|
187
|
-
if (state.page !== 1) params.set('page', state.page);
|
|
188
|
-
if (state.sortBy) params.set('sortBy', state.sortBy);
|
|
189
|
-
if (state.sortOrder !== initialSortOrder) params.set('sortOrder', state.sortOrder);
|
|
190
|
-
if (state.search) params.set('search', state.search);
|
|
191
|
-
if (state.statusFilter !== 'all') params.set('statusFilter', state.statusFilter);
|
|
192
|
-
if (state.limit !== (initialLimit || 10)) params.set('limit', state.limit);
|
|
193
|
-
|
|
194
205
|
// Add column search params
|
|
195
206
|
Object.entries(state.columnSearch || {}).forEach(([field, value]) => {
|
|
196
|
-
if (value)
|
|
207
|
+
if (value) current.set(`columnSearch[${field}]`, value);
|
|
197
208
|
});
|
|
198
209
|
|
|
199
210
|
// Use React Router if available, otherwise window.history
|
|
200
211
|
if (hasReactRouter && setSearchParams) {
|
|
201
|
-
|
|
202
|
-
setSearchParams(params, { replace: true });
|
|
212
|
+
setSearchParams(current, { replace: true });
|
|
203
213
|
} else if (typeof window !== 'undefined') {
|
|
204
|
-
const newURL =
|
|
205
|
-
? `${window.location.pathname}?${
|
|
214
|
+
const newURL = current.toString()
|
|
215
|
+
? `${window.location.pathname}?${current.toString()}`
|
|
206
216
|
: window.location.pathname;
|
|
207
|
-
|
|
208
|
-
|
|
209
217
|
window.history.replaceState({}, '', newURL);
|
|
210
218
|
}
|
|
211
|
-
}, [persistState, hasReactRouter, setSearchParams]);
|
|
219
|
+
}, [persistState, hasReactRouter, setSearchParams, initialSortOrder, initialLimit]);
|
|
212
220
|
|
|
213
221
|
/**
|
|
214
|
-
* Sync URL whenever state changes
|
|
222
|
+
* Sync URL whenever DataTable state changes
|
|
215
223
|
*/
|
|
216
224
|
React.useEffect(() => {
|
|
217
225
|
updateURL({
|
|
@@ -77,6 +77,7 @@ export function EnhancedPhotoManager({
|
|
|
77
77
|
deletePhoto,
|
|
78
78
|
deleteMultiplePhotos,
|
|
79
79
|
reorderPhotos,
|
|
80
|
+
setPrimaryPhoto,
|
|
80
81
|
} = photosHook;
|
|
81
82
|
|
|
82
83
|
const [selectedPhotos, setSelectedPhotos] = useState([]);
|
|
@@ -255,6 +256,7 @@ export function EnhancedPhotoManager({
|
|
|
255
256
|
isSelected={selectedPhotos.includes(photo.id)}
|
|
256
257
|
onSelect={handlePhotoSelect}
|
|
257
258
|
onDelete={handlePhotoDelete}
|
|
259
|
+
onSetPrimary={setPrimaryPhoto || undefined}
|
|
258
260
|
disabled={loading}
|
|
259
261
|
/>
|
|
260
262
|
))}
|
|
@@ -294,6 +296,7 @@ EnhancedPhotoManager.propTypes = {
|
|
|
294
296
|
deletePhoto: PropTypes.func,
|
|
295
297
|
deleteMultiplePhotos: PropTypes.func,
|
|
296
298
|
reorderPhotos: PropTypes.func,
|
|
299
|
+
setPrimaryPhoto: PropTypes.func,
|
|
297
300
|
}).isRequired,
|
|
298
301
|
/** Section title */
|
|
299
302
|
title: PropTypes.string,
|