react-admin-crud-manager 1.2.0 → 1.2.2
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/README.md +382 -501
- package/dist/index.cjs.js +18 -18
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1580 -1580
- package/dist/index.es.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -38,7 +38,6 @@ function App() {
|
|
|
38
38
|
## Components
|
|
39
39
|
|
|
40
40
|
- `Crud`: Main CRUD page component
|
|
41
|
-
- `Table`, `Modal`, `Form`, etc.: Available for advanced use
|
|
42
41
|
|
|
43
42
|
## Props
|
|
44
43
|
|
|
@@ -58,7 +57,7 @@ Below is a complete reference of the public props accepted by this package (type
|
|
|
58
57
|
| ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
58
|
| `title` | string | Yes | Title of the CRUD page |
|
|
60
59
|
| `description` | string | No | Optional description text |
|
|
61
|
-
| `buttonText` | string | No | Custom text for
|
|
60
|
+
| `buttonText` | string | No | Custom text for add button |
|
|
62
61
|
| `fetchData` | function | Yes | Async function to fetch data. Signature: `async ({ search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => Promise<{ data: Array, pagination?: { current_page: number, rows_per_page: number, total_pages: number, total_records: number } }>`. Component expects `resp.data` (array) and optional `resp.pagination` for server-side pagination. |
|
|
63
62
|
| `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
|
|
64
63
|
| `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
|
|
@@ -72,41 +71,36 @@ Below is a complete reference of the public props accepted by this package (type
|
|
|
72
71
|
|
|
73
72
|
#### Table Configuration Keys
|
|
74
73
|
|
|
75
|
-
| Key
|
|
76
|
-
|
|
|
77
|
-
| `table_head`
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| `onMenuAction` | function | Callback for menu actions (edit/view/delete) | `(actionType: string, item: object) => void`; actionType: `"edit"`, `"view"`, `"delete"` |
|
|
87
|
-
| `setServerSidePaginationData` | function | Update pagination/search state | `(data: { search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => void` |
|
|
88
|
-
| `onFilterApply` | function | Callback when filter applied | `(filters: object) => void` |
|
|
89
|
-
| `filterConfig` | object | Filter drawer form fields | `{ fields: [{ key: "status", type: "select", options: [...] }, ...] }`; uses [Form Field Schema](#form-field-schema) |
|
|
90
|
-
| `rowClick` | function | Callback on table row click | `(row: object, rowIndex: number) => void` |
|
|
74
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
75
|
+
| ------------ | ----------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `table_head` | array of column objects | Column definitions | Array of table column objects (see [Table column object](#table-column-object-table_head)) |
|
|
77
|
+
| `search` | object | Search functionality config | `{ enabled: true, useServerSideSearch?: false, searchKeys?: ["name", "email"] }` |
|
|
78
|
+
| `filter` | object | Filter drawer config | `{ enabled: true, useServerSideFilters?: false }` |
|
|
79
|
+
| `pagination` | object | Pagination controls | `{ enabled: true, useServerSidePagination?: false }` |
|
|
80
|
+
| `sort` | object | Sorting configuration | `{ enabled: true, useServerSideSorting?: false, autoGenerate: true, onChange?: (payload) => void, options?: [...], clearLabel?: "Clear Sort" }` |
|
|
81
|
+
| `exportCSV` | object | Export data as CSV | `{ enabled: true, fileName: "users.csv", fields: [{ label: "Name", key: "name" }, ...] }` |
|
|
82
|
+
| `rowClick` | function or boolean | Callback on table row click | `(row: object, rowIndex: number) => void` or `true` (setting true will open details) |
|
|
83
|
+
|
|
84
|
+
<br>
|
|
91
85
|
|
|
92
86
|
#### Table Column Object (`table_head[]`)
|
|
93
87
|
|
|
94
|
-
| Key | Type | Description
|
|
95
|
-
| ---------------- | -------- |
|
|
96
|
-
| `key` | string | Property name in row objects
|
|
97
|
-
| `title` | string | Column header text
|
|
98
|
-
| `type` | string | Column renderer type
|
|
99
|
-
| `imageKey` | string | Image property for avatar/group types
|
|
100
|
-
| `titleKey` | string | Title property for group/avatar types
|
|
101
|
-
| `subtitleKey` | string | Subtitle property for group/avatar types
|
|
102
|
-
| `onClickDetails` | boolean | Clicking cell opens view details modal
|
|
103
|
-
| `variant` | string | Chip styling variant
|
|
104
|
-
| `chipOptions` | array | Map values to chip labels and colors; array of `{ value: string\|number\|boolean, label: string, color?: string }` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]`
|
|
105
|
-
| `defaultColor` | string | Default color for chips (if no match in chipOptions)
|
|
106
|
-
| `className` | string | Custom CSS class for cell content
|
|
107
|
-
| `format` | string | Date format pattern
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
88
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
89
|
+
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
90
|
+
| `key` | string | Property name in row objects | `"id"`, `"name"`, `"email"` (must exist in data objects) |
|
|
91
|
+
| `title` | string | Column header text | `"User ID"`, `"Full Name"`, `"Email Address"` |
|
|
92
|
+
| `type` | string | Column renderer type | `"plain"` (default), `"index"` (row number), `"group"` (avatar+text), `"chip"` (badge), `"date"`, `"avatar"`, `"menu_actions"` |
|
|
93
|
+
| `imageKey` | string | Image property for avatar/group types | `"profileImage"`, `"avatarUrl"` (path to image in data object) |
|
|
94
|
+
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` (property key in data object) |
|
|
95
|
+
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` (property key in data object) |
|
|
96
|
+
| `onClickDetails` | boolean | Clicking cell opens view details modal | `true`, `false` (default: `false`) |
|
|
97
|
+
| `variant` | string | Chip styling variant | `"contained"`, `"outline"`, `"soft"` (used with `type: "chip"`) |
|
|
98
|
+
| `chipOptions` | array | Map values to chip labels and colors; array of `{ value: string\|number\|boolean, label: string, color?: string }` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
|
|
99
|
+
| `defaultColor` | string | Default color for chips (if no match in chipOptions) | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"`, etc. |
|
|
100
|
+
| `className` | string | Custom CSS class for cell content | Tailwind classes: `"font-bold text-sm text-gray-600"` |
|
|
101
|
+
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` (uses date-fns patterns) |
|
|
102
|
+
| `menuList` | array | Action menu items; array of `{ title: string, type: string, variant?: string, icon?: ReactNode }` | `[{ title: "Edit", type: "edit", icon: <EditIcon /> }, { title: "Delete", type: "delete", icon: <TrashIcon /> }]` |
|
|
103
|
+
| `render` | function | Custom cell renderer (overrides built-in logic) | `(row: object, rowIndex: number) => ReactNode`; e.g., `(row) => <span>{row.name.toUpperCase()}</span>` |
|
|
110
104
|
|
|
111
105
|
---
|
|
112
106
|
|
|
@@ -114,41 +108,40 @@ Below is a complete reference of the public props accepted by this package (type
|
|
|
114
108
|
|
|
115
109
|
#### Add & Edit Modal (`addModal`, `editModal`)
|
|
116
110
|
|
|
117
|
-
| Property | Type
|
|
118
|
-
| --------------- |
|
|
119
|
-
| `title` | string
|
|
120
|
-
| `icon` | ReactNode | No
|
|
121
|
-
| `size` | string
|
|
122
|
-
| `formClass` | string
|
|
123
|
-
| `formFields` | array
|
|
124
|
-
| `handleSubmit` | function
|
|
125
|
-
| `actionButtons` | array
|
|
111
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
112
|
+
| --------------- | --------- | -------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `title` | string | Yes | Modal title | `"Add New User"`, `"Edit User Profile"` |
|
|
114
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<PlusIcon />`, `<EditIcon />` |
|
|
115
|
+
| `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
|
|
116
|
+
| `formClass` | string | No | Custom CSS class for form wrapper | Tailwind classes: `"grid grid-cols-12 gap-4"` |
|
|
117
|
+
| `formFields` | array | Yes | Form field objects | Array of form field objects (see [Form Field Schema](#form-field-schema)) |
|
|
118
|
+
| `handleSubmit` | function | Yes | Async callback on form submission | `async (formData) => Promise<{ newObject, message?: string }>` (add), `async (formData, item) => Promise<{ newObject, targetObject, message?: string }>` (edit) |
|
|
119
|
+
| `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Save", color: "primary", variant: "contained", onClick?: (e, item) => void, disabled?: boolean }]` |
|
|
126
120
|
|
|
127
121
|
#### Delete Modal (`deleteModal`)
|
|
128
122
|
|
|
129
|
-
| Property | Type
|
|
130
|
-
| --------------- |
|
|
131
|
-
| `title` | string
|
|
132
|
-
| `icon` | ReactNode | No
|
|
133
|
-
| `size` | string
|
|
134
|
-
| `confirmText` | string
|
|
135
|
-
| `referenceKey` | string
|
|
136
|
-
| `
|
|
137
|
-
| `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Delete", color: "error", variant: "contained" }]` |
|
|
123
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
124
|
+
| --------------- | --------- | -------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
125
|
+
| `title` | string | No | Modal title | `"Delete User"`, `"Confirm Delete"` |
|
|
126
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<TrashIcon />`, `<WarningIcon />` |
|
|
127
|
+
| `size` | string | No | Modal width | `"sm"` (default), `"md"`, `"lg"`, `"xl"`, `"full"` |
|
|
128
|
+
| `confirmText` | string | No | Confirmation message text | `"Are you sure you want to delete this user?"`, `"This action cannot be undone."` |
|
|
129
|
+
| `referenceKey` | string | No | Property key to display as confirmation reference | `"name"`, `"email"` (shows the value from selected item) |
|
|
130
|
+
| `actionButtons` | array | No | Custom action buttons | `[{ type: "button", label: "Delete", color: "error", variant: "contained", onClick: async (event , selectedItem) => Promise<{ targetObject }>},... ]` |
|
|
138
131
|
|
|
139
132
|
#### View Modal (`viewModal`)
|
|
140
133
|
|
|
141
|
-
| Property
|
|
142
|
-
|
|
|
143
|
-
| `title`
|
|
144
|
-
| `icon`
|
|
145
|
-
| `size`
|
|
146
|
-
| `component`
|
|
147
|
-
| `variant`
|
|
148
|
-
| `fields`
|
|
149
|
-
| `styles`
|
|
150
|
-
| `modalClassNames` | object
|
|
151
|
-
| `footer`
|
|
134
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
135
|
+
| ----------------- | --------------- | -------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| `title` | string | Yes | Modal title | `"User Details"`, `"View Profile"` |
|
|
137
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<EyeIcon />`, `<InfoIcon />` |
|
|
138
|
+
| `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
|
|
139
|
+
| `component` | React component | No | Custom component to render (receives `data` prop) | `(props) => <CustomViewComponent data={props.data} />` |
|
|
140
|
+
| `variant` | string | No | View layout style | `"default"`, `"card"`, `"split"` |
|
|
141
|
+
| `fields` | array | No | View field objects (if not using custom component) | Array of field objects (see [View Field Schema](#view-field-schema)) |
|
|
142
|
+
| `styles` | object | No | Custom CSS classes for various view elements | `{ containerClass: "...", rowClass: "...", labelClass: "...", valueClass: "...", ... }` |
|
|
143
|
+
| `modalClassNames` | object | No | Custom classes for modal structure | `{ overlay: "...", container: "...", header: "...", body: "...", footer: "...", closeButton: "..." }` |
|
|
144
|
+
| `footer` | object | No | Footer configuration | `{ cancelButton: true, cancelText: "Close" }` |
|
|
152
145
|
|
|
153
146
|
---
|
|
154
147
|
|
|
@@ -158,136 +151,116 @@ Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields
|
|
|
158
151
|
|
|
159
152
|
#### Common Field Properties (All Types)
|
|
160
153
|
|
|
161
|
-
| Key
|
|
162
|
-
|
|
|
163
|
-
| `key`
|
|
164
|
-
| `label`
|
|
165
|
-
| `type`
|
|
166
|
-
| `required`
|
|
167
|
-
| `minLength`
|
|
168
|
-
| `placeholder`
|
|
169
|
-
| `disabled`
|
|
170
|
-
| `parentClass`
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `pattern` | string | No | Regex pattern for validation (HTML5) | Regex: `"^[a-zA-Z0-9]*$"`, `"^[0-9]{10}$"` |
|
|
175
|
-
| `className` | string | No | Custom CSS class for input element | Tailwind classes: `"bg-gray-100 rounded-lg"` |
|
|
154
|
+
| Key | Type | Required | Description | Accepted Values / Example |
|
|
155
|
+
| ------------------ | -------- | -------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `key` | string | Yes | Property name/identifier for form data | `"username"`, `"email"`, `"birth_date"` |
|
|
157
|
+
| `label` | string | No | Human-readable label for the field | `"User Name"`, `"Email Address"`, `"Date of Birth"` |
|
|
158
|
+
| `type` | string | Yes | Field type determining the input/renderer | `"text"`, `"number"`, `"email"`, `"password"`, `"select"`, `"checkbox"`, `"radio"`, `"switch"`, `"phone"`, `"textarea"`, `"image"`, `"video"`, `"audio"`, `"tinyEditor"`, `"group"` |
|
|
159
|
+
| `required` | boolean | No | Field must have a value for form submission | `true`, `false` (default: `false`) |
|
|
160
|
+
| `minLength` | number | No | Minimum character length (for text fields) | `5`, `10`, `50` |
|
|
161
|
+
| `placeholder` | string | No | Placeholder text shown in empty field | `"Enter your name"`, `"user@example.com"` |
|
|
162
|
+
| `disabled` | boolean | No | Field is disabled and read-only | `true`, `false` (default: `false`) |
|
|
163
|
+
| `parentClass` | string | No | Custom CSS classes for field wrapper (grid) | Tailwind classes: `"col-span-6"`, `"col-span-12"` |
|
|
164
|
+
| `renderCondition` | function | No | Show field based on form data | `(formData: Record<string, any>) => boolean`; e.g., `(data) => data.userType === 'admin'` |
|
|
165
|
+
| `customValidation` | function | No | Custom validation logic | `(value: any) => boolean \| string`; return `false` for invalid, error message string, or `true` for valid |
|
|
166
|
+
| `className` | string | No | Custom CSS class for input element | Tailwind classes: `"bg-gray-100 rounded-lg"` |
|
|
176
167
|
|
|
177
168
|
#### Type-Specific Properties
|
|
178
169
|
|
|
179
170
|
##### `"text"` Field
|
|
180
171
|
|
|
181
|
-
| Property
|
|
182
|
-
|
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `mask` | string | Input mask pattern | `"(99) 99999-9999"` (format: 9=digit, A=letter, X=alphanumeric, *=any char) |
|
|
187
|
-
| `maskApplyOnValue` | boolean | Apply mask to initial value | `true`, `false` (default: `true`) |
|
|
172
|
+
| Property | Type | Description | Example |
|
|
173
|
+
| ------------------ | ------- | ------------------------- | ---------------------------------------------------------------------------- |
|
|
174
|
+
| `pattern` | string | Regex validation pattern | `"^[a-zA-Z ]*$"` (letters and spaces only) |
|
|
175
|
+
| `mask` | string | Input mask pattern | `"(99) 99999-9999"` (format: 9=digit, A=letter, X=alphanumeric, \*=any char) |
|
|
176
|
+
| `maskApplyOnValue` | boolean | Apply mask formData value | `true`, `false` (default: `true`) |
|
|
188
177
|
|
|
189
178
|
##### `"number"` Field
|
|
190
179
|
|
|
191
|
-
| Property
|
|
192
|
-
|
|
|
193
|
-
| `negativeNumberAllow` | boolean | Allow negative numbers
|
|
194
|
-
|
|
195
|
-
##### `"email"` Field
|
|
196
|
-
|
|
197
|
-
| Property | Type | Description | Example |
|
|
198
|
-
| ------ | ------- | -------------------- | --- |
|
|
199
|
-
| `placeholder` | string | Placeholder text | `"user@example.com"` |
|
|
200
|
-
|
|
201
|
-
##### `"password"` Field
|
|
202
|
-
|
|
203
|
-
| Property | Type | Description | Example |
|
|
204
|
-
| --- | --- | -- | -- |
|
|
205
|
-
| `minLength` | number | Minimum character length | `8` |
|
|
180
|
+
| Property | Type | Description | Example |
|
|
181
|
+
| --------------------- | ------- | ---------------------- | ---------------------------------- |
|
|
182
|
+
| `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
|
|
206
183
|
|
|
207
184
|
##### `"select"` Field
|
|
208
185
|
|
|
209
|
-
| Property
|
|
210
|
-
|
|
|
211
|
-
| `options`
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
186
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
187
|
+
| ------------------- | ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
188
|
+
| `options` | array | Select options; `{ value, label, color? }[]` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
|
|
189
|
+
| `countriesList` | boolean | Country selector dropdown | `true`, `false` (default: `false`) |
|
|
190
|
+
| `multiple` | boolean | Allow selecting multiple options | `true`, `false` (default: `false`) |
|
|
191
|
+
| `search` | boolean | Enable searchable dropdown | `true`, `false` (default: `false`) |
|
|
192
|
+
| `dropdownMaxHeight` | number | Max height of dropdown in pixels | `300`, `400` |
|
|
193
|
+
| `defaultValue` | any | Initial value for the field | `"John"`, `25`, `true`, `["option1", "option2"]` |
|
|
215
194
|
|
|
216
195
|
##### `"checkbox"` Field
|
|
217
196
|
|
|
218
|
-
| Property
|
|
219
|
-
|
|
|
220
|
-
| `options` | array | Checkbox options; `{ value, label }[]`
|
|
221
|
-
| `multiple` | boolean | Allow selecting multiple values
|
|
197
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
198
|
+
| ---------- | ------- | -------------------------------------- | -------------------------------------------------------------------------------- |
|
|
199
|
+
| `options` | array | Checkbox options; `{ value, label }[]` | `[{ value: "read", label: "Can Read" }, { value: "write", label: "Can Write" }]` |
|
|
200
|
+
| `multiple` | boolean | Allow selecting multiple values | `true` (default: `true`); affects how data is stored |
|
|
222
201
|
|
|
223
202
|
##### `"radio"` Field
|
|
224
203
|
|
|
225
|
-
| Property | Type
|
|
226
|
-
|
|
|
227
|
-
| `options`
|
|
204
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
205
|
+
| --------- | ----- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| `options` | array | Radio button options; `{ value, label }[]` | `[{ value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "other", label: "Other" }]` |
|
|
228
207
|
|
|
229
208
|
##### `"switch"` Field
|
|
230
209
|
|
|
231
|
-
| Property | Type
|
|
232
|
-
|
|
|
233
|
-
| `text` | string
|
|
234
|
-
| `options` | array
|
|
210
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
211
|
+
| --------- | ------ | -------------------------------------------- | ---------------------------------------------------------------- |
|
|
212
|
+
| `text` | string | Description/label shown next to switch | `"Enable notifications"`, `"Is admin"` |
|
|
213
|
+
| `options` | array | Optional radio-like options (radio fallback) | `[{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]` |
|
|
235
214
|
|
|
236
215
|
##### `"phone"` Field
|
|
237
216
|
|
|
238
|
-
| Property
|
|
239
|
-
|
|
|
240
|
-
| `countriesList`
|
|
241
|
-
| `defaultCountry` | string
|
|
242
|
-
| `search`
|
|
243
|
-
| `placeholder`
|
|
217
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
218
|
+
| ---------------- | ------- | -------------------------------------- | -------------------------------------- |
|
|
219
|
+
| `countriesList` | boolean | Show country selector dropdown | `true`, `false` (default: `false`) |
|
|
220
|
+
| `defaultCountry` | string | Default country ISO code | `"US"`, `"GB"`, `"IN"`, `"CA"`, `"AU"` |
|
|
221
|
+
| `search` | boolean | Enable searching countries in selector | `true`, `false` (default: `false`) |
|
|
222
|
+
| `placeholder` | string | Placeholder text | `"+1 (555) 000-0000"` |
|
|
244
223
|
|
|
245
224
|
##### `"textarea"` Field
|
|
246
225
|
|
|
247
|
-
| Property
|
|
248
|
-
|
|
|
249
|
-
| `rows`
|
|
226
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
227
|
+
| -------- | ------ | ---------------------- | ------------------------- |
|
|
228
|
+
| `rows` | number | Number of visible rows | `3`, `5`, `10` |
|
|
250
229
|
|
|
251
230
|
##### `"image"` Field
|
|
252
231
|
|
|
253
|
-
| Property
|
|
254
|
-
|
|
|
255
|
-
| `accept`
|
|
256
|
-
| `dragDrop`
|
|
257
|
-
| `cropImage`
|
|
258
|
-
| `aspectRatio` | number
|
|
232
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
233
|
+
| ------------- | ------- | -------------------------------- | ----------------------------------------------- |
|
|
234
|
+
| `accept` | string | MIME type filter | `"image/*"` (default), `"image/jpeg,image/png"` |
|
|
235
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
236
|
+
| `cropImage` | boolean | Enable image cropping modal | `true`, `false` (default: `false`) |
|
|
237
|
+
| `aspectRatio` | number | Crop aspect ratio (width:height) | `1` (1:1 square), `16/9`, `4/3`, `1.5` |
|
|
238
|
+
| `multiple` | boolean | Allow selecting images | `true`, `false` (default: `false`) |
|
|
239
|
+
| `maxImages` | number | Limit the multiple select images | `8`, `4` |
|
|
259
240
|
|
|
260
241
|
##### `"video"` Field
|
|
261
242
|
|
|
262
|
-
| Property
|
|
263
|
-
|
|
|
264
|
-
| `accept`
|
|
265
|
-
| `dragDrop` | boolean | Enable drag-and-drop upload
|
|
266
|
-
| `maxSize`
|
|
243
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
244
|
+
| ---------- | ------- | --------------------------- | ----------------------------------------------- |
|
|
245
|
+
| `accept` | string | MIME type filter | `"video/*"` (default), `"video/mp4,video/webm"` |
|
|
246
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
247
|
+
| `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
|
|
267
248
|
|
|
268
249
|
##### `"audio"` Field
|
|
269
250
|
|
|
270
|
-
| Property
|
|
271
|
-
|
|
|
272
|
-
| `accept`
|
|
273
|
-
| `dragDrop` | boolean | Enable drag-and-drop upload
|
|
274
|
-
| `maxSize`
|
|
251
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
252
|
+
| ---------- | ------- | --------------------------- | ----------------------------------------------- |
|
|
253
|
+
| `accept` | string | MIME type filter | `"audio/*"` (default), `"audio/mpeg,audio/wav"` |
|
|
254
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
255
|
+
| `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
|
|
275
256
|
|
|
276
257
|
##### `"tinyEditor"` Field
|
|
277
258
|
|
|
278
|
-
| Property
|
|
279
|
-
|
|
|
280
|
-
| `editorKey`
|
|
281
|
-
| `fontFamily`
|
|
282
|
-
| `height`
|
|
283
|
-
| `imageUploadHandler` | function | Async image upload handler | `async (blobInfo) => Promise<string>` (returns image URL) |
|
|
284
|
-
|
|
285
|
-
##### `"group"` & `"cardGroup"` Fields
|
|
286
|
-
|
|
287
|
-
| Property | Type | Description | Accepted Values / Example |
|
|
288
|
-
| -- | -- | -- | - |
|
|
289
|
-
| `renderType` | string | Rendering type for grouped fields | `"default"`, `"card"`, or custom layout type |
|
|
290
|
-
| (nested fields) | array | Child form fields within group | Array of FormField objects |
|
|
259
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
260
|
+
| ------------ | ------ | ----------------------------- | ----------------------------------------------------------- |
|
|
261
|
+
| `editorKey` | string | TinyMCE API key (required) | Get from `import.meta.env.VITE_EDITOR_KEY` or pass directly |
|
|
262
|
+
| `fontFamily` | string | Default font family in editor | `"Arial"`, `"Georgia"`, `"Courier New"` |
|
|
263
|
+
| `height` | number | Editor height in pixels | `300`, `500` |
|
|
291
264
|
|
|
292
265
|
---
|
|
293
266
|
|
|
@@ -295,173 +268,22 @@ Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields
|
|
|
295
268
|
|
|
296
269
|
Used by `viewModal.fields` to display data in view/details modals.
|
|
297
270
|
|
|
298
|
-
| Key
|
|
299
|
-
|
|
|
300
|
-
| `key`
|
|
301
|
-
| `label`
|
|
302
|
-
| `type`
|
|
303
|
-
| `format`
|
|
304
|
-
| `imageKey`
|
|
305
|
-
| `titleKey`
|
|
306
|
-
| `subtitleKey`
|
|
307
|
-
| `variant`
|
|
308
|
-
| `chipOptions`
|
|
309
|
-
| `defaultColor`
|
|
310
|
-
| `className`
|
|
311
|
-
| `blockClass`
|
|
312
|
-
| `icon`
|
|
313
|
-
| `renderCondition` | function
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
### Standalone UI Components
|
|
318
|
-
|
|
319
|
-
In addition to the main CRUD component, the library exports reusable UI components that can be used independently:
|
|
320
|
-
|
|
321
|
-
#### Button Component
|
|
322
|
-
|
|
323
|
-
```jsx
|
|
324
|
-
import { Button } from 'react-admin-crud-manager';
|
|
325
|
-
|
|
326
|
-
// Basic button
|
|
327
|
-
<Button onClick={() => console.log('Clicked')}>Click Me</Button>
|
|
328
|
-
|
|
329
|
-
// Variants
|
|
330
|
-
<Button variant="contained" color="primary">Primary</Button>
|
|
331
|
-
<Button variant="outlined" color="success">Success</Button>
|
|
332
|
-
<Button variant="text" color="error">Error</Button>
|
|
333
|
-
|
|
334
|
-
// Sizes
|
|
335
|
-
<Button size="sm">Small</Button>
|
|
336
|
-
<Button size="md">Medium</Button>
|
|
337
|
-
<Button size="lg">Large</Button>
|
|
338
|
-
|
|
339
|
-
// States
|
|
340
|
-
<Button disabled>Disabled</Button>
|
|
341
|
-
<Button fullWidth>Full Width</Button>
|
|
342
|
-
|
|
343
|
-
// Submit button
|
|
344
|
-
<Button type="submit">Submit Form</Button>
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
| Prop | Type | Default | Description | Accepted Values |
|
|
348
|
-
| ----------- | -------- | ----------- | ----------------------------------------------- | ----------- |
|
|
349
|
-
| `variant` | string | `contained` | Style variant | `"contained"`, `"outlined"`, `"text"` |
|
|
350
|
-
| `color` | string | `primary` | Button color theme | `"primary"`, `"success"`, `"error"`, `"warning"`, `"default"` |
|
|
351
|
-
| `size` | string | `md` | Button size | `"sm"`, `"md"`, `"lg"`, `"xl"` |
|
|
352
|
-
| `fullWidth` | boolean | `false` | Stretch to full width | `true`, `false` |
|
|
353
|
-
| `className` | string | — | Additional CSS classes | Tailwind classes |
|
|
354
|
-
| `onClick` | function | — | Click handler (can be async) | `(e) => void \| Promise<any>` |
|
|
355
|
-
| `type` | string | `button` | HTML button type | `"button"`, `"submit"`, `"reset"` |
|
|
356
|
-
| `disabled` | boolean | `false` | Disable interaction | `true`, `false` |
|
|
357
|
-
|
|
358
|
-
#### Chip Component
|
|
359
|
-
|
|
360
|
-
```jsx
|
|
361
|
-
import { Chip } from 'react-admin-crud-manager';
|
|
362
|
-
|
|
363
|
-
// Basic chip
|
|
364
|
-
<Chip label="Active" />
|
|
365
|
-
|
|
366
|
-
// Variants with colors
|
|
367
|
-
<Chip label="Active" color="green" variant="contained" />
|
|
368
|
-
<Chip label="Pending" color="yellow" variant="outline" />
|
|
369
|
-
<Chip label="Inactive" color="red" variant="soft" />
|
|
370
|
-
|
|
371
|
-
// All available colors
|
|
372
|
-
<Chip label="Blue" color="blue" />
|
|
373
|
-
<Chip label="Teal" color="teal" />
|
|
374
|
-
<Chip label="Purple" color="purple" />
|
|
375
|
-
<Chip label="Green" color="green" />
|
|
376
|
-
<Chip label="Red" color="red" />
|
|
377
|
-
<Chip label="Yellow" color="yellow" />
|
|
378
|
-
<Chip label="Gray" color="gray" />
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
| Prop | Type | Default | Description | Accepted Values |
|
|
382
|
-
| --------- | ------ | ----------- | ------------------------------------- | --------- |
|
|
383
|
-
| `label` | string | Required | Badge text content | Any string |
|
|
384
|
-
| `variant` | string | `contained` | Display style | `"contained"`, `"outline"`, `"soft"` |
|
|
385
|
-
| `color` | string | `blue` | Color scheme | `"blue"`, `"teal"`, `"purple"`, `"green"`, `"red"`, `"yellow"`, `"gray"` |
|
|
386
|
-
|
|
387
|
-
#### Modal Component
|
|
388
|
-
|
|
389
|
-
```jsx
|
|
390
|
-
import { Modal } from 'react-admin-crud-manager';
|
|
391
|
-
import { AlertIcon } from 'lucide-react';
|
|
392
|
-
|
|
393
|
-
// Basic modal
|
|
394
|
-
<Modal
|
|
395
|
-
isOpen={isOpen}
|
|
396
|
-
onClose={() => setIsOpen(false)}
|
|
397
|
-
title="Confirm Action"
|
|
398
|
-
icon={<AlertIcon />}
|
|
399
|
-
actionButtons={[
|
|
400
|
-
{ type: "submit", label: "Confirm", color: "primary" },
|
|
401
|
-
{ type: "cancel", label: "Cancel", color: "default" }
|
|
402
|
-
]}
|
|
403
|
-
>
|
|
404
|
-
<p>Are you sure you want to continue?</p>
|
|
405
|
-
</Modal>
|
|
406
|
-
|
|
407
|
-
// Modal sizes
|
|
408
|
-
<Modal isOpen={true} size="sm" title="Small Modal" />
|
|
409
|
-
<Modal isOpen={true} size="md" title="Medium Modal" />
|
|
410
|
-
<Modal isOpen={true} size="lg" title="Large Modal" />
|
|
411
|
-
<Modal isOpen={true} size="xl" title="Extra Large Modal" />
|
|
412
|
-
<Modal isOpen={true} size="full" title="Full Screen Modal" />
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
| Prop | Type | Description | Accepted Values |
|
|
416
|
-
| --------------- | ---------- | -------------------------------------------- | --------- --– |
|
|
417
|
-
| `isOpen` | boolean | Show/hide modal | `true`, `false` |
|
|
418
|
-
| `onClose` | function | Callback when modal closes | `() => void` |
|
|
419
|
-
| `icon` | ReactNode | Icon displayed in header | Any React component or JSX element |
|
|
420
|
-
| `title` | string | Modal title text | Any string |
|
|
421
|
-
| `size` | string | Modal width | `"sm"`, `"md"`, `"lg"`, `"xl"`, `"full"` |
|
|
422
|
-
| `actionButtons` | array | Footer action buttons | Array of `{ type, label, color, variant, onClick, disabled }` |
|
|
423
|
-
| `loadingBtn` | boolean | Show loading spinner on action button | `true`, `false` |
|
|
424
|
-
| `children` | ReactNode | Modal content | Any React components |
|
|
425
|
-
|
|
426
|
-
#### FilterDrawer Component
|
|
427
|
-
|
|
428
|
-
```jsx
|
|
429
|
-
import { FilterDrawer } from 'react-admin-crud-manager';
|
|
430
|
-
|
|
431
|
-
<FilterDrawer
|
|
432
|
-
isOpen={isOpen}
|
|
433
|
-
onClose={() => setIsOpen(false)}
|
|
434
|
-
config={{
|
|
435
|
-
fields: [
|
|
436
|
-
{
|
|
437
|
-
key: "status",
|
|
438
|
-
label: "Status",
|
|
439
|
-
type: "select",
|
|
440
|
-
options: [
|
|
441
|
-
{ value: "active", label: "Active" },
|
|
442
|
-
{ value: "inactive", label: "Inactive" }
|
|
443
|
-
]
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
key: "dateFrom",
|
|
447
|
-
label: "From Date",
|
|
448
|
-
type: "date"
|
|
449
|
-
}
|
|
450
|
-
]
|
|
451
|
-
}}
|
|
452
|
-
onApply={(filters) => {
|
|
453
|
-
console.log("Applied filters:", filters);
|
|
454
|
-
setIsOpen(false);
|
|
455
|
-
}}
|
|
456
|
-
/>
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
| Prop | Type | Description | Accepted Values |
|
|
460
|
-
| --------- | -------- | ---------------- | --|
|
|
461
|
-
| `isOpen` | boolean | Show/hide filter drawer | `true`, `false` |
|
|
462
|
-
| `onClose` | function | Callback when drawer closes | `() => void` |
|
|
463
|
-
| `config` | object | Filter field configuration | `{ fields: [...] }` (uses Form Field Schema) |
|
|
464
|
-
| `onApply` | function | Callback with applied filter values | `(filters: object) => void` |
|
|
271
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
272
|
+
| ----------------- | --------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
273
|
+
| `key` | string | Property name to display | `"username"`, `"email"`, `"birth_date"` |
|
|
274
|
+
| `label` | string | Display label for the field | `"User Name"`, `"Email Address"` |
|
|
275
|
+
| `type` | string | Display type (similar to form field types) | `"text"`, `"date"`, `"chip"`, `"image"`, `"avatar"`, `"group"`, `"cardGroup"` |
|
|
276
|
+
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` |
|
|
277
|
+
| `imageKey` | string | Image property for avatar types | `"profileImage"`, `"avatarUrl"` |
|
|
278
|
+
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` |
|
|
279
|
+
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` |
|
|
280
|
+
| `variant` | string | Chip display variant | `"contained"`, `"outline"`, `"soft"` |
|
|
281
|
+
| `chipOptions` | array | Map values to chip labels and colors | `[{ value: "active", label: "Active", color: "green" }]` |
|
|
282
|
+
| `defaultColor` | string | Default chip color | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"` |
|
|
283
|
+
| `className` | string | Custom CSS class for display element | Tailwind classes |
|
|
284
|
+
| `blockClass` | string | Custom CSS class for field block/wrapper | Tailwind classes |
|
|
285
|
+
| `icon` | ReactNode | Icon element to display with field | `<UserIcon />`, `<MailIcon />` |
|
|
286
|
+
| `renderCondition` | function | Show field based on data | `(data: Record<string, any>) => boolean` |
|
|
465
287
|
|
|
466
288
|
---
|
|
467
289
|
|
|
@@ -503,8 +325,8 @@ const formFields = [
|
|
|
503
325
|
type: "select",
|
|
504
326
|
options: [
|
|
505
327
|
{ value: "admin", label: "Administrator" },
|
|
506
|
-
{ value: "user", label: "Regular User" }
|
|
507
|
-
]
|
|
328
|
+
{ value: "user", label: "Regular User" },
|
|
329
|
+
],
|
|
508
330
|
},
|
|
509
331
|
{
|
|
510
332
|
key: "adminLevel",
|
|
@@ -513,15 +335,15 @@ const formFields = [
|
|
|
513
335
|
renderCondition: (formData) => formData.userType === "admin", // Only show if userType is admin
|
|
514
336
|
options: [
|
|
515
337
|
{ value: "superadmin", label: "Super Admin" },
|
|
516
|
-
{ value: "moderator", label: "Moderator" }
|
|
517
|
-
]
|
|
338
|
+
{ value: "moderator", label: "Moderator" },
|
|
339
|
+
],
|
|
518
340
|
},
|
|
519
341
|
{
|
|
520
342
|
key: "department",
|
|
521
343
|
label: "Department",
|
|
522
344
|
type: "text",
|
|
523
345
|
renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
|
|
524
|
-
}
|
|
346
|
+
},
|
|
525
347
|
];
|
|
526
348
|
```
|
|
527
349
|
|
|
@@ -541,7 +363,7 @@ const formFields = [
|
|
|
541
363
|
return "Email must be a company email"; // Error message
|
|
542
364
|
}
|
|
543
365
|
return true; // Valid
|
|
544
|
-
}
|
|
366
|
+
},
|
|
545
367
|
},
|
|
546
368
|
{
|
|
547
369
|
key: "age",
|
|
@@ -551,8 +373,8 @@ const formFields = [
|
|
|
551
373
|
if (value < 18) return "Must be 18 or older";
|
|
552
374
|
if (value > 120) return "Please enter a valid age";
|
|
553
375
|
return true;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
376
|
+
},
|
|
377
|
+
},
|
|
556
378
|
];
|
|
557
379
|
```
|
|
558
380
|
|
|
@@ -648,8 +470,8 @@ Use a fully custom component for the view modal:
|
|
|
648
470
|
const CustomUserView = ({ data }) => (
|
|
649
471
|
<div className="space-y-4">
|
|
650
472
|
<div className="flex items-center gap-4">
|
|
651
|
-
<img
|
|
652
|
-
src={data.avatarUrl}
|
|
473
|
+
<img
|
|
474
|
+
src={data.avatarUrl}
|
|
653
475
|
alt={data.name}
|
|
654
476
|
className="w-16 h-16 rounded-full"
|
|
655
477
|
/>
|
|
@@ -675,9 +497,9 @@ const config = {
|
|
|
675
497
|
modalConfig: {
|
|
676
498
|
viewModal: {
|
|
677
499
|
title: "User Details",
|
|
678
|
-
component: CustomUserView // Use custom component
|
|
679
|
-
}
|
|
680
|
-
}
|
|
500
|
+
component: CustomUserView, // Use custom component
|
|
501
|
+
},
|
|
502
|
+
},
|
|
681
503
|
};
|
|
682
504
|
```
|
|
683
505
|
|
|
@@ -686,6 +508,7 @@ const config = {
|
|
|
686
508
|
Handle clicks on table rows:
|
|
687
509
|
|
|
688
510
|
```js
|
|
511
|
+
// For a custom function
|
|
689
512
|
const config = {
|
|
690
513
|
tableConfig: {
|
|
691
514
|
table_head: [...],
|
|
@@ -696,6 +519,16 @@ const config = {
|
|
|
696
519
|
}
|
|
697
520
|
}
|
|
698
521
|
};
|
|
522
|
+
|
|
523
|
+
// For open details on row click
|
|
524
|
+
const config = {
|
|
525
|
+
tableConfig: {
|
|
526
|
+
table_head: [...],
|
|
527
|
+
data: [...],
|
|
528
|
+
rowClick: true,
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
699
532
|
```
|
|
700
533
|
|
|
701
534
|
### 8. Server-Side Filtering
|
|
@@ -707,7 +540,7 @@ const config = {
|
|
|
707
540
|
tableConfig: {
|
|
708
541
|
filter: {
|
|
709
542
|
enabled: true,
|
|
710
|
-
useServerSideFilters: true // Enable server-side filtering
|
|
543
|
+
useServerSideFilters: true, // Enable server-side filtering
|
|
711
544
|
},
|
|
712
545
|
filterConfig: {
|
|
713
546
|
fields: [
|
|
@@ -717,25 +550,21 @@ const config = {
|
|
|
717
550
|
type: "select",
|
|
718
551
|
options: [
|
|
719
552
|
{ value: "active", label: "Active" },
|
|
720
|
-
{ value: "inactive", label: "Inactive" }
|
|
721
|
-
]
|
|
553
|
+
{ value: "inactive", label: "Inactive" },
|
|
554
|
+
],
|
|
722
555
|
},
|
|
723
556
|
{
|
|
724
557
|
key: "createdFrom",
|
|
725
558
|
label: "Created From",
|
|
726
|
-
type: "date"
|
|
559
|
+
type: "date",
|
|
727
560
|
},
|
|
728
561
|
{
|
|
729
562
|
key: "createdTo",
|
|
730
563
|
label: "Created To",
|
|
731
|
-
type: "date"
|
|
732
|
-
}
|
|
733
|
-
]
|
|
564
|
+
type: "date",
|
|
565
|
+
},
|
|
566
|
+
],
|
|
734
567
|
},
|
|
735
|
-
onFilterApply: (filters) => {
|
|
736
|
-
console.log("Filters applied:", filters);
|
|
737
|
-
// Filters will be passed to fetchData as additional params
|
|
738
|
-
}
|
|
739
568
|
},
|
|
740
569
|
fetchData: async ({
|
|
741
570
|
search,
|
|
@@ -752,11 +581,11 @@ const config = {
|
|
|
752
581
|
page: current_page,
|
|
753
582
|
sort_by,
|
|
754
583
|
sort_order,
|
|
755
|
-
...filters // Include filters in API call
|
|
756
|
-
}
|
|
584
|
+
...filters, // Include filters in API call
|
|
585
|
+
},
|
|
757
586
|
});
|
|
758
587
|
return { data: resp.items, pagination: resp.pagination };
|
|
759
|
-
}
|
|
588
|
+
},
|
|
760
589
|
};
|
|
761
590
|
```
|
|
762
591
|
|
|
@@ -772,26 +601,27 @@ const formFields = [
|
|
|
772
601
|
type: "text",
|
|
773
602
|
mask: "(99) 99999-9999", // Mask pattern
|
|
774
603
|
maskApplyOnValue: true, // Apply mask to default value
|
|
775
|
-
placeholder: "(00) 00000-0000"
|
|
604
|
+
placeholder: "(00) 00000-0000",
|
|
776
605
|
},
|
|
777
606
|
{
|
|
778
607
|
key: "zipCode",
|
|
779
608
|
label: "ZIP Code",
|
|
780
609
|
type: "text",
|
|
781
610
|
mask: "99999-999", // Pattern: 9 = digit, A = letter, X = alphanumeric, * = any
|
|
782
|
-
placeholder: "00000-000"
|
|
611
|
+
placeholder: "00000-000",
|
|
783
612
|
},
|
|
784
613
|
{
|
|
785
614
|
key: "creditCard",
|
|
786
615
|
label: "Credit Card",
|
|
787
616
|
type: "text",
|
|
788
617
|
mask: "9999 9999 9999 9999",
|
|
789
|
-
placeholder: "0000 0000 0000 0000"
|
|
790
|
-
}
|
|
618
|
+
placeholder: "0000 0000 0000 0000",
|
|
619
|
+
},
|
|
791
620
|
];
|
|
792
621
|
```
|
|
793
622
|
|
|
794
623
|
**Mask Pattern Reference:**
|
|
624
|
+
|
|
795
625
|
- `9` — Digit (0-9)
|
|
796
626
|
- `A` — Letter (a-zA-Z)
|
|
797
627
|
- `X` — Alphanumeric (a-zA-Z0-9)
|
|
@@ -810,7 +640,7 @@ const formFields = [
|
|
|
810
640
|
type: "image",
|
|
811
641
|
cropImage: true, // Enable cropping modal
|
|
812
642
|
aspectRatio: 1, // 1:1 square ratio
|
|
813
|
-
dragDrop: true
|
|
643
|
+
dragDrop: true,
|
|
814
644
|
},
|
|
815
645
|
{
|
|
816
646
|
key: "bannerImage",
|
|
@@ -818,40 +648,12 @@ const formFields = [
|
|
|
818
648
|
type: "image",
|
|
819
649
|
cropImage: true,
|
|
820
650
|
aspectRatio: 16 / 9, // 16:9 widescreen ratio
|
|
821
|
-
dragDrop: true
|
|
822
|
-
}
|
|
651
|
+
dragDrop: true,
|
|
652
|
+
},
|
|
823
653
|
];
|
|
824
654
|
```
|
|
825
655
|
|
|
826
|
-
### 11.
|
|
827
|
-
|
|
828
|
-
Display success/error messages automatically:
|
|
829
|
-
|
|
830
|
-
Notifications are handled internally by the library using the `notistack` library. Success and error messages are shown automatically:
|
|
831
|
-
|
|
832
|
-
- **Success messages**: Shown when add/edit/delete operations complete successfully
|
|
833
|
-
- **Error messages**: Shown when operations fail
|
|
834
|
-
- **Custom messages**: Handlers can return `{ newObject, message: "Custom message" }`
|
|
835
|
-
|
|
836
|
-
```js
|
|
837
|
-
const config = {
|
|
838
|
-
modalConfig: {
|
|
839
|
-
addModal: {
|
|
840
|
-
title: "Add User",
|
|
841
|
-
formFields: [...],
|
|
842
|
-
handleSubmit: async (formData) => {
|
|
843
|
-
const resp = await api.post("/users", formData);
|
|
844
|
-
return {
|
|
845
|
-
newObject: resp.user,
|
|
846
|
-
message: "User created successfully!" // Custom success message
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
### 12. Sortable Columns
|
|
656
|
+
### 11. Sortable Columns
|
|
855
657
|
|
|
856
658
|
Configure sorting with custom options and auto-generation:
|
|
857
659
|
|
|
@@ -868,14 +670,14 @@ const config = {
|
|
|
868
670
|
onChange: (payload) => {
|
|
869
671
|
console.log("Sort changed:", payload);
|
|
870
672
|
// payload contains: { value, option, key, order, type }
|
|
871
|
-
}
|
|
673
|
+
},
|
|
872
674
|
},
|
|
873
675
|
table_head: [
|
|
874
676
|
{ key: "name", title: "Name" },
|
|
875
677
|
{ key: "email", title: "Email" },
|
|
876
|
-
{ key: "createdAt", title: "Created Date" }
|
|
877
|
-
]
|
|
878
|
-
}
|
|
678
|
+
{ key: "createdAt", title: "Created Date" },
|
|
679
|
+
],
|
|
680
|
+
},
|
|
879
681
|
};
|
|
880
682
|
```
|
|
881
683
|
|
|
@@ -884,11 +686,11 @@ const config = {
|
|
|
884
686
|
#### Example 1: Minimal Client-Side CRUD
|
|
885
687
|
|
|
886
688
|
```js
|
|
887
|
-
import Crud from
|
|
689
|
+
import Crud from "react-admin-crud-manager";
|
|
888
690
|
|
|
889
691
|
const users = [
|
|
890
692
|
{ id: 1, name: "John Doe", email: "john@example.com", status: "active" },
|
|
891
|
-
{ id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" }
|
|
693
|
+
{ id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" },
|
|
892
694
|
];
|
|
893
695
|
|
|
894
696
|
function App() {
|
|
@@ -901,13 +703,18 @@ function App() {
|
|
|
901
703
|
{ key: "id", title: "ID", type: "index" },
|
|
902
704
|
{ key: "name", title: "Name" },
|
|
903
705
|
{ key: "email", title: "Email" },
|
|
904
|
-
{
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
706
|
+
{
|
|
707
|
+
key: "status",
|
|
708
|
+
title: "Status",
|
|
709
|
+
type: "chip",
|
|
710
|
+
chipOptions: [
|
|
711
|
+
{ value: "active", label: "Active", color: "green" },
|
|
712
|
+
{ value: "inactive", label: "Inactive", color: "red" },
|
|
713
|
+
],
|
|
714
|
+
},
|
|
908
715
|
],
|
|
909
|
-
search: { enabled: true },
|
|
910
|
-
pagination: { enabled: true
|
|
716
|
+
search: { enabled: true, searchKeys: ["name", "email"] },
|
|
717
|
+
pagination: { enabled: true },
|
|
911
718
|
},
|
|
912
719
|
modalConfig: {
|
|
913
720
|
addModal: {
|
|
@@ -921,15 +728,18 @@ function App() {
|
|
|
921
728
|
type: "select",
|
|
922
729
|
options: [
|
|
923
730
|
{ value: "active", label: "Active" },
|
|
924
|
-
{ value: "inactive", label: "Inactive" }
|
|
925
|
-
]
|
|
926
|
-
}
|
|
731
|
+
{ value: "inactive", label: "Inactive" },
|
|
732
|
+
],
|
|
733
|
+
},
|
|
927
734
|
],
|
|
928
735
|
handleSubmit: async (formData) => ({
|
|
929
|
-
newObject: {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
736
|
+
newObject: {
|
|
737
|
+
...formData,
|
|
738
|
+
id: Math.max(...users.map((u) => u.id)) + 1,
|
|
739
|
+
},
|
|
740
|
+
}),
|
|
741
|
+
},
|
|
742
|
+
},
|
|
933
743
|
};
|
|
934
744
|
|
|
935
745
|
return <Crud config={config} />;
|
|
@@ -941,12 +751,12 @@ export default App;
|
|
|
941
751
|
#### Example 2: Server-Side CRUD with Advanced Features
|
|
942
752
|
|
|
943
753
|
```js
|
|
944
|
-
import Crud from
|
|
945
|
-
import axios from
|
|
946
|
-
import { PlusIcon, EditIcon, TrashIcon } from
|
|
754
|
+
import Crud from "react-admin-crud-manager";
|
|
755
|
+
import axios from "axios";
|
|
756
|
+
import { PlusIcon, EditIcon, TrashIcon } from "lucide-react";
|
|
947
757
|
|
|
948
758
|
function App() {
|
|
949
|
-
const api = axios.create({ baseURL:
|
|
759
|
+
const api = axios.create({ baseURL: "https://api.example.com" });
|
|
950
760
|
|
|
951
761
|
const config = {
|
|
952
762
|
title: "Products",
|
|
@@ -960,7 +770,7 @@ function App() {
|
|
|
960
770
|
sort_order,
|
|
961
771
|
category,
|
|
962
772
|
minPrice,
|
|
963
|
-
maxPrice
|
|
773
|
+
maxPrice,
|
|
964
774
|
}) => {
|
|
965
775
|
const resp = await api.get("/products", {
|
|
966
776
|
params: {
|
|
@@ -971,8 +781,8 @@ function App() {
|
|
|
971
781
|
sort_order,
|
|
972
782
|
category,
|
|
973
783
|
minPrice,
|
|
974
|
-
maxPrice
|
|
975
|
-
}
|
|
784
|
+
maxPrice,
|
|
785
|
+
},
|
|
976
786
|
});
|
|
977
787
|
return {
|
|
978
788
|
data: resp.data.items,
|
|
@@ -980,16 +790,26 @@ function App() {
|
|
|
980
790
|
current_page: resp.data.page,
|
|
981
791
|
rows_per_page: resp.data.limit,
|
|
982
792
|
total_pages: resp.data.totalPages,
|
|
983
|
-
total_records: resp.data.total
|
|
984
|
-
}
|
|
793
|
+
total_records: resp.data.total,
|
|
794
|
+
},
|
|
985
795
|
};
|
|
986
796
|
},
|
|
987
797
|
tableConfig: {
|
|
988
798
|
table_head: [
|
|
989
799
|
{ key: "id", title: "ID", type: "index" },
|
|
990
|
-
{
|
|
800
|
+
{
|
|
801
|
+
key: "image",
|
|
802
|
+
title: "Image",
|
|
803
|
+
type: "avatar",
|
|
804
|
+
imageKey: "image",
|
|
805
|
+
titleKey: "name",
|
|
806
|
+
},
|
|
991
807
|
{ key: "name", title: "Product Name" },
|
|
992
|
-
{
|
|
808
|
+
{
|
|
809
|
+
key: "price",
|
|
810
|
+
title: "Price",
|
|
811
|
+
render: (row) => `$${row.price.toFixed(2)}`,
|
|
812
|
+
},
|
|
993
813
|
{
|
|
994
814
|
key: "category",
|
|
995
815
|
title: "Category",
|
|
@@ -997,11 +817,14 @@ function App() {
|
|
|
997
817
|
variant: "soft",
|
|
998
818
|
chipOptions: [
|
|
999
819
|
{ value: "electronics", label: "Electronics", color: "blue" },
|
|
1000
|
-
{ value: "clothing", label: "Clothing", color: "purple" }
|
|
1001
|
-
]
|
|
1002
|
-
}
|
|
820
|
+
{ value: "clothing", label: "Clothing", color: "purple" },
|
|
821
|
+
],
|
|
822
|
+
},
|
|
1003
823
|
],
|
|
1004
|
-
search: {
|
|
824
|
+
search: {
|
|
825
|
+
enabled: true,
|
|
826
|
+
useServerSideSearch: true,
|
|
827
|
+
},
|
|
1005
828
|
filter: { enabled: true, useServerSideFilters: true },
|
|
1006
829
|
pagination: { enabled: true, useServerSidePagination: true },
|
|
1007
830
|
sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
|
|
@@ -1011,8 +834,8 @@ function App() {
|
|
|
1011
834
|
fields: [
|
|
1012
835
|
{ label: "ID", key: "id" },
|
|
1013
836
|
{ label: "Name", key: "name" },
|
|
1014
|
-
{ label: "Price", key: "price" }
|
|
1015
|
-
]
|
|
837
|
+
{ label: "Price", key: "price" },
|
|
838
|
+
],
|
|
1016
839
|
},
|
|
1017
840
|
filterConfig: {
|
|
1018
841
|
fields: [
|
|
@@ -1022,13 +845,13 @@ function App() {
|
|
|
1022
845
|
type: "select",
|
|
1023
846
|
options: [
|
|
1024
847
|
{ value: "electronics", label: "Electronics" },
|
|
1025
|
-
{ value: "clothing", label: "Clothing" }
|
|
1026
|
-
]
|
|
848
|
+
{ value: "clothing", label: "Clothing" },
|
|
849
|
+
],
|
|
1027
850
|
},
|
|
1028
851
|
{ key: "minPrice", label: "Min Price", type: "number" },
|
|
1029
|
-
{ key: "maxPrice", label: "Max Price", type: "number" }
|
|
1030
|
-
]
|
|
1031
|
-
}
|
|
852
|
+
{ key: "maxPrice", label: "Max Price", type: "number" },
|
|
853
|
+
],
|
|
854
|
+
},
|
|
1032
855
|
},
|
|
1033
856
|
modalConfig: {
|
|
1034
857
|
addModal: {
|
|
@@ -1036,8 +859,20 @@ function App() {
|
|
|
1036
859
|
size: "lg",
|
|
1037
860
|
icon: <PlusIcon />,
|
|
1038
861
|
formFields: [
|
|
1039
|
-
{
|
|
1040
|
-
|
|
862
|
+
{
|
|
863
|
+
key: "name",
|
|
864
|
+
label: "Product Name",
|
|
865
|
+
type: "text",
|
|
866
|
+
required: true,
|
|
867
|
+
parentClass: "col-span-12",
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
key: "price",
|
|
871
|
+
label: "Price",
|
|
872
|
+
type: "number",
|
|
873
|
+
required: true,
|
|
874
|
+
parentClass: "col-span-6",
|
|
875
|
+
},
|
|
1041
876
|
{
|
|
1042
877
|
key: "category",
|
|
1043
878
|
label: "Category",
|
|
@@ -1046,11 +881,23 @@ function App() {
|
|
|
1046
881
|
parentClass: "col-span-6",
|
|
1047
882
|
options: [
|
|
1048
883
|
{ value: "electronics", label: "Electronics" },
|
|
1049
|
-
{ value: "clothing", label: "Clothing" }
|
|
1050
|
-
]
|
|
884
|
+
{ value: "clothing", label: "Clothing" },
|
|
885
|
+
],
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
key: "stock",
|
|
889
|
+
label: "Stock",
|
|
890
|
+
type: "number",
|
|
891
|
+
required: true,
|
|
892
|
+
parentClass: "col-span-6",
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
key: "description",
|
|
896
|
+
label: "Description",
|
|
897
|
+
type: "textarea",
|
|
898
|
+
rows: 4,
|
|
899
|
+
parentClass: "col-span-12",
|
|
1051
900
|
},
|
|
1052
|
-
{ key: "stock", label: "Stock", type: "number", required: true, parentClass: "col-span-6" },
|
|
1053
|
-
{ key: "description", label: "Description", type: "textarea", rows: 4, parentClass: "col-span-12" },
|
|
1054
901
|
{
|
|
1055
902
|
key: "image",
|
|
1056
903
|
label: "Product Image",
|
|
@@ -1058,37 +905,67 @@ function App() {
|
|
|
1058
905
|
cropImage: true,
|
|
1059
906
|
aspectRatio: 1,
|
|
1060
907
|
dragDrop: true,
|
|
1061
|
-
parentClass: "col-span-12"
|
|
1062
|
-
}
|
|
908
|
+
parentClass: "col-span-12",
|
|
909
|
+
},
|
|
1063
910
|
],
|
|
1064
911
|
handleSubmit: async (formData) => {
|
|
1065
912
|
const resp = await api.post("/products", formData);
|
|
1066
|
-
return {
|
|
1067
|
-
|
|
913
|
+
return {
|
|
914
|
+
newObject: resp.data,
|
|
915
|
+
message: "Product added successfully!",
|
|
916
|
+
};
|
|
917
|
+
},
|
|
1068
918
|
},
|
|
1069
919
|
editModal: {
|
|
1070
920
|
title: "Edit Product",
|
|
1071
921
|
size: "lg",
|
|
1072
922
|
icon: <EditIcon />,
|
|
1073
923
|
formFields: [
|
|
1074
|
-
{
|
|
1075
|
-
|
|
1076
|
-
|
|
924
|
+
{
|
|
925
|
+
key: "name",
|
|
926
|
+
label: "Product Name",
|
|
927
|
+
type: "text",
|
|
928
|
+
required: true,
|
|
929
|
+
parentClass: "col-span-12",
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
key: "price",
|
|
933
|
+
label: "Price",
|
|
934
|
+
type: "number",
|
|
935
|
+
required: true,
|
|
936
|
+
parentClass: "col-span-6",
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
key: "stock",
|
|
940
|
+
label: "Stock",
|
|
941
|
+
type: "number",
|
|
942
|
+
required: true,
|
|
943
|
+
parentClass: "col-span-6",
|
|
944
|
+
},
|
|
1077
945
|
],
|
|
1078
946
|
handleSubmit: async (formData, item) => {
|
|
1079
947
|
const resp = await api.put(`/products/${item.id}`, formData);
|
|
1080
948
|
return { newObject: resp.data, targetObject: item };
|
|
1081
|
-
}
|
|
949
|
+
},
|
|
1082
950
|
},
|
|
1083
951
|
deleteModal: {
|
|
1084
952
|
title: "Delete Product",
|
|
1085
953
|
icon: <TrashIcon />,
|
|
1086
954
|
confirmText: "Are you sure you want to delete this product?",
|
|
1087
955
|
referenceKey: "name",
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
956
|
+
actionButtons: [
|
|
957
|
+
{
|
|
958
|
+
type: "button",
|
|
959
|
+
label: "Delete",
|
|
960
|
+
color: "error",
|
|
961
|
+
variant: "contained",
|
|
962
|
+
onClick: async (event, item) => {
|
|
963
|
+
event.preventDefault();
|
|
964
|
+
await api.delete(`/products/${item.id}`);
|
|
965
|
+
return { targetObject: item };
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
],
|
|
1092
969
|
},
|
|
1093
970
|
viewModal: {
|
|
1094
971
|
title: "Product Details",
|
|
@@ -1098,10 +975,10 @@ function App() {
|
|
|
1098
975
|
{ key: "name", label: "Product Name" },
|
|
1099
976
|
{ key: "price", label: "Price" },
|
|
1100
977
|
{ key: "stock", label: "Stock" },
|
|
1101
|
-
{ key: "category", label: "Category", type: "chip" }
|
|
1102
|
-
]
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
978
|
+
{ key: "category", label: "Category", type: "chip" },
|
|
979
|
+
],
|
|
980
|
+
},
|
|
981
|
+
},
|
|
1105
982
|
};
|
|
1106
983
|
|
|
1107
984
|
return <Crud config={config} />;
|
|
@@ -1120,8 +997,8 @@ const config = {
|
|
|
1120
997
|
{ key: "id", title: "ID", type: "index" },
|
|
1121
998
|
{ key: "name", title: "Full Name" },
|
|
1122
999
|
{ key: "email", title: "Email" },
|
|
1123
|
-
{ key: "userType", title: "Type", type: "chip" }
|
|
1124
|
-
]
|
|
1000
|
+
{ key: "userType", title: "Type", type: "chip" },
|
|
1001
|
+
],
|
|
1125
1002
|
},
|
|
1126
1003
|
modalConfig: {
|
|
1127
1004
|
addModal: {
|
|
@@ -1138,7 +1015,7 @@ const config = {
|
|
|
1138
1015
|
return "Please enter your full name";
|
|
1139
1016
|
}
|
|
1140
1017
|
return true;
|
|
1141
|
-
}
|
|
1018
|
+
},
|
|
1142
1019
|
},
|
|
1143
1020
|
{
|
|
1144
1021
|
key: "email",
|
|
@@ -1151,7 +1028,7 @@ const config = {
|
|
|
1151
1028
|
return "Must use company email";
|
|
1152
1029
|
}
|
|
1153
1030
|
return true;
|
|
1154
|
-
}
|
|
1031
|
+
},
|
|
1155
1032
|
},
|
|
1156
1033
|
{
|
|
1157
1034
|
key: "phone",
|
|
@@ -1159,7 +1036,7 @@ const config = {
|
|
|
1159
1036
|
type: "phone",
|
|
1160
1037
|
countriesList: true,
|
|
1161
1038
|
mask: "(99) 99999-9999",
|
|
1162
|
-
parentClass: "col-span-12"
|
|
1039
|
+
parentClass: "col-span-12",
|
|
1163
1040
|
},
|
|
1164
1041
|
{
|
|
1165
1042
|
key: "userType",
|
|
@@ -1169,8 +1046,8 @@ const config = {
|
|
|
1169
1046
|
parentClass: "col-span-6",
|
|
1170
1047
|
options: [
|
|
1171
1048
|
{ value: "admin", label: "Administrator" },
|
|
1172
|
-
{ value: "user", label: "Regular User" }
|
|
1173
|
-
]
|
|
1049
|
+
{ value: "user", label: "Regular User" },
|
|
1050
|
+
],
|
|
1174
1051
|
},
|
|
1175
1052
|
{
|
|
1176
1053
|
key: "adminLevel",
|
|
@@ -1180,8 +1057,8 @@ const config = {
|
|
|
1180
1057
|
renderCondition: (data) => data.userType === "admin",
|
|
1181
1058
|
options: [
|
|
1182
1059
|
{ value: "superadmin", label: "Super Admin" },
|
|
1183
|
-
{ value: "moderator", label: "Moderator" }
|
|
1184
|
-
]
|
|
1060
|
+
{ value: "moderator", label: "Moderator" },
|
|
1061
|
+
],
|
|
1185
1062
|
},
|
|
1186
1063
|
{
|
|
1187
1064
|
key: "permissions",
|
|
@@ -1193,19 +1070,19 @@ const config = {
|
|
|
1193
1070
|
options: [
|
|
1194
1071
|
{ value: "read", label: "Can Read" },
|
|
1195
1072
|
{ value: "write", label: "Can Write" },
|
|
1196
|
-
{ value: "delete", label: "Can Delete" }
|
|
1197
|
-
]
|
|
1198
|
-
}
|
|
1073
|
+
{ value: "delete", label: "Can Delete" },
|
|
1074
|
+
],
|
|
1075
|
+
},
|
|
1199
1076
|
],
|
|
1200
1077
|
handleSubmit: async (formData) => {
|
|
1201
1078
|
const resp = await api.post("/users", formData);
|
|
1202
1079
|
return {
|
|
1203
1080
|
newObject: resp.data,
|
|
1204
|
-
message: `User ${formData.name} created successfully
|
|
1081
|
+
message: `User ${formData.name} created successfully!`,
|
|
1205
1082
|
};
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
},
|
|
1209
1086
|
};
|
|
1210
1087
|
```
|
|
1211
1088
|
|
|
@@ -1229,16 +1106,16 @@ const config = {
|
|
|
1229
1106
|
const newUser = { ...formData, id: Date.now() };
|
|
1230
1107
|
setUsers([...users, newUser]);
|
|
1231
1108
|
return { newObject: newUser };
|
|
1232
|
-
}
|
|
1109
|
+
},
|
|
1233
1110
|
},
|
|
1234
1111
|
editModal: {
|
|
1235
1112
|
handleSubmit: async (formData, item) => {
|
|
1236
1113
|
const updated = { ...item, ...formData };
|
|
1237
|
-
setUsers(users.map(u => u.id === item.id ? updated : u));
|
|
1114
|
+
setUsers(users.map((u) => (u.id === item.id ? updated : u)));
|
|
1238
1115
|
return { newObject: updated, targetObject: item };
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1242
1119
|
};
|
|
1243
1120
|
```
|
|
1244
1121
|
|
|
@@ -1254,8 +1131,8 @@ const fetchData = async (params) => {
|
|
|
1254
1131
|
current_page: resp.data.meta.currentPage,
|
|
1255
1132
|
rows_per_page: resp.data.meta.perPage,
|
|
1256
1133
|
total_pages: resp.data.meta.totalPages,
|
|
1257
|
-
total_records: resp.data.meta.total
|
|
1258
|
-
}
|
|
1134
|
+
total_records: resp.data.meta.total,
|
|
1135
|
+
},
|
|
1259
1136
|
};
|
|
1260
1137
|
};
|
|
1261
1138
|
```
|
|
@@ -1268,7 +1145,7 @@ const handleSubmit = async (formData) => {
|
|
|
1268
1145
|
const resp = await api.post("/items", formData);
|
|
1269
1146
|
return {
|
|
1270
1147
|
newObject: resp.data,
|
|
1271
|
-
message: "Item created successfully!" // Optional success message
|
|
1148
|
+
message: "Item created successfully!", // Optional success message
|
|
1272
1149
|
};
|
|
1273
1150
|
} catch (error) {
|
|
1274
1151
|
// Throw error to trigger snackbar error notification
|
|
@@ -1288,21 +1165,20 @@ const formFields = [
|
|
|
1288
1165
|
type: "select",
|
|
1289
1166
|
options: [
|
|
1290
1167
|
{ value: "personal", label: "Personal" },
|
|
1291
|
-
{ value: "business", label: "Business" }
|
|
1292
|
-
]
|
|
1168
|
+
{ value: "business", label: "Business" },
|
|
1169
|
+
],
|
|
1293
1170
|
},
|
|
1294
1171
|
{
|
|
1295
1172
|
key: "companyName",
|
|
1296
1173
|
label: "Company Name",
|
|
1297
1174
|
type: "text",
|
|
1298
1175
|
renderCondition: (data) => data.type === "business",
|
|
1299
|
-
required: (data) => data.type === "business"
|
|
1300
1176
|
},
|
|
1301
1177
|
{
|
|
1302
1178
|
key: "phone",
|
|
1303
1179
|
type: "phone",
|
|
1304
|
-
renderCondition: (data) => data.type === "personal"
|
|
1305
|
-
}
|
|
1180
|
+
renderCondition: (data) => data.type === "personal",
|
|
1181
|
+
},
|
|
1306
1182
|
];
|
|
1307
1183
|
```
|
|
1308
1184
|
|
|
@@ -1322,7 +1198,7 @@ const tableConfig = {
|
|
|
1322
1198
|
<p className="text-xs text-gray-500">{row.email}</p>
|
|
1323
1199
|
</div>
|
|
1324
1200
|
</div>
|
|
1325
|
-
)
|
|
1201
|
+
),
|
|
1326
1202
|
},
|
|
1327
1203
|
{
|
|
1328
1204
|
key: "metrics",
|
|
@@ -1333,9 +1209,9 @@ const tableConfig = {
|
|
|
1333
1209
|
{row.completed}%
|
|
1334
1210
|
</span>
|
|
1335
1211
|
</div>
|
|
1336
|
-
)
|
|
1337
|
-
}
|
|
1338
|
-
]
|
|
1212
|
+
),
|
|
1213
|
+
},
|
|
1214
|
+
],
|
|
1339
1215
|
};
|
|
1340
1216
|
```
|
|
1341
1217
|
|
|
@@ -1346,12 +1222,12 @@ const formFields = [
|
|
|
1346
1222
|
{
|
|
1347
1223
|
key: "email",
|
|
1348
1224
|
type: "email",
|
|
1349
|
-
customValidation:
|
|
1225
|
+
customValidation: (value) => {
|
|
1350
1226
|
// Async validation example
|
|
1351
|
-
const exists =
|
|
1227
|
+
const exists = checkEmailExists(value);
|
|
1352
1228
|
if (exists) return "Email already in use";
|
|
1353
1229
|
return true;
|
|
1354
|
-
}
|
|
1230
|
+
},
|
|
1355
1231
|
},
|
|
1356
1232
|
{
|
|
1357
1233
|
key: "password",
|
|
@@ -1361,8 +1237,8 @@ const formFields = [
|
|
|
1361
1237
|
if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
|
|
1362
1238
|
if (!/[0-9]/.test(value)) return "Must contain a number";
|
|
1363
1239
|
return true;
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1366
1242
|
];
|
|
1367
1243
|
```
|
|
1368
1244
|
|
|
@@ -1385,20 +1261,20 @@ const config = {
|
|
|
1385
1261
|
type: "image",
|
|
1386
1262
|
dragDrop: true,
|
|
1387
1263
|
cropImage: true,
|
|
1388
|
-
aspectRatio: 1
|
|
1389
|
-
}
|
|
1264
|
+
aspectRatio: 1,
|
|
1265
|
+
},
|
|
1390
1266
|
],
|
|
1391
1267
|
handleSubmit: async (formData) => {
|
|
1392
1268
|
// Image data includes base64 or file data
|
|
1393
1269
|
const imageUrl = await handleImageUpload(formData.image);
|
|
1394
1270
|
const resp = await api.post("/items", {
|
|
1395
1271
|
...formData,
|
|
1396
|
-
image: imageUrl
|
|
1272
|
+
image: imageUrl,
|
|
1397
1273
|
});
|
|
1398
1274
|
return { newObject: resp.data };
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1402
1278
|
};
|
|
1403
1279
|
```
|
|
1404
1280
|
|
|
@@ -1414,15 +1290,15 @@ const viewModal = {
|
|
|
1414
1290
|
renderCondition: (data) => data.status !== null,
|
|
1415
1291
|
chipOptions: [
|
|
1416
1292
|
{ value: "active", label: "Active", color: "green" },
|
|
1417
|
-
{ value: "suspended", label: "Suspended", color: "red" }
|
|
1418
|
-
]
|
|
1293
|
+
{ value: "suspended", label: "Suspended", color: "red" },
|
|
1294
|
+
],
|
|
1419
1295
|
},
|
|
1420
1296
|
{
|
|
1421
1297
|
key: "suspendReason",
|
|
1422
1298
|
label: "Suspension Reason",
|
|
1423
|
-
renderCondition: (data) => data.status === "suspended" // Only show if suspended
|
|
1424
|
-
}
|
|
1425
|
-
]
|
|
1299
|
+
renderCondition: (data) => data.status === "suspended", // Only show if suspended
|
|
1300
|
+
},
|
|
1301
|
+
],
|
|
1426
1302
|
};
|
|
1427
1303
|
```
|
|
1428
1304
|
|
|
@@ -1434,16 +1310,21 @@ const tableConfig = {
|
|
|
1434
1310
|
enabled: true,
|
|
1435
1311
|
useServerSideSorting: true,
|
|
1436
1312
|
options: [
|
|
1437
|
-
{
|
|
1313
|
+
{
|
|
1314
|
+
value: "recent",
|
|
1315
|
+
label: "Most Recent",
|
|
1316
|
+
key: "createdAt",
|
|
1317
|
+
order: "desc",
|
|
1318
|
+
},
|
|
1438
1319
|
{ value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
|
|
1439
1320
|
{ value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
|
|
1440
1321
|
{ value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
|
|
1441
|
-
{ value: "popular", label: "Most Popular", key: "views", order: "desc" }
|
|
1322
|
+
{ value: "popular", label: "Most Popular", key: "views", order: "desc" },
|
|
1442
1323
|
],
|
|
1443
1324
|
onChange: (payload) => {
|
|
1444
1325
|
console.log(`Sorted by: ${payload.option?.label}`);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1447
1328
|
};
|
|
1448
1329
|
```
|
|
1449
1330
|
|