ding-react-admin 2.0.0 → 2.0.4

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.
Files changed (169) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -195
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +1772 -1169
  5. package/dist/src/components/NavMenuLabel.d.ts +8 -0
  6. package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
  7. package/dist/src/components/NavMenuSearch.d.ts +9 -0
  8. package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
  9. package/dist/src/components/ScrollableArea.d.ts +10 -0
  10. package/dist/src/components/ScrollableArea.d.ts.map +1 -0
  11. package/dist/src/components/index.d.ts +3 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/context/AuthProvider.d.ts +2 -0
  14. package/dist/src/context/AuthProvider.d.ts.map +1 -1
  15. package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
  16. package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
  17. package/dist/src/crud/ResourceForm.d.ts +1 -4
  18. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  20. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  22. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  23. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  24. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  25. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  26. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  27. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  28. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  29. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  30. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  31. package/dist/src/crud/index.d.ts +5 -0
  32. package/dist/src/crud/index.d.ts.map +1 -1
  33. package/dist/src/crud/types.d.ts +12 -0
  34. package/dist/src/crud/types.d.ts.map +1 -1
  35. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  36. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  37. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  38. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  39. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  40. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  41. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  42. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  43. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +1 -0
  44. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -1
  45. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  46. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  47. package/dist/src/crud/utils/toFormData.d.ts +1 -1
  48. package/dist/src/crud/utils/toFormData.d.ts.map +1 -1
  49. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  50. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  51. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  52. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  53. package/dist/src/crud/utils/useFormRecord.d.ts +9 -7
  54. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -1
  55. package/dist/src/data/parseFormErrorHelpers.d.ts +6 -27
  56. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  57. package/dist/src/index.d.ts +2 -2
  58. package/dist/src/index.d.ts.map +1 -1
  59. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  60. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  61. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  62. package/dist/src/layouts/navFilter.d.ts +5 -0
  63. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  64. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  65. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  66. package/dist/src/types.d.ts +12 -0
  67. package/dist/src/types.d.ts.map +1 -1
  68. package/package.json +79 -59
  69. package/src/app/AdminApp.tsx +34 -34
  70. package/src/components/AuthAlternateLink.tsx +23 -23
  71. package/src/components/DensitySwitch.tsx +31 -31
  72. package/src/components/NavMenuLabel.tsx +26 -0
  73. package/src/components/NavMenuSearch.tsx +76 -0
  74. package/src/components/ScrollableArea.tsx +46 -0
  75. package/src/components/ThemeSwitch.tsx +28 -28
  76. package/src/components/ThemeToolbar.tsx +34 -34
  77. package/src/components/index.ts +7 -4
  78. package/src/components/navMenu.css +21 -0
  79. package/src/components/scrollable.css +28 -0
  80. package/src/context/AppThemeProvider.tsx +128 -128
  81. package/src/context/AuthProvider.tsx +89 -77
  82. package/src/context/DataProvider.tsx +30 -30
  83. package/src/context/PermissionsProvider.tsx +57 -57
  84. package/src/crud/FilterBar.tsx +36 -36
  85. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  86. package/src/crud/FormSteps.tsx +135 -135
  87. package/src/crud/FormTabs.tsx +126 -126
  88. package/src/crud/InlineFormSet.tsx +162 -162
  89. package/src/crud/ListActionsBar.tsx +106 -106
  90. package/src/crud/ResourceForm.tsx +83 -160
  91. package/src/crud/ResourceFormModal.tsx +75 -133
  92. package/src/crud/ResourceRecordForm.tsx +166 -0
  93. package/src/crud/columns/BooleanColumn.tsx +32 -32
  94. package/src/crud/columns/CustomColumn.tsx +33 -33
  95. package/src/crud/columns/DateColumn.tsx +28 -28
  96. package/src/crud/columns/ImageColumn.tsx +58 -58
  97. package/src/crud/columns/NumberColumn.tsx +31 -31
  98. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  99. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  100. package/src/crud/columns/TextColumn.tsx +38 -38
  101. package/src/crud/context/FilterContext.tsx +71 -71
  102. package/src/crud/context/FormContext.tsx +39 -39
  103. package/src/crud/context/FormSectionContext.tsx +22 -22
  104. package/src/crud/context/InlineFieldsRegistry.tsx +61 -61
  105. package/src/crud/context/ListContext.tsx +93 -93
  106. package/src/crud/fields/BooleanField.tsx +38 -38
  107. package/src/crud/fields/DateField.tsx +53 -53
  108. package/src/crud/fields/FileField.tsx +123 -123
  109. package/src/crud/fields/ImageField.tsx +120 -120
  110. package/src/crud/fields/NumberField.tsx +52 -52
  111. package/src/crud/fields/PasswordField.tsx +99 -99
  112. package/src/crud/fields/ReferenceField.tsx +237 -96
  113. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  114. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  115. package/src/crud/fields/SelectField.tsx +48 -48
  116. package/src/crud/fields/TextField.tsx +46 -46
  117. package/src/crud/fields/uploadFieldUtils.ts +21 -21
  118. package/src/crud/fields/useUploadPreviewUrl.ts +20 -20
  119. package/src/crud/filters/BooleanFilter.tsx +38 -38
  120. package/src/crud/filters/DateFilter.tsx +35 -35
  121. package/src/crud/filters/NumberFilter.tsx +33 -33
  122. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  123. package/src/crud/filters/SelectFilter.tsx +44 -44
  124. package/src/crud/filters/TextFilter.tsx +36 -36
  125. package/src/crud/index.ts +99 -94
  126. package/src/crud/types.ts +146 -134
  127. package/src/crud/utils/buildFormPayload.ts +24 -24
  128. package/src/crud/utils/buildInlineRowsPayload.ts +37 -37
  129. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -29
  130. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  131. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  132. package/src/crud/utils/formErrors.test.ts +168 -0
  133. package/src/crud/utils/formErrors.ts +138 -42
  134. package/src/crud/utils/formSectionErrors.ts +85 -85
  135. package/src/crud/utils/getFormValue.ts +13 -13
  136. package/src/crud/utils/hasUploadValues.ts +9 -9
  137. package/src/crud/utils/nestedFieldPath.ts +8 -8
  138. package/src/crud/utils/prepareFormSubmitBody.ts +22 -17
  139. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  140. package/src/crud/utils/setFormValue.ts +18 -18
  141. package/src/crud/utils/sortQueryParam.ts +37 -37
  142. package/src/crud/utils/toFormData.ts +79 -81
  143. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  144. package/src/crud/utils/useAbortableEffect.ts +17 -17
  145. package/src/crud/utils/useChoices.ts +330 -182
  146. package/src/crud/utils/useFormRecord.ts +179 -156
  147. package/src/crud/utils/useListQueryState.ts +191 -191
  148. package/src/data/abortError.test.ts +19 -19
  149. package/src/data/abortError.ts +10 -10
  150. package/src/data/createMemoryResourceHandlers.ts +78 -78
  151. package/src/data/createRestResourceHandlers.ts +63 -63
  152. package/src/data/dataUtils.test.ts +228 -220
  153. package/src/data/inMemoryList.ts +97 -97
  154. package/src/data/parseFormErrorHelpers.test.ts +201 -138
  155. package/src/data/parseFormErrorHelpers.ts +376 -311
  156. package/src/data/sortHelpers.ts +48 -48
  157. package/src/index.ts +211 -207
  158. package/src/layouts/AdminLayout.tsx +530 -382
  159. package/src/layouts/AuthPageLayout.tsx +87 -81
  160. package/src/layouts/navFilter.ts +49 -0
  161. package/src/layouts/navMenuItems.tsx +44 -0
  162. package/src/pages/LoginPage.tsx +80 -80
  163. package/src/pages/PlaceholderPage.tsx +9 -9
  164. package/src/permissions/resourcePermissions.ts +47 -47
  165. package/src/router/createAdminRouter.tsx +64 -64
  166. package/src/router/guards.tsx +63 -63
  167. package/src/router/routeAccess.ts +89 -89
  168. package/src/types.ts +10 -0
  169. package/src/vite-env.d.ts +1 -1
@@ -1,156 +1,179 @@
1
- import type { MessageInstance } from "antd/es/message/interface";
2
- import {
3
- useCallback,
4
- useState,
5
- type MutableRefObject,
6
- } from "react";
7
- import type { NavigateFunction } from "react-router-dom";
8
- import type { DataProvider } from "../../data/dataProviderTypes";
9
- import { isAbortError } from "../../data/abortError";
10
- import {
11
- type DefaultValues,
12
- type FieldValues,
13
- type UseFormReturn,
14
- } from "react-hook-form";
15
- import type { InlineFieldRegistration } from "../context/InlineFieldsRegistry";
16
- import { buildResourceFormSubmitBody } from "./buildResourceFormSubmitBody";
17
- import { applyApiErrorsToForm } from "./formErrors";
18
- import { useAbortableEffect } from "./useAbortableEffect";
19
-
20
- type LoadOptions<T extends FieldValues> = {
21
- dp: DataProvider;
22
- resource: string;
23
- id: string | undefined;
24
- isNew: boolean;
25
- form: UseFormReturn<T>;
26
- message: MessageInstance;
27
- defaultValues?: Partial<T>;
28
- };
29
-
30
- /** Edit mode: `getOne` → `form.reset` → bump `formVersion` so field arrays remount. */
31
- export function useFormRecordLoad<T extends FieldValues>({
32
- dp,
33
- resource,
34
- id,
35
- isNew,
36
- form,
37
- message,
38
- defaultValues,
39
- }: LoadOptions<T>) {
40
- const [loading, setLoading] = useState(!isNew);
41
- const [formVersion, setFormVersion] = useState(0);
42
-
43
- const load = useCallback(
44
- async (signal?: AbortSignal) => {
45
- if (isNew || !id) {
46
- if (defaultValues) {
47
- form.reset({ ...defaultValues } as DefaultValues<T>);
48
- }
49
- setLoading(false);
50
- return;
51
- }
52
- setLoading(true);
53
- try {
54
- const res = await dp.getOne(resource, id, { signal });
55
- if (signal?.aborted) return;
56
- form.reset(res.data as DefaultValues<T>);
57
- setFormVersion((v) => v + 1);
58
- } catch (e) {
59
- if (!isAbortError(e)) {
60
- message.error(e instanceof Error ? e.message : "Load failed");
61
- }
62
- } finally {
63
- if (!signal?.aborted) setLoading(false);
64
- }
65
- },
66
- [dp, resource, id, isNew, form, message, defaultValues],
67
- );
68
-
69
- useAbortableEffect((signal) => load(signal), [load]);
70
-
71
- return { loading, formVersion };
72
- }
73
-
74
- type SubmitOptions<T extends FieldValues & { id?: unknown }> = {
75
- dp: DataProvider;
76
- resource: string;
77
- id: string | undefined;
78
- isNew: boolean;
79
- form: UseFormReturn<T>;
80
- message: MessageInstance;
81
- navigate: NavigateFunction;
82
- listPath: string;
83
- payloadFieldsRef: MutableRefObject<Set<string>>;
84
- inlineRegistryRef: MutableRefObject<Map<string, InlineFieldRegistration>>;
85
- onSaved?: (record: T) => void;
86
- stayOnPage?: boolean;
87
- };
88
-
89
- /** Save: `buildFormPayload` → clean inline rows → one `create` or `update`. */
90
- export function useFormRecordSave<T extends FieldValues & { id?: unknown }>({
91
- dp,
92
- resource,
93
- id,
94
- isNew,
95
- form,
96
- message,
97
- navigate,
98
- listPath,
99
- payloadFieldsRef,
100
- inlineRegistryRef,
101
- onSaved,
102
- stayOnPage,
103
- }: SubmitOptions<T>) {
104
- return useCallback(
105
- async (values: T) => {
106
- try {
107
- const raw = values as Record<string, unknown>;
108
- const body = buildResourceFormSubmitBody(
109
- raw,
110
- Array.from(payloadFieldsRef.current),
111
- inlineRegistryRef.current.values(),
112
- );
113
-
114
- let saved: T;
115
- if (isNew) {
116
- const res = await dp.create(resource, body);
117
- saved = res.data as T;
118
- message.success("Created");
119
- } else if (id) {
120
- const res = await dp.update(resource, { id, data: body });
121
- saved = res.data as T;
122
- message.success("Updated");
123
- } else {
124
- return;
125
- }
126
-
127
- onSaved?.(saved);
128
- if (!stayOnPage) navigate(listPath);
129
- } catch (e) {
130
- const inlineFieldPaths = Array.from(inlineRegistryRef.current.keys());
131
- const handled = await applyApiErrorsToForm(dp, form, message, e, {
132
- resource,
133
- mutation: isNew ? "create" : "update",
134
- inlineFieldPaths,
135
- });
136
- if (!handled) {
137
- message.error(e instanceof Error ? e.message : "Save failed");
138
- }
139
- }
140
- },
141
- [
142
- dp,
143
- resource,
144
- id,
145
- isNew,
146
- form,
147
- message,
148
- navigate,
149
- listPath,
150
- payloadFieldsRef,
151
- inlineRegistryRef,
152
- onSaved,
153
- stayOnPage,
154
- ],
155
- );
156
- }
1
+ import type { MessageInstance } from "antd/es/message/interface";
2
+ import {
3
+ useCallback,
4
+ useState,
5
+ type MutableRefObject,
6
+ } from "react";
7
+ import type { DataProvider } from "../../data/dataProviderTypes";
8
+ import { isAbortError } from "../../data/abortError";
9
+ import {
10
+ type DefaultValues,
11
+ type FieldValues,
12
+ type UseFormReturn,
13
+ } from "react-hook-form";
14
+ import type { InlineFieldRegistration } from "../context/InlineFieldsRegistry";
15
+ import { buildResourceFormSubmitBody } from "./buildResourceFormSubmitBody";
16
+ import { applyApiErrorsToForm } from "./formErrors";
17
+ import { useAbortableEffect } from "./useAbortableEffect";
18
+
19
+ type LoadOptions<T extends FieldValues> = {
20
+ dp: DataProvider;
21
+ resource: string;
22
+ id: string | undefined;
23
+ isNew: boolean;
24
+ form: UseFormReturn<T>;
25
+ message: MessageInstance;
26
+ defaultValues?: Partial<T>;
27
+ /** When false, skip loading (e.g. modal closed). Defaults to true. */
28
+ enabled?: boolean;
29
+ };
30
+
31
+ /** Edit mode: `getOne` → `form.reset` → bump `formVersion` so field arrays remount. */
32
+ export function useFormRecordLoad<T extends FieldValues>({
33
+ dp,
34
+ resource,
35
+ id,
36
+ isNew,
37
+ form,
38
+ message,
39
+ defaultValues,
40
+ enabled = true,
41
+ }: LoadOptions<T>) {
42
+ const [loading, setLoading] = useState(!isNew);
43
+ const [formVersion, setFormVersion] = useState(0);
44
+
45
+ const load = useCallback(
46
+ async (signal?: AbortSignal) => {
47
+ if (isNew || !id) {
48
+ if (defaultValues) {
49
+ form.reset({ ...defaultValues } as DefaultValues<T>);
50
+ } else {
51
+ form.reset({} as DefaultValues<T>);
52
+ }
53
+ setLoading(false);
54
+ return;
55
+ }
56
+ setLoading(true);
57
+ try {
58
+ const res = await dp.getOne(resource, id, { signal });
59
+ if (signal?.aborted) return;
60
+ form.reset(res.data as DefaultValues<T>);
61
+ setFormVersion((v) => v + 1);
62
+ } catch (e) {
63
+ if (!isAbortError(e)) {
64
+ message.error(e instanceof Error ? e.message : "Load failed");
65
+ }
66
+ } finally {
67
+ if (!signal?.aborted) setLoading(false);
68
+ }
69
+ },
70
+ [dp, resource, id, isNew, form, message, defaultValues],
71
+ );
72
+
73
+ useAbortableEffect(
74
+ (signal) => {
75
+ if (!enabled) return;
76
+ return load(signal);
77
+ },
78
+ [enabled, load],
79
+ );
80
+
81
+ return { loading, formVersion };
82
+ }
83
+
84
+ type SubmitOptions<T extends FieldValues & { id?: unknown }> = {
85
+ dp: DataProvider;
86
+ resource: string;
87
+ id: string | undefined;
88
+ isNew: boolean;
89
+ form: UseFormReturn<T>;
90
+ message: MessageInstance;
91
+ payloadFieldsRef: MutableRefObject<Set<string>>;
92
+ inlineRegistryRef: MutableRefObject<Map<string, InlineFieldRegistration>>;
93
+ setGlobalErrors: (errors: string[]) => void;
94
+ onSuccess?: (record: T) => void;
95
+ };
96
+
97
+ /** Save: `buildFormPayload` → clean inline rows → one `create` or `update`. */
98
+ export function useFormRecordSave<T extends FieldValues & { id?: unknown }>({
99
+ dp,
100
+ resource,
101
+ id,
102
+ isNew,
103
+ form,
104
+ message,
105
+ payloadFieldsRef,
106
+ inlineRegistryRef,
107
+ setGlobalErrors,
108
+ onSuccess,
109
+ }: SubmitOptions<T>) {
110
+ const [saving, setSaving] = useState(false);
111
+
112
+ const onSubmit = useCallback(
113
+ async (values: T) => {
114
+ setGlobalErrors([]);
115
+ setSaving(true);
116
+ try {
117
+ const raw = values as Record<string, unknown>;
118
+ const body = buildResourceFormSubmitBody(
119
+ raw,
120
+ Array.from(payloadFieldsRef.current),
121
+ inlineRegistryRef.current.values(),
122
+ );
123
+
124
+ let saved: T;
125
+ if (isNew) {
126
+ const res = await dp.create(resource, body);
127
+ saved = res.data as T;
128
+ message.success("Created");
129
+ } else if (id) {
130
+ const res = await dp.update(resource, { id, data: body });
131
+ saved = res.data as T;
132
+ message.success("Updated");
133
+ } else {
134
+ return;
135
+ }
136
+
137
+ onSuccess?.(saved);
138
+ } catch (e) {
139
+ const { handled, globalErrors } = await applyApiErrorsToForm(
140
+ dp,
141
+ form,
142
+ e,
143
+ {
144
+ resource,
145
+ mutation: isNew ? "create" : "update",
146
+ inlineFieldPaths: Array.from(inlineRegistryRef.current.keys()),
147
+ },
148
+ {
149
+ payloadFields: payloadFieldsRef.current,
150
+ inlineRegistry: inlineRegistryRef.current.values(),
151
+ },
152
+ );
153
+ if (handled) {
154
+ setGlobalErrors(globalErrors);
155
+ message.error(globalErrors[0] ?? "Save failed.");
156
+ } else {
157
+ setGlobalErrors([]);
158
+ message.error(e instanceof Error ? e.message : "Save failed");
159
+ }
160
+ } finally {
161
+ setSaving(false);
162
+ }
163
+ },
164
+ [
165
+ dp,
166
+ resource,
167
+ id,
168
+ isNew,
169
+ form,
170
+ message,
171
+ payloadFieldsRef,
172
+ inlineRegistryRef,
173
+ setGlobalErrors,
174
+ onSuccess,
175
+ ],
176
+ );
177
+
178
+ return { onSubmit, saving };
179
+ }