ding-react-admin 1.0.2 → 2.0.0

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 (149) hide show
  1. package/README.md +38 -13
  2. package/dist/index.js +1317 -1060
  3. package/dist/src/crud/InlineFormSet.d.ts +12 -25
  4. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  5. package/dist/src/crud/ResourceForm.d.ts +7 -4
  6. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  7. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  8. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  9. package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
  10. package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
  11. package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
  12. package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
  13. package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
  14. package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
  15. package/dist/src/crud/fields/BooleanField.d.ts +3 -1
  16. package/dist/src/crud/fields/BooleanField.d.ts.map +1 -1
  17. package/dist/src/crud/fields/DateField.d.ts +3 -1
  18. package/dist/src/crud/fields/DateField.d.ts.map +1 -1
  19. package/dist/src/crud/fields/FieldWrapper.d.ts +7 -14
  20. package/dist/src/crud/fields/FieldWrapper.d.ts.map +1 -1
  21. package/dist/src/crud/fields/FileField.d.ts +12 -0
  22. package/dist/src/crud/fields/FileField.d.ts.map +1 -0
  23. package/dist/src/crud/fields/ImageField.d.ts +14 -0
  24. package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
  25. package/dist/src/crud/fields/NumberField.d.ts +5 -3
  26. package/dist/src/crud/fields/NumberField.d.ts.map +1 -1
  27. package/dist/src/crud/fields/PasswordField.d.ts +5 -3
  28. package/dist/src/crud/fields/PasswordField.d.ts.map +1 -1
  29. package/dist/src/crud/fields/ReferenceField.d.ts +6 -5
  30. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  31. package/dist/src/crud/fields/ReferenceManyField.d.ts +3 -1
  32. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  33. package/dist/src/crud/fields/SelectField.d.ts +3 -1
  34. package/dist/src/crud/fields/SelectField.d.ts.map +1 -1
  35. package/dist/src/crud/fields/TextField.d.ts +5 -3
  36. package/dist/src/crud/fields/TextField.d.ts.map +1 -1
  37. package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
  38. package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
  39. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
  40. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
  41. package/dist/src/crud/index.d.ts +26 -9
  42. package/dist/src/crud/index.d.ts.map +1 -1
  43. package/dist/src/crud/types.d.ts +23 -20
  44. package/dist/src/crud/types.d.ts.map +1 -1
  45. package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
  46. package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
  47. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
  48. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
  49. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
  50. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
  51. package/dist/src/crud/utils/formErrors.d.ts +1 -1
  52. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  53. package/dist/src/crud/utils/getFormValue.d.ts +3 -0
  54. package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
  55. package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
  56. package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
  57. package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
  58. package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
  59. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +7 -0
  60. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
  61. package/dist/src/crud/utils/setFormValue.d.ts +3 -0
  62. package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
  63. package/dist/src/crud/utils/toFormData.d.ts +13 -0
  64. package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
  65. package/dist/src/crud/utils/useAbortableEffect.d.ts +7 -0
  66. package/dist/src/crud/utils/useAbortableEffect.d.ts.map +1 -0
  67. package/dist/src/crud/utils/useFormRecord.d.ts +42 -0
  68. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
  69. package/dist/src/data/abortError.d.ts +3 -0
  70. package/dist/src/data/abortError.d.ts.map +1 -0
  71. package/dist/src/data/abortError.test.d.ts +2 -0
  72. package/dist/src/data/abortError.test.d.ts.map +1 -0
  73. package/dist/src/data/createRestResourceHandlers.d.ts +6 -6
  74. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
  75. package/dist/src/data/dataProviderTypes.d.ts +13 -6
  76. package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
  77. package/dist/src/data/parseFormErrorHelpers.d.ts +44 -6
  78. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  79. package/dist/src/data/resourceHandlers.d.ts +3 -3
  80. package/dist/src/data/resourceHandlers.d.ts.map +1 -1
  81. package/dist/src/index.d.ts +5 -4
  82. package/dist/src/index.d.ts.map +1 -1
  83. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  84. package/package.json +59 -59
  85. package/src/crud/InlineFormSet.tsx +90 -159
  86. package/src/crud/ResourceForm.tsx +83 -204
  87. package/src/crud/ResourceFormModal.tsx +57 -45
  88. package/src/crud/ResourceList.tsx +36 -26
  89. package/src/crud/columns/ImageColumn.tsx +58 -0
  90. package/src/crud/columns/TextColumn.tsx +2 -2
  91. package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
  92. package/src/crud/context/PayloadFieldsContext.tsx +62 -0
  93. package/src/crud/fields/BooleanField.tsx +22 -16
  94. package/src/crud/fields/DateField.tsx +32 -26
  95. package/src/crud/fields/FieldWrapper.tsx +87 -109
  96. package/src/crud/fields/FileField.tsx +123 -0
  97. package/src/crud/fields/ImageField.tsx +120 -0
  98. package/src/crud/fields/NumberField.tsx +36 -34
  99. package/src/crud/fields/PasswordField.tsx +41 -40
  100. package/src/crud/fields/ReferenceField.tsx +35 -130
  101. package/src/crud/fields/ReferenceManyField.tsx +31 -25
  102. package/src/crud/fields/SelectField.tsx +6 -0
  103. package/src/crud/fields/TextField.tsx +32 -30
  104. package/src/crud/fields/uploadFieldUtils.ts +21 -0
  105. package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
  106. package/src/crud/index.ts +35 -14
  107. package/src/crud/types.ts +134 -130
  108. package/src/crud/utils/buildFormPayload.ts +24 -0
  109. package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
  110. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
  111. package/src/crud/utils/formErrors.ts +42 -38
  112. package/src/crud/utils/{getByPath.ts → getFormValue.ts} +2 -2
  113. package/src/crud/utils/hasUploadValues.ts +9 -0
  114. package/src/crud/utils/nestedFieldPath.ts +8 -0
  115. package/src/crud/utils/prepareFormSubmitBody.ts +17 -0
  116. package/src/crud/utils/{setByPath.ts → setFormValue.ts} +2 -2
  117. package/src/crud/utils/toFormData.ts +81 -0
  118. package/src/crud/utils/useAbortableEffect.ts +17 -0
  119. package/src/crud/utils/useFormRecord.ts +156 -0
  120. package/src/data/abortError.test.ts +19 -0
  121. package/src/data/abortError.ts +10 -0
  122. package/src/data/createMemoryResourceHandlers.ts +1 -1
  123. package/src/data/createRestResourceHandlers.ts +22 -16
  124. package/src/data/dataProviderTypes.ts +15 -5
  125. package/src/data/dataUtils.test.ts +118 -6
  126. package/src/data/parseFormErrorHelpers.test.ts +138 -84
  127. package/src/data/parseFormErrorHelpers.ts +311 -224
  128. package/src/data/resourceHandlers.ts +5 -4
  129. package/src/index.ts +34 -9
  130. package/src/layouts/AdminLayout.tsx +2 -1
  131. package/dist/src/crud/context/FormFieldsContext.d.ts +0 -14
  132. package/dist/src/crud/context/FormFieldsContext.d.ts.map +0 -1
  133. package/dist/src/crud/context/InlineFormContext.d.ts +0 -16
  134. package/dist/src/crud/context/InlineFormContext.d.ts.map +0 -1
  135. package/dist/src/crud/fields/useInlineOrFormField.d.ts +0 -10
  136. package/dist/src/crud/fields/useInlineOrFormField.d.ts.map +0 -1
  137. package/dist/src/crud/utils/getByPath.d.ts +0 -3
  138. package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
  139. package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
  140. package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
  141. package/dist/src/crud/utils/pickBySources.d.ts +0 -6
  142. package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
  143. package/dist/src/crud/utils/setByPath.d.ts +0 -3
  144. package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
  145. package/src/crud/context/FormFieldsContext.tsx +0 -76
  146. package/src/crud/context/InlineFormContext.tsx +0 -71
  147. package/src/crud/fields/useInlineOrFormField.tsx +0 -91
  148. package/src/crud/utils/inlineArrayName.ts +0 -4
  149. package/src/crud/utils/pickBySources.ts +0 -24
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "ding-react-admin",
3
- "version": "1.0.2",
4
- "type": "module",
5
- "license": "MIT",
6
- "description": "Composable admin shell (Ant Design + React Router): quick-start AdminApp or build your own layout.",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/Muhammadinaam/ding-react-admin.git"
10
- },
11
- "main": "./dist/index.js",
12
- "module": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
- "exports": {
15
- ".": {
16
- "types": "./dist/index.d.ts",
17
- "import": "./dist/index.js"
18
- }
19
- },
20
- "files": [
21
- "dist",
22
- "src",
23
- "docs/assets"
24
- ],
25
- "scripts": {
26
- "build": "vite build && node -e \"require('fs').writeFileSync('dist/index.d.ts','export * from \\\"./src/index\\\";\\n')\"",
27
- "dev:example": "yarn --cwd examples/playground dev",
28
- "build:example": "yarn --cwd examples/playground build",
29
- "build:example:gh-pages": "yarn --cwd examples/playground build:gh-pages",
30
- "test": "vitest run",
31
- "prepublishOnly": "yarn build"
32
- },
33
- "peerDependencies": {
34
- "@ant-design/icons": ">=6",
35
- "antd": ">=6",
36
- "dayjs": ">=1",
37
- "react": ">=18",
38
- "react-dom": ">=18",
39
- "react-hook-form": ">=7",
40
- "react-router-dom": ">=6"
41
- },
42
- "devDependencies": {
43
- "@ant-design/icons": "^6.0.0",
44
- "@types/react": "^19.2.14",
45
- "@types/react-dom": "^19.2.3",
46
- "@vitejs/plugin-react": "^6.0.1",
47
- "antd": "^6.0.0",
48
- "dayjs": "^1.11.13",
49
- "react": "^19.2.5",
50
- "react-dom": "^19.2.5",
51
- "react-hook-form": "^7.56.4",
52
- "react-router-dom": "^7.14.2",
53
- "typescript": "~6.0.2",
54
- "vite": "^8.0.9",
55
- "vite-plugin-dts": "^4.5.4",
56
- "vitest": "^3.2.4"
57
- },
58
- "dependencies": {}
59
- }
1
+ {
2
+ "name": "ding-react-admin",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Composable admin shell (Ant Design + React Router): quick-start AdminApp or build your own layout.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Muhammadinaam/ding-react-admin.git"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "docs/assets"
24
+ ],
25
+ "scripts": {
26
+ "build": "vite build && node -e \"require('fs').writeFileSync('dist/index.d.ts','export * from \\\"./src/index\\\";\\n')\"",
27
+ "dev:example": "yarn --cwd examples/playground dev",
28
+ "build:example": "yarn --cwd examples/playground build",
29
+ "build:example:gh-pages": "yarn --cwd examples/playground build:gh-pages",
30
+ "test": "vitest run",
31
+ "prepublishOnly": "yarn build"
32
+ },
33
+ "peerDependencies": {
34
+ "@ant-design/icons": ">=6",
35
+ "antd": ">=6",
36
+ "dayjs": ">=1",
37
+ "react": ">=18",
38
+ "react-dom": ">=18",
39
+ "react-hook-form": ">=7",
40
+ "react-router-dom": ">=6"
41
+ },
42
+ "devDependencies": {
43
+ "@ant-design/icons": "^6.0.0",
44
+ "@types/react": "^19.2.14",
45
+ "@types/react-dom": "^19.2.3",
46
+ "@vitejs/plugin-react": "^6.0.1",
47
+ "antd": "^6.0.0",
48
+ "dayjs": "^1.11.13",
49
+ "react": "^19.2.5",
50
+ "react-dom": "^19.2.5",
51
+ "react-hook-form": "^7.56.4",
52
+ "react-router-dom": "^7.14.2",
53
+ "typescript": "~6.0.2",
54
+ "vite": "^8.0.9",
55
+ "vite-plugin-dts": "^4.5.4",
56
+ "vitest": "^3.2.4"
57
+ },
58
+ "dependencies": {}
59
+ }
@@ -1,72 +1,82 @@
1
1
  import { Button, Card, Space, Table, Typography } from "antd";
2
2
  import { useFieldArray, useFormContext, type FieldValues } from "react-hook-form";
3
3
  import { useMemo, type ReactNode } from "react";
4
- import {
5
- InlineFormSetProvider,
6
- useInlineFormSetContext,
7
- type InlineFormSetLayout,
8
- } from "./context/InlineFormContext";
9
- import type { Identifier } from "../data/dataProviderTypes";
10
- import type { DataProvider } from "../data/dataProviderTypes";
11
- import { inlineArrayName as buildInlineArrayName } from "./utils/inlineArrayName";
12
-
13
- export type { InlineFormSetLayout } from "./context/InlineFormContext";
4
+ import type {
5
+ InlineColumnDef,
6
+ InlineFormSetBaseProps,
7
+ InlineRowContext,
8
+ } from "./types";
9
+ import { nestedFieldPath } from "./utils/nestedFieldPath";
10
+ import { useRegisterInlineField } from "./context/InlineFieldsRegistry";
11
+ import { useRegisterPayloadField } from "./context/PayloadFieldsContext";
12
+
13
+ export type InlineFormSetLayout = "tabular" | "stacked";
14
+
15
+ export type InlineFormSetProps = InlineFormSetBaseProps & {
16
+ columns: InlineColumnDef[];
17
+ };
14
18
 
15
- export type InlineFormSetProps = {
16
- resource: string;
17
- foreignKey: string;
18
- label?: string;
19
- children: ReactNode;
20
- name?: string;
21
- /** `tabular` (default) — Django TabularInline; `stacked` — Django StackedInline. */
22
- layout?: InlineFormSetLayout;
19
+ export type InlineFormSetStackedProps = InlineFormSetBaseProps & {
20
+ /** Field sources in each row — used when appending an empty row. */
21
+ sources: string[];
22
+ renderRow: (ctx: InlineRowContext) => ReactNode;
23
23
  };
24
24
 
25
- function useInlineRows(arrayName: string) {
26
- const ctx = useInlineFormSetContext();
25
+ function emptyRow(sources: string[]) {
26
+ const row: Record<string, unknown> = {};
27
+ for (const source of sources) row[source] = undefined;
28
+ return row;
29
+ }
30
+
31
+ function useInlineRows(field: string, sources: string[]) {
27
32
  const { control } = useFormContext<FieldValues>();
28
33
  const { fields, append, remove } = useFieldArray({
29
34
  control,
30
- name: arrayName,
35
+ name: field,
36
+ keyName: "rowKey",
31
37
  });
32
38
 
33
- const appendEmpty = () => {
34
- if (!ctx) return;
35
- const empty: Record<string, unknown> = {};
36
- for (const f of ctx.fields) empty[f.source] = undefined;
37
- append(empty);
38
- };
39
+ const appendEmpty = () => append(emptyRow(sources));
39
40
 
40
- return { ctx, fields, remove, appendEmpty };
41
+ return { fields, remove, appendEmpty };
41
42
  }
42
43
 
43
- function InlineFormSetTable({
44
- arrayName,
44
+ /** Tabular inline (Django TabularInline) — table with column headers. */
45
+ export function InlineFormSet({
46
+ field,
45
47
  label,
46
- }: {
47
- arrayName: string;
48
- label?: string;
49
- }) {
50
- const { ctx, fields, remove, appendEmpty } = useInlineRows(arrayName);
51
-
52
- const columns = useMemo(() => {
53
- if (!ctx) return [];
54
- return ctx.fields.map((f) => ({
55
- title: f.label ?? f.source,
56
- key: f.source,
57
- width: f.width,
58
- onHeaderCell: () =>
59
- f.minWidth != null
60
- ? { style: { minWidth: f.minWidth } }
61
- : {},
62
- onCell: () =>
63
- f.minWidth != null ? { style: { minWidth: f.minWidth } } : {},
64
- render: (_: unknown, __: unknown, index: number) =>
65
- f.render({ name: `${arrayName}.${index}.${f.source}`, index }),
66
- }));
67
- }, [ctx, arrayName]);
68
-
69
- if (!ctx) return null;
48
+ payloadKey,
49
+ transformRows,
50
+ columns,
51
+ }: InlineFormSetProps) {
52
+ const sources = useMemo(
53
+ () => columns.map((col) => col.source),
54
+ [columns],
55
+ );
56
+ const { fields, remove, appendEmpty } = useInlineRows(field, sources);
57
+
58
+ useRegisterPayloadField(field);
59
+ useRegisterInlineField(field, sources, payloadKey, transformRows);
60
+
61
+ const tableColumns = useMemo(
62
+ () =>
63
+ columns.map((col) => ({
64
+ title: col.label ?? col.source,
65
+ key: col.source,
66
+ width: col.width,
67
+ onHeaderCell: () =>
68
+ col.minWidth != null ? { style: { minWidth: col.minWidth } } : {},
69
+ onCell: () =>
70
+ col.minWidth != null ? { style: { minWidth: col.minWidth } } : {},
71
+ render: (_: unknown, __: unknown, index: number) =>
72
+ col.cell({
73
+ name: nestedFieldPath(field, index, col.source),
74
+ index,
75
+ field,
76
+ }),
77
+ })),
78
+ [columns, field],
79
+ );
70
80
 
71
81
  return (
72
82
  <div style={{ marginTop: 24 }}>
@@ -75,9 +85,9 @@ function InlineFormSetTable({
75
85
  size="small"
76
86
  pagination={false}
77
87
  scroll={{ x: "max-content" }}
78
- dataSource={fields.map((f) => ({ ...f, key: f.id }))}
88
+ dataSource={fields.map((f) => ({ ...f, key: f.rowKey }))}
79
89
  columns={[
80
- ...columns,
90
+ ...tableColumns,
81
91
  {
82
92
  title: "",
83
93
  key: "__remove",
@@ -102,40 +112,45 @@ function InlineFormSetTable({
102
112
  );
103
113
  }
104
114
 
105
- function InlineFormSetStacked({
106
- arrayName,
115
+ /** Stacked inline (Django StackedInline) — each row in a card with field labels. */
116
+ export function InlineFormSetStacked({
117
+ field,
107
118
  label,
108
- }: {
109
- arrayName: string;
110
- label?: string;
111
- }) {
112
- const { ctx, fields, remove, appendEmpty } = useInlineRows(arrayName);
119
+ payloadKey,
120
+ transformRows,
121
+ sources,
122
+ renderRow,
123
+ }: InlineFormSetStackedProps) {
124
+ const { fields, remove, appendEmpty } = useInlineRows(field, sources);
113
125
 
114
- if (!ctx) return null;
126
+ useRegisterPayloadField(field);
127
+ useRegisterInlineField(field, sources, payloadKey, transformRows);
115
128
 
116
129
  return (
117
130
  <div style={{ marginTop: 24 }}>
118
131
  <Typography.Title level={5}>{label ?? "Related items"}</Typography.Title>
119
132
  <Space orientation="vertical" size="middle" style={{ width: "100%" }}>
120
- {fields.map((field, index) => (
133
+ {fields.map((rowField, index) => (
121
134
  <Card
122
- key={field.id}
135
+ key={rowField.rowKey}
123
136
  size="small"
124
137
  title={`Item ${index + 1}`}
125
138
  extra={
126
- <Button type="link" danger size="small" onClick={() => remove(index)}>
139
+ <Button
140
+ type="link"
141
+ danger
142
+ size="small"
143
+ onClick={() => remove(index)}
144
+ >
127
145
  Remove
128
146
  </Button>
129
147
  }
130
148
  >
131
- {ctx.fields.map((f) => (
132
- <div key={f.source}>
133
- {f.render({
134
- name: `${arrayName}.${index}.${f.source}`,
135
- index,
136
- })}
137
- </div>
138
- ))}
149
+ {renderRow({
150
+ field,
151
+ index,
152
+ name: (source) => nestedFieldPath(field, index, source),
153
+ })}
139
154
  </Card>
140
155
  ))}
141
156
  </Space>
@@ -145,87 +160,3 @@ function InlineFormSetStacked({
145
160
  </div>
146
161
  );
147
162
  }
148
-
149
- export function InlineFormSet({
150
- resource,
151
- label,
152
- children,
153
- name,
154
- layout = "tabular",
155
- }: InlineFormSetProps) {
156
- const arrayName = buildInlineArrayName(resource, name);
157
-
158
- return (
159
- <InlineFormSetProvider arrayName={arrayName} layout={layout}>
160
- {children}
161
- {layout === "stacked" ? (
162
- <InlineFormSetStacked arrayName={arrayName} label={label} />
163
- ) : (
164
- <InlineFormSetTable arrayName={arrayName} label={label} />
165
- )}
166
- </InlineFormSetProvider>
167
- );
168
- }
169
-
170
- export type SaveInlineOptions = {
171
- resource: string;
172
- foreignKey: string;
173
- parentId: Identifier;
174
- rows: Record<string, unknown>[];
175
- existingIds?: Identifier[];
176
- /** Return true when the error was handled (field errors applied). Stops saving further rows. */
177
- onRowError?: (error: unknown, index: number) => boolean;
178
- };
179
-
180
- export async function saveInlineRows(
181
- dp: DataProvider,
182
- opts: SaveInlineOptions,
183
- ): Promise<boolean> {
184
- const { resource, foreignKey, parentId, rows, existingIds = [], onRowError } =
185
- opts;
186
- const keptIds: Identifier[] = [];
187
-
188
- for (let index = 0; index < rows.length; index++) {
189
- const row = rows[index];
190
- try {
191
- const { id: _id, ...rest } = row;
192
- const data = { ...rest, [foreignKey]: parentId };
193
- const rowId = row.id as Identifier | undefined;
194
- if (rowId != null && existingIds.some((e) => e === rowId)) {
195
- await dp.update(resource, { id: rowId, data });
196
- keptIds.push(rowId);
197
- } else {
198
- const res = await dp.create(resource, data);
199
- const created = res.data as { id: Identifier };
200
- keptIds.push(created.id);
201
- }
202
- } catch (e) {
203
- if (onRowError?.(e, index)) return false;
204
- throw e;
205
- }
206
- }
207
-
208
- for (const oldId of existingIds) {
209
- if (!keptIds.some((k) => k === oldId)) {
210
- await dp.delete(resource, oldId);
211
- }
212
- }
213
-
214
- return true;
215
- }
216
-
217
- export async function loadInlineRows(
218
- dp: DataProvider,
219
- resource: string,
220
- foreignKey: string,
221
- parentId: Identifier,
222
- ) {
223
- const res = await dp.getList(resource, {
224
- filter: { [foreignKey]: Number(parentId) || parentId },
225
- pagination: { page: 1, perPage: 500 },
226
- });
227
- return {
228
- rows: res.data as Record<string, unknown>[],
229
- ids: (res.data as { id: Identifier }[]).map((r) => r.id),
230
- };
231
- }