xladmin 0.2.8 → 0.2.9
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/dist/client.d.ts +11 -3
- package/dist/components/ModelPage.d.ts +19 -1
- package/dist/components/model-page/useModelPageController.d.ts +9 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +225 -62
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import type { AdminChoicesResponse, AdminDeletePreviewResponse, AdminDetailRespo
|
|
|
2
2
|
export type XLAdminRequestOptions = {
|
|
3
3
|
signal?: AbortSignal;
|
|
4
4
|
};
|
|
5
|
+
export type XLAdminSelectionScope = {
|
|
6
|
+
q?: string;
|
|
7
|
+
filters?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
export type XLAdminSelectionOptions = {
|
|
10
|
+
selectAll?: boolean;
|
|
11
|
+
selectionScope?: XLAdminSelectionScope;
|
|
12
|
+
};
|
|
5
13
|
export type XLAdminClient = {
|
|
6
14
|
getModels: () => Promise<AdminModelsResponse>;
|
|
7
15
|
getModel: (slug: string) => Promise<AdminModelMeta>;
|
|
@@ -16,11 +24,11 @@ export type XLAdminClient = {
|
|
|
16
24
|
patchItem: (slug: string, id: string | number, payload: Record<string, unknown>) => Promise<AdminDetailResponse>;
|
|
17
25
|
deleteItem: (slug: string, id: string | number) => Promise<void>;
|
|
18
26
|
getDeletePreview: (slug: string, id: string | number) => Promise<AdminDeletePreviewResponse>;
|
|
19
|
-
bulkDelete: (slug: string, ids: Array<string | number
|
|
27
|
+
bulkDelete: (slug: string, ids: Array<string | number>, options?: XLAdminSelectionOptions) => Promise<{
|
|
20
28
|
deleted: number;
|
|
21
29
|
}>;
|
|
22
|
-
getBulkDeletePreview: (slug: string, ids: Array<string | number
|
|
23
|
-
runBulkAction: (slug: string, actionSlug: string, ids: Array<string | number>, payload?: Record<string, unknown
|
|
30
|
+
getBulkDeletePreview: (slug: string, ids: Array<string | number>, options?: XLAdminSelectionOptions) => Promise<AdminDeletePreviewResponse>;
|
|
31
|
+
runBulkAction: (slug: string, actionSlug: string, ids: Array<string | number>, payload?: Record<string, unknown>, options?: XLAdminSelectionOptions) => Promise<{
|
|
24
32
|
processed: number;
|
|
25
33
|
} & Record<string, unknown>>;
|
|
26
34
|
runObjectAction: (slug: string, id: string | number, actionSlug: string, payload?: Record<string, unknown>) => Promise<AdminObjectActionResponse>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import type { XLAdminClient } from '../client';
|
|
2
3
|
import type { XLAdminRouter } from '../router';
|
|
3
4
|
type AdminModelPageProps = {
|
|
@@ -5,7 +6,24 @@ type AdminModelPageProps = {
|
|
|
5
6
|
basePath: string;
|
|
6
7
|
slug: string;
|
|
7
8
|
router?: XLAdminRouter;
|
|
9
|
+
renderBeforePagination?: (context: ModelPageToolbarContext) => ReactNode;
|
|
8
10
|
};
|
|
9
11
|
export type ModelPageProps = AdminModelPageProps;
|
|
10
|
-
export
|
|
12
|
+
export type ModelPageToolbarContext = {
|
|
13
|
+
client: XLAdminClient;
|
|
14
|
+
slug: string;
|
|
15
|
+
meta: {
|
|
16
|
+
slug: string;
|
|
17
|
+
title: string;
|
|
18
|
+
};
|
|
19
|
+
selectedIds: Array<string | number>;
|
|
20
|
+
isAllMatchingSelected: boolean;
|
|
21
|
+
selectionCount: number;
|
|
22
|
+
total: number;
|
|
23
|
+
appliedQuery: string;
|
|
24
|
+
sortValue: string;
|
|
25
|
+
appliedFilters: Record<string, string>;
|
|
26
|
+
refresh: () => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export declare function ModelPage({ client, basePath, slug, router, renderBeforePagination }: ModelPageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
29
|
export {};
|
|
@@ -16,6 +16,7 @@ export declare function useModelPageController({ client, slug, pathname, locatio
|
|
|
16
16
|
appliedQuery: string;
|
|
17
17
|
bulkActionMenuAnchor: HTMLElement | null;
|
|
18
18
|
bulkActions: import("@xladmin-core/types").AdminBulkActionMeta[];
|
|
19
|
+
clearBulkDeleteState: () => void;
|
|
19
20
|
createOpen: boolean;
|
|
20
21
|
currentPage: number;
|
|
21
22
|
data: AdminListResponse | null;
|
|
@@ -25,6 +26,9 @@ export declare function useModelPageController({ client, slug, pathname, locatio
|
|
|
25
26
|
error: string | null;
|
|
26
27
|
fieldMap: Map<string, import("@xladmin-core/types").AdminFieldMeta>;
|
|
27
28
|
filtersOpen: boolean;
|
|
29
|
+
handleClearDeletePreview: () => void;
|
|
30
|
+
handleCloseBulkActionMenu: () => void;
|
|
31
|
+
handleCloseRowMenu: () => void;
|
|
28
32
|
handleConfirmDelete: () => Promise<void>;
|
|
29
33
|
handleFilterChange: (filterSlug: string, value: string) => void;
|
|
30
34
|
handleOpenRowMenu: (event: {
|
|
@@ -36,10 +40,13 @@ export declare function useModelPageController({ client, slug, pathname, locatio
|
|
|
36
40
|
handleRowDelete: (rowId: string | number) => Promise<void>;
|
|
37
41
|
handleRunNamedBulkAction: (actionSlug: string) => Promise<void>;
|
|
38
42
|
handleSearchCommit: (nextQuery: string) => void;
|
|
43
|
+
handleSelectAllMatching: () => void;
|
|
39
44
|
handleToggleAllVisible: (checked: boolean) => void;
|
|
40
45
|
handleToggleSelection: (rowId: string | number, checked: boolean) => void;
|
|
41
46
|
hasListFilters: boolean;
|
|
47
|
+
hasSelection: boolean;
|
|
42
48
|
hasVisibleSelection: boolean;
|
|
49
|
+
isAllMatchingSelected: boolean;
|
|
43
50
|
isDeletePreviewLoading: boolean;
|
|
44
51
|
isDeleteSubmitting: boolean;
|
|
45
52
|
isLoading: boolean;
|
|
@@ -53,6 +60,7 @@ export declare function useModelPageController({ client, slug, pathname, locatio
|
|
|
53
60
|
rowActionMenuAnchor: HTMLElement | null;
|
|
54
61
|
rowActionMenuId: string | number | null;
|
|
55
62
|
rows: Record<string, unknown>[];
|
|
63
|
+
selectionCount: number;
|
|
56
64
|
selectedIdSet: Set<string>;
|
|
57
65
|
selectedIds: (string | number)[];
|
|
58
66
|
setBulkActionMenuAnchor: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
|
|
@@ -62,9 +70,7 @@ export declare function useModelPageController({ client, slug, pathname, locatio
|
|
|
62
70
|
setDeletePreviewOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
63
71
|
setFiltersOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
64
72
|
setPageInput: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
65
|
-
|
|
66
|
-
setRowActionMenuAnchor: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
|
|
67
|
-
setRowActionMenuId: import("react").Dispatch<import("react").SetStateAction<string | number | null>>;
|
|
73
|
+
sortValue: string;
|
|
68
74
|
sortFields: string[];
|
|
69
75
|
toggleSort: (fieldName: string) => void;
|
|
70
76
|
total: number;
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ export { createAxiosXLAdminClient, createFetchXLAdminClient, createFetchXLAdminT
|
|
|
2
2
|
export { XLAdminRouterProvider, buildUrlWithParams, createBrowserXLAdminRouter, handleNavLinkClick, type XLAdminAnchorProps, type XLAdminLocation, type XLAdminRouter, useXLAdminLocation, useXLAdminRouter, } from './router';
|
|
3
3
|
export { FormDialog, type FormDialogProps } from './components/FormDialog';
|
|
4
4
|
export { OverviewPage, type OverviewPageProps } from './components/OverviewPage';
|
|
5
|
-
export { ModelPage, type ModelPageProps } from './components/ModelPage';
|
|
5
|
+
export { ModelPage, type ModelPageProps, type ModelPageToolbarContext } from './components/ModelPage';
|
|
6
6
|
export { ObjectPage, type ObjectPageProps } from './components/ObjectPage';
|
|
7
7
|
export { Shell, type ShellProps } from './components/Shell';
|
|
8
8
|
export { FieldEditor, type FieldEditorProps } from './components/FieldEditor';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { createAxiosXLAdminClient, createFetchXLAdminClient, createFetchXLAdminT
|
|
|
2
2
|
export { XLAdminRouterProvider, buildUrlWithParams, createBrowserXLAdminRouter, handleNavLinkClick, type XLAdminAnchorProps, type XLAdminLocation, type XLAdminRouter, useXLAdminLocation, useXLAdminRouter, } from './router';
|
|
3
3
|
export { FormDialog, type FormDialogProps } from './components/FormDialog';
|
|
4
4
|
export { OverviewPage, type OverviewPageProps } from './components/OverviewPage';
|
|
5
|
-
export { ModelPage, type ModelPageProps } from './components/ModelPage';
|
|
5
|
+
export { ModelPage, type ModelPageProps, type ModelPageToolbarContext } from './components/ModelPage';
|
|
6
6
|
export { ObjectPage, type ObjectPageProps } from './components/ObjectPage';
|
|
7
7
|
export { Shell, type ShellProps } from './components/Shell';
|
|
8
8
|
export { FieldEditor, type FieldEditorProps } from './components/FieldEditor';
|
package/dist/index.js
CHANGED
|
@@ -25,17 +25,25 @@ function createXLAdminClient(transport) {
|
|
|
25
25
|
async getDeletePreview(slug, id) {
|
|
26
26
|
return await transportGet(transport, `/xladmin/models/${slug}/items/${id}/delete-preview/`);
|
|
27
27
|
},
|
|
28
|
-
async bulkDelete(slug, ids) {
|
|
29
|
-
return await transportPost(
|
|
28
|
+
async bulkDelete(slug, ids, options) {
|
|
29
|
+
return await transportPost(
|
|
30
|
+
transport,
|
|
31
|
+
`/xladmin/models/${slug}/bulk-delete/`,
|
|
32
|
+
buildSelectionPayload(ids, void 0, options)
|
|
33
|
+
);
|
|
30
34
|
},
|
|
31
|
-
async getBulkDeletePreview(slug, ids) {
|
|
32
|
-
return await transportPost(
|
|
35
|
+
async getBulkDeletePreview(slug, ids, options) {
|
|
36
|
+
return await transportPost(
|
|
37
|
+
transport,
|
|
38
|
+
`/xladmin/models/${slug}/bulk-delete-preview/`,
|
|
39
|
+
buildSelectionPayload(ids, void 0, options)
|
|
40
|
+
);
|
|
33
41
|
},
|
|
34
|
-
async runBulkAction(slug, actionSlug, ids, payload) {
|
|
42
|
+
async runBulkAction(slug, actionSlug, ids, payload, options) {
|
|
35
43
|
return await transportPost(
|
|
36
44
|
transport,
|
|
37
45
|
`/xladmin/models/${slug}/bulk-actions/${actionSlug}/`,
|
|
38
|
-
|
|
46
|
+
buildSelectionPayload(ids, payload, options)
|
|
39
47
|
);
|
|
40
48
|
},
|
|
41
49
|
async runObjectAction(slug, id, actionSlug, payload) {
|
|
@@ -181,6 +189,23 @@ function buildSearchParams(params) {
|
|
|
181
189
|
}
|
|
182
190
|
return searchParams.toString();
|
|
183
191
|
}
|
|
192
|
+
function buildSelectionPayload(ids, payload, options) {
|
|
193
|
+
var _a;
|
|
194
|
+
const result = {
|
|
195
|
+
ids,
|
|
196
|
+
...payload != null ? payload : {}
|
|
197
|
+
};
|
|
198
|
+
if (options == null ? void 0 : options.selectAll) {
|
|
199
|
+
result.select_all = true;
|
|
200
|
+
}
|
|
201
|
+
if (options == null ? void 0 : options.selectionScope) {
|
|
202
|
+
result.selection_scope = {
|
|
203
|
+
q: options.selectionScope.q,
|
|
204
|
+
filters: (_a = options.selectionScope.filters) != null ? _a : {}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
184
209
|
|
|
185
210
|
// src/router.tsx
|
|
186
211
|
import { createContext, useContext, useMemo, useRef, useSyncExternalStore } from "react";
|
|
@@ -2562,6 +2587,7 @@ function useModelPageController({
|
|
|
2562
2587
|
})
|
|
2563
2588
|
)) != null ? _c : null;
|
|
2564
2589
|
const [selectedIds, setSelectedIds] = useState9([]);
|
|
2590
|
+
const [isAllMatchingSelected, setIsAllMatchingSelected] = useState9(false);
|
|
2565
2591
|
const [createOpen, setCreateOpen] = useState9(false);
|
|
2566
2592
|
const [data, setData] = useState9(() => initialCachedResponse);
|
|
2567
2593
|
const [error, setError] = useState9(null);
|
|
@@ -2580,6 +2606,8 @@ function useModelPageController({
|
|
|
2580
2606
|
const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState9(false);
|
|
2581
2607
|
const [isDeleteSubmitting, setIsDeleteSubmitting] = useState9(false);
|
|
2582
2608
|
const [pendingDeleteIds, setPendingDeleteIds] = useState9([]);
|
|
2609
|
+
const [pendingDeleteSelectAll, setPendingDeleteSelectAll] = useState9(false);
|
|
2610
|
+
const [pendingDeleteScope, setPendingDeleteScope] = useState9(null);
|
|
2583
2611
|
const [pendingDeleteMode, setPendingDeleteMode] = useState9("single");
|
|
2584
2612
|
const [filtersOpen, setFiltersOpen] = useState9(false);
|
|
2585
2613
|
const requestIdRef = useRef3(0);
|
|
@@ -2602,15 +2630,32 @@ function useModelPageController({
|
|
|
2602
2630
|
const listFilters = (_j = meta == null ? void 0 : meta.list_filters) != null ? _j : [];
|
|
2603
2631
|
const hasListFilters = listFilters.length > 0;
|
|
2604
2632
|
const bulkActions = (_k = meta == null ? void 0 : meta.bulk_actions) != null ? _k : [];
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2633
|
+
const selectionScope = useMemo8(
|
|
2634
|
+
() => ({
|
|
2635
|
+
q: appliedQuery || void 0,
|
|
2636
|
+
filters: { ...appliedFilters }
|
|
2637
|
+
}),
|
|
2638
|
+
[appliedFilters, appliedQuery]
|
|
2639
|
+
);
|
|
2640
|
+
const selectedIdSet = useMemo8(() => {
|
|
2641
|
+
if (isAllMatchingSelected) {
|
|
2642
|
+
return new Set(rows.map((row) => {
|
|
2643
|
+
var _a2;
|
|
2644
|
+
return String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]);
|
|
2645
|
+
}));
|
|
2646
|
+
}
|
|
2647
|
+
return new Set(selectedIds.map((item) => String(item)));
|
|
2648
|
+
}, [isAllMatchingSelected, meta == null ? void 0 : meta.pk_field, rows, selectedIds]);
|
|
2649
|
+
const allVisibleSelected = rows.length > 0 && (isAllMatchingSelected || rows.every((row) => {
|
|
2607
2650
|
var _a2;
|
|
2608
2651
|
return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
|
|
2609
|
-
});
|
|
2610
|
-
const hasVisibleSelection = rows.some((row) => {
|
|
2652
|
+
}));
|
|
2653
|
+
const hasVisibleSelection = isAllMatchingSelected || rows.some((row) => {
|
|
2611
2654
|
var _a2;
|
|
2612
2655
|
return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
|
|
2613
2656
|
});
|
|
2657
|
+
const hasSelection = isAllMatchingSelected || selectedIds.length > 0;
|
|
2658
|
+
const selectionCount = isAllMatchingSelected ? total : selectedIds.length;
|
|
2614
2659
|
useEffect9(() => {
|
|
2615
2660
|
dataRef.current = data;
|
|
2616
2661
|
}, [data]);
|
|
@@ -2630,6 +2675,10 @@ function useModelPageController({
|
|
|
2630
2675
|
setPageInput(String(nextPage));
|
|
2631
2676
|
setAppliedFilters(nextFilters);
|
|
2632
2677
|
}, [locationSearch]);
|
|
2678
|
+
const clearSelection = useCallback4(() => {
|
|
2679
|
+
setSelectedIds([]);
|
|
2680
|
+
setIsAllMatchingSelected(false);
|
|
2681
|
+
}, []);
|
|
2633
2682
|
const loadItems = useCallback4(async () => {
|
|
2634
2683
|
var _a2;
|
|
2635
2684
|
const activeRequestId = requestIdRef.current + 1;
|
|
@@ -2646,7 +2695,9 @@ function useModelPageController({
|
|
|
2646
2695
|
const cachedResponse = (_a2 = getClientCacheBucket(client).listResponseCache.get(requestKey)) != null ? _a2 : null;
|
|
2647
2696
|
if (cachedResponse) {
|
|
2648
2697
|
setData(cachedResponse);
|
|
2649
|
-
|
|
2698
|
+
if (!isAllMatchingSelected) {
|
|
2699
|
+
setSelectedIds([]);
|
|
2700
|
+
}
|
|
2650
2701
|
setIsLoading(false);
|
|
2651
2702
|
return;
|
|
2652
2703
|
}
|
|
@@ -2661,7 +2712,9 @@ function useModelPageController({
|
|
|
2661
2712
|
}
|
|
2662
2713
|
pageSizeRef.current = response.meta.page_size;
|
|
2663
2714
|
setData(response);
|
|
2664
|
-
|
|
2715
|
+
if (!isAllMatchingSelected) {
|
|
2716
|
+
setSelectedIds([]);
|
|
2717
|
+
}
|
|
2665
2718
|
} catch (reason) {
|
|
2666
2719
|
setError(reason instanceof Error ? reason.message : t("model_load_error"));
|
|
2667
2720
|
} finally {
|
|
@@ -2669,7 +2722,7 @@ function useModelPageController({
|
|
|
2669
2722
|
setIsLoading(false);
|
|
2670
2723
|
}
|
|
2671
2724
|
}
|
|
2672
|
-
}, [appliedFilters, appliedQuery, client, currentPage, slug, sortValue, t]);
|
|
2725
|
+
}, [appliedFilters, appliedQuery, client, currentPage, isAllMatchingSelected, slug, sortValue, t]);
|
|
2673
2726
|
useEffect9(() => {
|
|
2674
2727
|
void loadItems();
|
|
2675
2728
|
}, [loadItems]);
|
|
@@ -2684,11 +2737,12 @@ function useModelPageController({
|
|
|
2684
2737
|
if (nextQuery === appliedQuery) {
|
|
2685
2738
|
return;
|
|
2686
2739
|
}
|
|
2740
|
+
clearSelection();
|
|
2687
2741
|
setAppliedQuery(nextQuery);
|
|
2688
2742
|
setCurrentPage(1);
|
|
2689
2743
|
setPageInput("1");
|
|
2690
2744
|
replaceLocation(nextQuery, sortValue, 1, appliedFilters);
|
|
2691
|
-
}, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
|
|
2745
|
+
}, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
|
|
2692
2746
|
const handlePageChange = useCallback4((nextPage) => {
|
|
2693
2747
|
const safePage = Math.min(Math.max(nextPage, 1), totalPages);
|
|
2694
2748
|
setCurrentPage(safePage);
|
|
@@ -2723,22 +2777,26 @@ function useModelPageController({
|
|
|
2723
2777
|
if (!value) {
|
|
2724
2778
|
delete nextFilters[filterSlug];
|
|
2725
2779
|
}
|
|
2780
|
+
clearSelection();
|
|
2726
2781
|
setAppliedFilters(nextFilters);
|
|
2727
2782
|
setCurrentPage(1);
|
|
2728
2783
|
setPageInput("1");
|
|
2729
2784
|
replaceLocation(appliedQuery, sortValue, 1, nextFilters);
|
|
2730
|
-
}, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
|
|
2785
|
+
}, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
|
|
2731
2786
|
const handleResetFilters = useCallback4(() => {
|
|
2732
2787
|
if (Object.keys(appliedFilters).length === 0) {
|
|
2733
2788
|
return;
|
|
2734
2789
|
}
|
|
2790
|
+
clearSelection();
|
|
2735
2791
|
setAppliedFilters({});
|
|
2736
2792
|
setCurrentPage(1);
|
|
2737
2793
|
setPageInput("1");
|
|
2738
2794
|
replaceLocation(appliedQuery, sortValue, 1, {});
|
|
2739
|
-
}, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
|
|
2795
|
+
}, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
|
|
2740
2796
|
const openSingleDeletePreview = useCallback4(async (rowId) => {
|
|
2741
2797
|
setPendingDeleteIds([rowId]);
|
|
2798
|
+
setPendingDeleteSelectAll(false);
|
|
2799
|
+
setPendingDeleteScope(null);
|
|
2742
2800
|
setPendingDeleteMode("single");
|
|
2743
2801
|
setDeletePreviewOpen(true);
|
|
2744
2802
|
setDeletePreview(null);
|
|
@@ -2754,17 +2812,23 @@ function useModelPageController({
|
|
|
2754
2812
|
}
|
|
2755
2813
|
}, [client, slug, t]);
|
|
2756
2814
|
const openBulkDeletePreview = useCallback4(async () => {
|
|
2757
|
-
if (
|
|
2815
|
+
if (!hasSelection) {
|
|
2758
2816
|
return;
|
|
2759
2817
|
}
|
|
2760
|
-
setPendingDeleteIds(selectedIds);
|
|
2818
|
+
setPendingDeleteIds(isAllMatchingSelected ? [] : selectedIds);
|
|
2819
|
+
setPendingDeleteSelectAll(isAllMatchingSelected);
|
|
2820
|
+
setPendingDeleteScope(isAllMatchingSelected ? selectionScope : null);
|
|
2761
2821
|
setPendingDeleteMode("bulk");
|
|
2762
2822
|
setDeletePreviewOpen(true);
|
|
2763
2823
|
setDeletePreview(null);
|
|
2764
2824
|
setDeletePreviewError(null);
|
|
2765
2825
|
setIsDeletePreviewLoading(true);
|
|
2766
2826
|
try {
|
|
2767
|
-
const preview = await client.getBulkDeletePreview(
|
|
2827
|
+
const preview = await client.getBulkDeletePreview(
|
|
2828
|
+
slug,
|
|
2829
|
+
isAllMatchingSelected ? [] : selectedIds,
|
|
2830
|
+
isAllMatchingSelected ? { selectAll: true, selectionScope } : void 0
|
|
2831
|
+
);
|
|
2768
2832
|
setDeletePreview(preview);
|
|
2769
2833
|
} catch (reason) {
|
|
2770
2834
|
setDeletePreviewError(reason instanceof Error ? reason.message : t("delete_preview_error"));
|
|
@@ -2772,9 +2836,12 @@ function useModelPageController({
|
|
|
2772
2836
|
setIsDeletePreviewLoading(false);
|
|
2773
2837
|
setBulkActionMenuAnchor(null);
|
|
2774
2838
|
}
|
|
2775
|
-
}, [client, selectedIds, slug, t]);
|
|
2839
|
+
}, [client, hasSelection, isAllMatchingSelected, selectedIds, selectionScope, slug, t]);
|
|
2776
2840
|
const handleConfirmDelete = useCallback4(async () => {
|
|
2777
|
-
if (pendingDeleteIds.length === 0) {
|
|
2841
|
+
if (pendingDeleteMode === "single" && pendingDeleteIds.length === 0) {
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (pendingDeleteMode === "bulk" && pendingDeleteIds.length === 0 && !pendingDeleteSelectAll) {
|
|
2778
2845
|
return;
|
|
2779
2846
|
}
|
|
2780
2847
|
setIsDeleteSubmitting(true);
|
|
@@ -2783,21 +2850,28 @@ function useModelPageController({
|
|
|
2783
2850
|
if (pendingDeleteMode === "single") {
|
|
2784
2851
|
await client.deleteItem(slug, pendingDeleteIds[0]);
|
|
2785
2852
|
} else {
|
|
2786
|
-
await client.bulkDelete(
|
|
2853
|
+
await client.bulkDelete(
|
|
2854
|
+
slug,
|
|
2855
|
+
pendingDeleteIds,
|
|
2856
|
+
pendingDeleteSelectAll && pendingDeleteScope ? { selectAll: true, selectionScope: pendingDeleteScope } : void 0
|
|
2857
|
+
);
|
|
2787
2858
|
}
|
|
2788
2859
|
setDeletePreviewOpen(false);
|
|
2789
2860
|
setDeletePreview(null);
|
|
2790
2861
|
setDeletePreviewError(null);
|
|
2791
2862
|
setPendingDeleteIds([]);
|
|
2863
|
+
setPendingDeleteSelectAll(false);
|
|
2864
|
+
setPendingDeleteScope(null);
|
|
2865
|
+
clearSelection();
|
|
2792
2866
|
await refresh();
|
|
2793
2867
|
} catch (reason) {
|
|
2794
2868
|
setDeletePreviewError(reason instanceof Error ? reason.message : t("object_delete_error"));
|
|
2795
2869
|
} finally {
|
|
2796
2870
|
setIsDeleteSubmitting(false);
|
|
2797
2871
|
}
|
|
2798
|
-
}, [client, pendingDeleteIds, pendingDeleteMode, refresh, slug, t]);
|
|
2872
|
+
}, [clearSelection, client, pendingDeleteIds, pendingDeleteMode, pendingDeleteScope, pendingDeleteSelectAll, refresh, slug, t]);
|
|
2799
2873
|
const handleRunNamedBulkAction = useCallback4(async (actionSlug) => {
|
|
2800
|
-
if (!actionSlug ||
|
|
2874
|
+
if (!actionSlug || !hasSelection) {
|
|
2801
2875
|
return;
|
|
2802
2876
|
}
|
|
2803
2877
|
if (actionSlug === "delete") {
|
|
@@ -2805,19 +2879,40 @@ function useModelPageController({
|
|
|
2805
2879
|
return;
|
|
2806
2880
|
}
|
|
2807
2881
|
try {
|
|
2808
|
-
await client.runBulkAction(
|
|
2882
|
+
await client.runBulkAction(
|
|
2883
|
+
slug,
|
|
2884
|
+
actionSlug,
|
|
2885
|
+
isAllMatchingSelected ? [] : selectedIds,
|
|
2886
|
+
void 0,
|
|
2887
|
+
isAllMatchingSelected ? { selectAll: true, selectionScope } : void 0
|
|
2888
|
+
);
|
|
2809
2889
|
setBulkActionMenuAnchor(null);
|
|
2890
|
+
clearSelection();
|
|
2810
2891
|
await refresh();
|
|
2811
2892
|
} catch (reason) {
|
|
2812
2893
|
setError(reason instanceof Error ? reason.message : t("object_action_error"));
|
|
2813
2894
|
}
|
|
2814
|
-
}, [client, openBulkDeletePreview, refresh, selectedIds, slug, t]);
|
|
2895
|
+
}, [clearSelection, client, hasSelection, isAllMatchingSelected, openBulkDeletePreview, refresh, selectedIds, selectionScope, slug, t]);
|
|
2815
2896
|
const handleRowDelete = useCallback4(async (rowId) => {
|
|
2816
2897
|
setRowActionMenuAnchor(null);
|
|
2817
2898
|
setRowActionMenuId(null);
|
|
2818
2899
|
await openSingleDeletePreview(rowId);
|
|
2819
2900
|
}, [openSingleDeletePreview]);
|
|
2820
2901
|
const handleToggleSelection = useCallback4((rowId, checked) => {
|
|
2902
|
+
if (isAllMatchingSelected) {
|
|
2903
|
+
if (checked) {
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
setIsAllMatchingSelected(false);
|
|
2907
|
+
if (!meta) {
|
|
2908
|
+
setSelectedIds([]);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
setSelectedIds(
|
|
2912
|
+
rows.map((row) => row[meta.pk_field]).filter((item) => String(item) !== String(rowId))
|
|
2913
|
+
);
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2821
2916
|
setSelectedIds((current) => {
|
|
2822
2917
|
const rowKey = String(rowId);
|
|
2823
2918
|
if (checked) {
|
|
@@ -2828,27 +2923,54 @@ function useModelPageController({
|
|
|
2828
2923
|
}
|
|
2829
2924
|
return current.filter((item) => String(item) !== rowKey);
|
|
2830
2925
|
});
|
|
2831
|
-
}, []);
|
|
2926
|
+
}, [isAllMatchingSelected, meta, rows]);
|
|
2832
2927
|
const handleToggleAllVisible = useCallback4((checked) => {
|
|
2833
2928
|
if (!meta) {
|
|
2834
2929
|
return;
|
|
2835
2930
|
}
|
|
2836
2931
|
if (checked) {
|
|
2932
|
+
setIsAllMatchingSelected(false);
|
|
2837
2933
|
setSelectedIds(rows.map((row) => row[meta.pk_field]));
|
|
2838
2934
|
return;
|
|
2839
2935
|
}
|
|
2936
|
+
clearSelection();
|
|
2937
|
+
}, [clearSelection, meta, rows]);
|
|
2938
|
+
const handleSelectAllMatching = useCallback4(() => {
|
|
2939
|
+
if (!hasSelection || total === 0) {
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2840
2942
|
setSelectedIds([]);
|
|
2841
|
-
|
|
2943
|
+
setIsAllMatchingSelected(true);
|
|
2944
|
+
}, [hasSelection, total]);
|
|
2945
|
+
const clearBulkDeleteState = useCallback4(() => {
|
|
2946
|
+
setPendingDeleteIds([]);
|
|
2947
|
+
setPendingDeleteSelectAll(false);
|
|
2948
|
+
setPendingDeleteScope(null);
|
|
2949
|
+
}, []);
|
|
2950
|
+
const handleClearDeletePreview = useCallback4(() => {
|
|
2951
|
+
setDeletePreviewOpen(false);
|
|
2952
|
+
setDeletePreview(null);
|
|
2953
|
+
setDeletePreviewError(null);
|
|
2954
|
+
clearBulkDeleteState();
|
|
2955
|
+
}, [clearBulkDeleteState]);
|
|
2842
2956
|
const handleOpenRowMenu = useCallback4((event, rowId) => {
|
|
2843
2957
|
setRowActionMenuAnchor(event.currentTarget);
|
|
2844
2958
|
setRowActionMenuId(rowId);
|
|
2845
2959
|
}, []);
|
|
2960
|
+
const handleCloseRowMenu = useCallback4(() => {
|
|
2961
|
+
setRowActionMenuAnchor(null);
|
|
2962
|
+
setRowActionMenuId(null);
|
|
2963
|
+
}, []);
|
|
2964
|
+
const handleCloseBulkActionMenu = useCallback4(() => {
|
|
2965
|
+
setBulkActionMenuAnchor(null);
|
|
2966
|
+
}, []);
|
|
2846
2967
|
return {
|
|
2847
2968
|
allVisibleSelected,
|
|
2848
2969
|
appliedFilters,
|
|
2849
2970
|
appliedQuery,
|
|
2850
2971
|
bulkActionMenuAnchor,
|
|
2851
2972
|
bulkActions,
|
|
2973
|
+
clearBulkDeleteState,
|
|
2852
2974
|
createOpen,
|
|
2853
2975
|
currentPage,
|
|
2854
2976
|
data,
|
|
@@ -2858,6 +2980,9 @@ function useModelPageController({
|
|
|
2858
2980
|
error,
|
|
2859
2981
|
fieldMap,
|
|
2860
2982
|
filtersOpen,
|
|
2983
|
+
handleClearDeletePreview,
|
|
2984
|
+
handleCloseBulkActionMenu,
|
|
2985
|
+
handleCloseRowMenu,
|
|
2861
2986
|
handleConfirmDelete,
|
|
2862
2987
|
handleFilterChange,
|
|
2863
2988
|
handleOpenRowMenu,
|
|
@@ -2867,10 +2992,13 @@ function useModelPageController({
|
|
|
2867
2992
|
handleRowDelete,
|
|
2868
2993
|
handleRunNamedBulkAction,
|
|
2869
2994
|
handleSearchCommit,
|
|
2995
|
+
handleSelectAllMatching,
|
|
2870
2996
|
handleToggleAllVisible,
|
|
2871
2997
|
handleToggleSelection,
|
|
2872
2998
|
hasListFilters,
|
|
2999
|
+
hasSelection,
|
|
2873
3000
|
hasVisibleSelection,
|
|
3001
|
+
isAllMatchingSelected,
|
|
2874
3002
|
isDeletePreviewLoading,
|
|
2875
3003
|
isDeleteSubmitting,
|
|
2876
3004
|
isLoading,
|
|
@@ -2884,6 +3012,7 @@ function useModelPageController({
|
|
|
2884
3012
|
rowActionMenuAnchor,
|
|
2885
3013
|
rowActionMenuId,
|
|
2886
3014
|
rows,
|
|
3015
|
+
selectionCount,
|
|
2887
3016
|
selectedIdSet,
|
|
2888
3017
|
selectedIds,
|
|
2889
3018
|
setBulkActionMenuAnchor,
|
|
@@ -2893,9 +3022,7 @@ function useModelPageController({
|
|
|
2893
3022
|
setDeletePreviewOpen,
|
|
2894
3023
|
setFiltersOpen,
|
|
2895
3024
|
setPageInput,
|
|
2896
|
-
|
|
2897
|
-
setRowActionMenuAnchor,
|
|
2898
|
-
setRowActionMenuId,
|
|
3025
|
+
sortValue,
|
|
2899
3026
|
sortFields,
|
|
2900
3027
|
toggleSort,
|
|
2901
3028
|
total,
|
|
@@ -2971,10 +3098,11 @@ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
|
2971
3098
|
var SEARCH_DEBOUNCE_MS = 300;
|
|
2972
3099
|
var CHECKBOX_COLUMN_WIDTH = 56;
|
|
2973
3100
|
var ACTIONS_COLUMN_WIDTH = 56;
|
|
2974
|
-
function ModelPage({ client, basePath, slug, router }) {
|
|
3101
|
+
function ModelPage({ client, basePath, slug, router, renderBeforePagination }) {
|
|
2975
3102
|
var _a, _b;
|
|
2976
3103
|
const locale = useAdminLocale();
|
|
2977
3104
|
const t = useAdminTranslation();
|
|
3105
|
+
const selectAllLabel = locale === "ru" ? "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435" : "Select All";
|
|
2978
3106
|
const resolvedRouter = useXLAdminRouter(router);
|
|
2979
3107
|
const location = useXLAdminLocation(resolvedRouter);
|
|
2980
3108
|
const theme = useTheme2();
|
|
@@ -2995,6 +3123,22 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
2995
3123
|
return /* @__PURE__ */ jsx19(ModelPageSkeleton, {});
|
|
2996
3124
|
}
|
|
2997
3125
|
const meta = controller.meta;
|
|
3126
|
+
const beforePagination = renderBeforePagination == null ? void 0 : renderBeforePagination({
|
|
3127
|
+
client,
|
|
3128
|
+
slug,
|
|
3129
|
+
meta: {
|
|
3130
|
+
slug: meta.slug,
|
|
3131
|
+
title: meta.title
|
|
3132
|
+
},
|
|
3133
|
+
selectedIds: controller.selectedIds,
|
|
3134
|
+
isAllMatchingSelected: controller.isAllMatchingSelected,
|
|
3135
|
+
selectionCount: controller.selectionCount,
|
|
3136
|
+
total: controller.total,
|
|
3137
|
+
appliedQuery: controller.appliedQuery,
|
|
3138
|
+
sortValue: controller.sortValue,
|
|
3139
|
+
appliedFilters: controller.appliedFilters,
|
|
3140
|
+
refresh: controller.refresh
|
|
3141
|
+
});
|
|
2998
3142
|
return /* @__PURE__ */ jsx19(XLAdminRouterProvider, { router: resolvedRouter, children: /* @__PURE__ */ jsxs12(Stack9, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
|
|
2999
3143
|
/* @__PURE__ */ jsx19(
|
|
3000
3144
|
MainHeader,
|
|
@@ -3006,8 +3150,8 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
3006
3150
|
"aria-label": t("create"),
|
|
3007
3151
|
onClick: () => controller.setCreateOpen(true),
|
|
3008
3152
|
sx: {
|
|
3009
|
-
width:
|
|
3010
|
-
height:
|
|
3153
|
+
width: 29,
|
|
3154
|
+
height: 29,
|
|
3011
3155
|
p: 0.5,
|
|
3012
3156
|
backgroundColor: "primary.main",
|
|
3013
3157
|
color: "primary.contrastText",
|
|
@@ -3031,28 +3175,53 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
3031
3175
|
flexShrink: 0
|
|
3032
3176
|
},
|
|
3033
3177
|
children: /* @__PURE__ */ jsxs12(Stack9, { direction: { xs: "column", lg: "row" }, spacing: 1.5, alignItems: { lg: "center" }, children: [
|
|
3034
|
-
/* @__PURE__ */
|
|
3035
|
-
|
|
3178
|
+
/* @__PURE__ */ jsxs12(
|
|
3179
|
+
Stack9,
|
|
3036
3180
|
{
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3181
|
+
direction: { xs: "column", xl: "row" },
|
|
3182
|
+
spacing: 1.5,
|
|
3183
|
+
alignItems: { xl: "center" },
|
|
3184
|
+
sx: { flex: 1, minWidth: 0 },
|
|
3185
|
+
children: [
|
|
3186
|
+
/* @__PURE__ */ jsx19(
|
|
3187
|
+
SearchField,
|
|
3188
|
+
{
|
|
3189
|
+
value: controller.appliedQuery,
|
|
3190
|
+
onCommit: controller.handleSearchCommit,
|
|
3191
|
+
debounceMs: SEARCH_DEBOUNCE_MS,
|
|
3192
|
+
placeholder: t("search")
|
|
3193
|
+
}
|
|
3194
|
+
),
|
|
3195
|
+
controller.hasSelection ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, alignItems: "center", sx: { minWidth: 0, flexWrap: "wrap" }, children: [
|
|
3196
|
+
/* @__PURE__ */ jsx19(
|
|
3197
|
+
Button4,
|
|
3198
|
+
{
|
|
3199
|
+
variant: "outlined",
|
|
3200
|
+
endIcon: /* @__PURE__ */ jsx19(ExpandMoreIcon4, {}),
|
|
3201
|
+
onClick: (event) => controller.setBulkActionMenuAnchor(event.currentTarget),
|
|
3202
|
+
children: t("actions")
|
|
3203
|
+
}
|
|
3204
|
+
),
|
|
3205
|
+
!controller.isAllMatchingSelected && controller.selectionCount < controller.total ? /* @__PURE__ */ jsx19(
|
|
3206
|
+
Button4,
|
|
3207
|
+
{
|
|
3208
|
+
variant: "text",
|
|
3209
|
+
onClick: controller.handleSelectAllMatching,
|
|
3210
|
+
sx: { px: 0.5, minWidth: "auto" },
|
|
3211
|
+
children: selectAllLabel
|
|
3212
|
+
}
|
|
3213
|
+
) : null,
|
|
3214
|
+
/* @__PURE__ */ jsxs12(Typography5, { color: "text.secondary", sx: { alignSelf: "center", whiteSpace: "nowrap" }, children: [
|
|
3215
|
+
t("selected_count", { count: controller.selectionCount }),
|
|
3216
|
+
" / ",
|
|
3217
|
+
controller.total
|
|
3218
|
+
] })
|
|
3219
|
+
] }) : null
|
|
3220
|
+
]
|
|
3041
3221
|
}
|
|
3042
|
-
)
|
|
3043
|
-
controller.selectedIds.length > 0 ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, sx: { flex: { lg: 1 }, minWidth: 0 }, children: [
|
|
3044
|
-
/* @__PURE__ */ jsx19(
|
|
3045
|
-
Button4,
|
|
3046
|
-
{
|
|
3047
|
-
variant: "outlined",
|
|
3048
|
-
endIcon: /* @__PURE__ */ jsx19(ExpandMoreIcon4, {}),
|
|
3049
|
-
onClick: (event) => controller.setBulkActionMenuAnchor(event.currentTarget),
|
|
3050
|
-
children: t("actions")
|
|
3051
|
-
}
|
|
3052
|
-
),
|
|
3053
|
-
/* @__PURE__ */ jsx19(Typography5, { color: "text.secondary", sx: { alignSelf: "center" }, children: t("selected_count", { count: controller.selectedIds.length }) })
|
|
3054
|
-
] }) : null,
|
|
3222
|
+
),
|
|
3055
3223
|
/* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, alignItems: "center", sx: { marginLeft: "auto" }, children: [
|
|
3224
|
+
beforePagination ? /* @__PURE__ */ jsx19(Box9, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforePagination }) : null,
|
|
3056
3225
|
isMobile && controller.hasListFilters ? /* @__PURE__ */ jsx19(Tooltip, { title: t("filters"), children: /* @__PURE__ */ jsx19(
|
|
3057
3226
|
IconButton3,
|
|
3058
3227
|
{
|
|
@@ -3257,7 +3426,7 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
3257
3426
|
{
|
|
3258
3427
|
anchorEl: controller.bulkActionMenuAnchor,
|
|
3259
3428
|
open: Boolean(controller.bulkActionMenuAnchor),
|
|
3260
|
-
onClose:
|
|
3429
|
+
onClose: controller.handleCloseBulkActionMenu,
|
|
3261
3430
|
children: controller.bulkActions.map((action) => /* @__PURE__ */ jsx19(
|
|
3262
3431
|
MenuItem2,
|
|
3263
3432
|
{
|
|
@@ -3273,10 +3442,7 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
3273
3442
|
{
|
|
3274
3443
|
anchorEl: controller.rowActionMenuAnchor,
|
|
3275
3444
|
open: Boolean(controller.rowActionMenuAnchor),
|
|
3276
|
-
onClose:
|
|
3277
|
-
controller.setRowActionMenuAnchor(null);
|
|
3278
|
-
controller.setRowActionMenuId(null);
|
|
3279
|
-
},
|
|
3445
|
+
onClose: controller.handleCloseRowMenu,
|
|
3280
3446
|
children: /* @__PURE__ */ jsx19(
|
|
3281
3447
|
MenuItem2,
|
|
3282
3448
|
{
|
|
@@ -3353,10 +3519,7 @@ function ModelPage({ client, basePath, slug, router }) {
|
|
|
3353
3519
|
if (controller.isDeleteSubmitting) {
|
|
3354
3520
|
return;
|
|
3355
3521
|
}
|
|
3356
|
-
controller.
|
|
3357
|
-
controller.setDeletePreview(null);
|
|
3358
|
-
controller.setDeletePreviewError(null);
|
|
3359
|
-
controller.setPendingDeleteIds([]);
|
|
3522
|
+
controller.handleClearDeletePreview();
|
|
3360
3523
|
},
|
|
3361
3524
|
onConfirm: () => void controller.handleConfirmDelete()
|
|
3362
3525
|
}
|