react-admin-crud-manager 1.1.2 → 1.2.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.
- package/README.md +1322 -207
- package/dist/index.cjs.js +20 -55
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +4092 -2529
- package/dist/index.es.js.map +1 -1
- package/dist/types/components/Details/Details.d.ts +11 -0
- package/dist/types/components/Details/components/CardGroup.d.ts +9 -1
- package/dist/types/components/Details/components/DetailRow.d.ts +9 -1
- package/dist/types/components/Details/components/GroupRow.d.ts +9 -1
- package/dist/types/components/Filter/FilterDrawer.d.ts +2 -2
- package/dist/types/components/Form/components/AudioPicker.d.ts +2 -1
- package/dist/types/components/Form/components/Checkbox.d.ts +2 -1
- package/dist/types/components/Form/components/ImagePicker.d.ts +2 -1
- package/dist/types/components/Form/components/Input.d.ts +1 -0
- package/dist/types/components/Form/components/MultiImagePicker.d.ts +21 -0
- package/dist/types/components/Form/components/PhoneInput.d.ts +2 -1
- package/dist/types/components/Form/components/Radio.d.ts +2 -1
- package/dist/types/components/Form/components/RenderFields.d.ts +3 -2
- package/dist/types/components/Form/components/Select.d.ts +5 -2
- package/dist/types/components/Form/components/Switch.d.ts +1 -0
- package/dist/types/components/Form/components/TextArea.d.ts +2 -0
- package/dist/types/components/Form/components/TinyEditor.d.ts +3 -1
- package/dist/types/components/Form/components/VideoPicker.d.ts +2 -1
- package/dist/types/components/Modal/Modal.d.ts +10 -1
- package/dist/types/lib/crudClasses.d.ts +94 -0
- package/dist/types/types/crudtypes.d.ts +51 -0
- package/package.json +10 -8
- package/dist/tailwind.css +0 -2364
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A reusable React CRUD admin template with modular components for rapid admin dashboard development.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
|
+
|
|
6
7
|
- Plug-and-play CRUD page component
|
|
7
8
|
- Modular, customizable UI components (Table, Modal, Form, etc.)
|
|
8
9
|
- Built with React 18+ and TypeScript
|
|
@@ -11,7 +12,6 @@ A reusable React CRUD admin template with modular components for rapid admin das
|
|
|
11
12
|
- Sorting, filtering, searching, and pagination support
|
|
12
13
|
- Rich form fields including text, select, image upload, rich text editor, and more
|
|
13
14
|
|
|
14
|
-
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
17
|
```sh
|
|
@@ -20,15 +20,7 @@ npm install react-admin-crud-manager
|
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
|
-
### 1.
|
|
24
|
-
|
|
25
|
-
In your main entry file (e.g., `src/main.jsx` or `src/index.js`):
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
import 'react-admin-crud-manager/dist/tailwind.css';
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. Use the component
|
|
23
|
+
### 1. Use the component
|
|
32
24
|
|
|
33
25
|
```
|
|
34
26
|
import Crud from 'react-admin-crud-manager';
|
|
@@ -44,31 +36,35 @@ function App() {
|
|
|
44
36
|
```
|
|
45
37
|
|
|
46
38
|
## Components
|
|
39
|
+
|
|
47
40
|
- `Crud`: Main CRUD page component
|
|
48
41
|
- `Table`, `Modal`, `Form`, etc.: Available for advanced use
|
|
49
42
|
|
|
50
43
|
## Props
|
|
44
|
+
|
|
51
45
|
Below is a complete reference of the public props accepted by this package (types, accepted values and defaults). The library exposes a single primary component (`Crud`) that receives a single `config` prop — most configuration lives inside that object.
|
|
52
46
|
|
|
53
47
|
---
|
|
54
48
|
|
|
55
49
|
### Crud (default export)
|
|
50
|
+
|
|
56
51
|
`<Crud config={config} />`
|
|
52
|
+
|
|
57
53
|
- `config` (object) — required. Top-level configuration object used by the CRUD page.
|
|
58
54
|
|
|
59
55
|
#### Key properties of `config`
|
|
60
56
|
|
|
61
|
-
| Property
|
|
62
|
-
|
|
63
|
-
| `title`
|
|
64
|
-
| `description`
|
|
65
|
-
| `buttonText`
|
|
66
|
-
| `fetchData`
|
|
67
|
-
| `fetchRowDetails` | function | No
|
|
68
|
-
| `isStaticData`
|
|
69
|
-
| `tableConfig`
|
|
70
|
-
| `modalConfig`
|
|
71
|
-
| `filterConfig`
|
|
57
|
+
| Property | Type | Required | Description |
|
|
58
|
+
| ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `title` | string | Yes | Title of the CRUD page |
|
|
60
|
+
| `description` | string | No | Optional description text |
|
|
61
|
+
| `buttonText` | string | No | Custom text for action buttons |
|
|
62
|
+
| `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
|
+
| `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
|
|
64
|
+
| `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
|
|
65
|
+
| `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
|
|
66
|
+
| `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
|
|
67
|
+
| `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
|
|
72
68
|
|
|
73
69
|
---
|
|
74
70
|
|
|
@@ -76,39 +72,41 @@ Below is a complete reference of the public props accepted by this package (type
|
|
|
76
72
|
|
|
77
73
|
#### Table Configuration Keys
|
|
78
74
|
|
|
79
|
-
| Key
|
|
80
|
-
|
|
81
|
-
| `table_head`
|
|
82
|
-
| `data`
|
|
83
|
-
| `loading`
|
|
84
|
-
| `search`
|
|
85
|
-
| `filter`
|
|
86
|
-
| `pagination` | object |
|
|
87
|
-
| `sort`
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
75
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
76
|
+
| ----------------------------- | ----------------------- | ------------------------------- | ------- |
|
|
77
|
+
| `table_head` | array of column objects | Column definitions | Array of table column objects (see [Table column object](#table-column-object-table_head)) |
|
|
78
|
+
| `data` | array | Rows displayed in the table | Array of objects (e.g., `[{ id: 1, name: "John" }, ...]`) |
|
|
79
|
+
| `loading` | boolean | Show skeleton loader when true | `true`, `false`; default: `false` |
|
|
80
|
+
| `search` | object | Search functionality config | `{ enabled: true, placeholder: "Search...", useServerSideSearch?: false, searchKeys?: ["name", "email"] }` |
|
|
81
|
+
| `filter` | object | Filter drawer config | `{ enabled: true, useServerSideFilters?: false }` |
|
|
82
|
+
| `pagination` | object | Pagination controls | `{ enabled: true, rows_per_page: 10, useServerSidePagination?: false, current_page?: 1, total_pages?: 5, total_records?: 50 }` |
|
|
83
|
+
| `sort` | object | Sorting configuration | `{ enabled: true, useServerSideSorting?: false, autoGenerate: true, onChange?: (payload) => void, options?: [...], clearLabel?: "Clear Sort" }` |
|
|
84
|
+
| `exportCSV` | object | Export data as CSV | `{ enabled: true, fileName: "users.csv", fields: [{ label: "Name", key: "name" }, ...] }` |
|
|
85
|
+
| `emptyMessage` | string | Message when no rows exist | `"No data available"`, `"No records found"`, etc. |
|
|
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` |
|
|
93
91
|
|
|
94
92
|
#### Table Column Object (`table_head[]`)
|
|
95
93
|
|
|
96
|
-
| Key
|
|
97
|
-
|
|
98
|
-
| `key`
|
|
99
|
-
| `title`
|
|
100
|
-
| `type`
|
|
101
|
-
| `imageKey`
|
|
102
|
-
| `titleKey`
|
|
103
|
-
| `subtitleKey`
|
|
104
|
-
| `onClickDetails` | boolean
|
|
105
|
-
| `variant`
|
|
106
|
-
| `chipOptions`
|
|
107
|
-
| `defaultColor`
|
|
108
|
-
| `className`
|
|
109
|
-
| `format`
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
94
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
95
|
+
| ---------------- | -------- | ---------------------------------------------------------------- | ------------------------------------- |
|
|
96
|
+
| `key` | string | Property name in row objects | `"id"`, `"name"`, `"email"` (must exist in data objects)|
|
|
97
|
+
| `title` | string | Column header text | `"User ID"`, `"Full Name"`, `"Email Address"` |
|
|
98
|
+
| `type` | string | Column renderer type | `"plain"` (default), `"index"` (row number), `"group"` (avatar+text), `"chip"` (badge), `"date"`, `"avatar"`, `"menu_actions"` |
|
|
99
|
+
| `imageKey` | string | Image property for avatar/group types | `"profileImage"`, `"avatarUrl"` (path to image in data object) |
|
|
100
|
+
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` (property key in data object) |
|
|
101
|
+
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` (property key in data object) |
|
|
102
|
+
| `onClickDetails` | boolean | Clicking cell opens view details modal | `true`, `false` (default: `false`) |
|
|
103
|
+
| `variant` | string | Chip styling variant | `"contained"`, `"outline"`, `"soft"` (used with `type: "chip"`) |
|
|
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) | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"`, etc. |
|
|
106
|
+
| `className` | string | Custom CSS class for cell content | Tailwind classes: `"font-bold text-sm text-gray-600"` |
|
|
107
|
+
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` (uses date-fns patterns) |
|
|
108
|
+
| `render` | function | Custom cell renderer (overrides built-in logic) | `(row: object, rowIndex: number) => ReactNode`; e.g., `(row) => <span>{row.name.toUpperCase()}</span>` |
|
|
109
|
+
| `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 /> }]` |
|
|
112
110
|
|
|
113
111
|
---
|
|
114
112
|
|
|
@@ -116,224 +114,1341 @@ Below is a complete reference of the public props accepted by this package (type
|
|
|
116
114
|
|
|
117
115
|
#### Add & Edit Modal (`addModal`, `editModal`)
|
|
118
116
|
|
|
119
|
-
| Property
|
|
120
|
-
|
|
121
|
-
| `title`
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
117
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
118
|
+
| --------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ |
|
|
119
|
+
| `title` | string | Yes | Modal title | `"Add New User"`, `"Edit User Profile"` |
|
|
120
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<PlusIcon />`, `<EditIcon />` |
|
|
121
|
+
| `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
|
|
122
|
+
| `formClass` | string | No | Custom CSS class for form wrapper | Tailwind classes: `"grid grid-cols-12 gap-4"` |
|
|
123
|
+
| `formFields` | array | Yes | Form field objects | Array of form field objects (see [Form Field Schema](#form-field-schema)) |
|
|
124
|
+
| `handleSubmit` | function | Yes | Async callback on form submission | `async (formData) => Promise<{ newObject, message?: string }>` (add), `async (formData, item) => Promise<{ newObject, targetObject, message?: string }>` (edit) |
|
|
125
|
+
| `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Save", color: "primary", variant: "contained", onClick?: (e, item) => void, disabled?: boolean }]` |
|
|
127
126
|
|
|
128
127
|
#### Delete Modal (`deleteModal`)
|
|
129
128
|
|
|
130
|
-
| Property
|
|
131
|
-
|
|
132
|
-
| `title`
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
129
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
130
|
+
| --------------- | -------- | -------- | -------------------------------- | ---------------------- |
|
|
131
|
+
| `title` | string | No | Modal title | `"Delete User"`, `"Confirm Delete"` |
|
|
132
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<TrashIcon />`, `<WarningIcon />` |
|
|
133
|
+
| `size` | string | No | Modal width | `"sm"` (default), `"md"`, `"lg"`, `"xl"`, `"full"` |
|
|
134
|
+
| `confirmText` | string | No | Confirmation message text | `"Are you sure you want to delete this user?"`, `"This action cannot be undone."` |
|
|
135
|
+
| `referenceKey` | string | No | Property key to display as confirmation reference | `"name"`, `"email"` (shows the value from selected item) |
|
|
136
|
+
| `action` | function | Yes | Async callback to delete item | `async (selectedItem) => Promise<{ targetObject }>` (returns the deleted row) |
|
|
137
|
+
| `actionButtons` | array | No | Custom action buttons | `[{ type: "submit", label: "Delete", color: "error", variant: "contained" }]` |
|
|
138
138
|
|
|
139
139
|
#### View Modal (`viewModal`)
|
|
140
140
|
|
|
141
|
-
| Property
|
|
142
|
-
|
|
143
|
-
| `title`
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
141
|
+
| Property | Type | Required | Description | Accepted Values / Example |
|
|
142
|
+
| ----------- | --------------- | -------- | -------------------- | -------------------- |
|
|
143
|
+
| `title` | string | Yes | Modal title | `"User Details"`, `"View Profile"` |
|
|
144
|
+
| `icon` | ReactNode | No | Icon element displayed in modal header | `<EyeIcon />`, `<InfoIcon />` |
|
|
145
|
+
| `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
|
|
146
|
+
| `component` | React component | No | Custom component to render (receives `data` prop) | `(props) => <CustomViewComponent data={props.data} />` |
|
|
147
|
+
| `variant` | string | No | View layout style | `"default"`, `"card"`, `"split"` |
|
|
148
|
+
| `fields` | array | No | View field objects (if not using custom component) | Array of field objects (see [View Field Schema](#view-field-schema)) |
|
|
149
|
+
| `styles` | object | No | Custom CSS classes for various view elements | `{ containerClass: "...", rowClass: "...", labelClass: "...", valueClass: "...", ... }` |
|
|
150
|
+
| `modalClassNames` | object | No | Custom classes for modal structure | `{ overlay: "...", container: "...", header: "...", body: "...", footer: "...", closeButton: "..." }` |
|
|
151
|
+
| `footer` | object | No | Footer configuration | `{ cancelButton: true, cancelText: "Close" }` |
|
|
148
152
|
|
|
149
153
|
---
|
|
150
154
|
|
|
151
155
|
### Form Field Schema
|
|
152
156
|
|
|
153
|
-
Used by `modalConfig.*.formFields` and `
|
|
157
|
+
Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields`. All form fields follow the `FormField` shape.
|
|
158
|
+
|
|
159
|
+
#### Common Field Properties (All Types)
|
|
160
|
+
|
|
161
|
+
| Key | Type | Required | Description | Accepted Values / Example |
|
|
162
|
+
| ---------------- | -------- | --- | ----------------------- | -------------------- |
|
|
163
|
+
| `key` | string | Yes | Property name/identifier for form data | `"username"`, `"email"`, `"birth_date"` |
|
|
164
|
+
| `label` | string | No | Human-readable label for the field | `"User Name"`, `"Email Address"`, `"Date of Birth"` |
|
|
165
|
+
| `type` | string | Yes | Field type determining the input/renderer | `"text"`, `"number"`, `"email"`, `"password"`, `"select"`, `"checkbox"`, `"radio"`, `"switch"`, `"phone"`, `"textarea"`, `"image"`, `"video"`, `"audio"`, `"tinyEditor"`, `"group"`, `"cardGroup"` |
|
|
166
|
+
| `required` | boolean | No | Field must have a value for form submission | `true`, `false` (default: `false`) |
|
|
167
|
+
| `minLength` | number | No | Minimum character length (for text fields) | `5`, `10`, `50` |
|
|
168
|
+
| `placeholder` | string | No | Placeholder text shown in empty field | `"Enter your name"`, `"user@example.com"` |
|
|
169
|
+
| `disabled` | boolean | No | Field is disabled and read-only | `true`, `false` (default: `false`) |
|
|
170
|
+
| `parentClass` | string | No | Custom CSS classes for field wrapper (grid) | Tailwind classes: `"col-span-6"`, `"col-span-12"` |
|
|
171
|
+
| `defaultValue` | any | No | Initial value for the field | Depends on field type; e.g., `"John"`, `25`, `true`, `["option1", "option2"]` |
|
|
172
|
+
| `renderCondition` | function | No | Show field based on form data | `(formData: Record<string, any>) => boolean`; e.g., `(data) => data.userType === 'admin'` |
|
|
173
|
+
| `customValidation`| function | No | Custom validation logic | `(value: any) => boolean \| string`; return `false` for invalid, error message string, or `true` for valid |
|
|
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"` |
|
|
176
|
+
|
|
177
|
+
#### Type-Specific Properties
|
|
178
|
+
|
|
179
|
+
##### `"text"` Field
|
|
180
|
+
|
|
181
|
+
| Property | Type | Description | Example |
|
|
182
|
+
| ---- | --- | -------------------- | --- |
|
|
183
|
+
| `minLength` | number | Minimum character length | `5` |
|
|
184
|
+
| `placeholder` | string | Placeholder text | `"Enter your name"` |
|
|
185
|
+
| `pattern` | string | Regex validation pattern | `"^[a-zA-Z ]*$"` (letters and spaces only) |
|
|
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`) |
|
|
154
188
|
|
|
155
|
-
|
|
189
|
+
##### `"number"` Field
|
|
156
190
|
|
|
157
|
-
|
|
|
158
|
-
|
|
159
|
-
| `
|
|
160
|
-
| `label` | string | No | Human-readable label |
|
|
161
|
-
| `type` | string | Yes | Field type: `text`, `number`, `email`, `password`, `select`, `checkbox`, `radio`, `switch`, `phone`, `textarea`, `image`, `video`, `audio`, `tinyEditor`, `group`, `cardGroup` |
|
|
162
|
-
| `required` | boolean | No | Field is required for form submission |
|
|
163
|
-
| `minLength` | number | No | Minimum character length |
|
|
164
|
-
| `parentClass` | string | No | Tailwind grid class (e.g., `col-span-6`) |
|
|
165
|
-
| `placeholder` | string | No | Placeholder text |
|
|
166
|
-
| `disabled` | boolean | No | Field is disabled |
|
|
191
|
+
| Property | Type | Description | Example |
|
|
192
|
+
| --- | --- | ------------- | --- |
|
|
193
|
+
| `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
|
|
167
194
|
|
|
168
|
-
|
|
195
|
+
##### `"email"` Field
|
|
169
196
|
|
|
170
|
-
|
|
197
|
+
| Property | Type | Description | Example |
|
|
198
|
+
| ------ | ------- | -------------------- | --- |
|
|
199
|
+
| `placeholder` | string | Placeholder text | `"user@example.com"` |
|
|
171
200
|
|
|
172
|
-
|
|
173
|
-
|-----|------|-------------|
|
|
174
|
-
| `options` | array | `{ value: string|number|boolean, label: string }[]` |
|
|
175
|
-
| `multiple` | boolean | Allow multiple selections |
|
|
176
|
-
| `search` | boolean | Show search input inside dropdown |
|
|
177
|
-
| `dropdownMaxHeight` | string | CSS height value (e.g., `300px`) |
|
|
201
|
+
##### `"password"` Field
|
|
178
202
|
|
|
179
|
-
|
|
203
|
+
| Property | Type | Description | Example |
|
|
204
|
+
| --- | --- | -- | -- |
|
|
205
|
+
| `minLength` | number | Minimum character length | `8` |
|
|
180
206
|
|
|
181
|
-
|
|
182
|
-
|-----|------|-------------|
|
|
183
|
-
| `options` | array | `{ value, label }[]` |
|
|
184
|
-
| `multiple` | boolean | Allow selecting multiple values (component prop: `multiSelect`) |
|
|
207
|
+
##### `"select"` Field
|
|
185
208
|
|
|
186
|
-
|
|
209
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
210
|
+
| ----- | --- | -------------------- | -- |
|
|
211
|
+
| `options` | array | Select options; `{ value, label, color? }[]` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
|
|
212
|
+
| `multiple` | boolean | Allow selecting multiple options | `true`, `false` (default: `false`) |
|
|
213
|
+
| `search` | boolean | Enable searchable dropdown | `true`, `false` (default: `false`) |
|
|
214
|
+
| `dropdownMaxHeight` | number | Max height of dropdown in pixels | `300`, `400` |
|
|
187
215
|
|
|
188
|
-
|
|
189
|
-
|-----|------|-------------|
|
|
190
|
-
| `options` | array | Optional radio-like options: `[{ label, value }]` |
|
|
191
|
-
| `text` | string | Description text shown next to switch |
|
|
216
|
+
##### `"checkbox"` Field
|
|
192
217
|
|
|
193
|
-
|
|
218
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
219
|
+
| -- | --- | -------------------- | -- |
|
|
220
|
+
| `options` | array | Checkbox options; `{ value, label }[]` | `[{ value: "read", label: "Can Read" }, { value: "write", label: "Can Write" }]` |
|
|
221
|
+
| `multiple` | boolean | Allow selecting multiple values | `true` (default: `true`); affects how data is stored |
|
|
194
222
|
|
|
195
|
-
|
|
196
|
-
|-----|------|-------------|
|
|
197
|
-
| `countriesList` | boolean | Show country selector |
|
|
198
|
-
| `defaultCountry` | string | ISO country code (e.g., `US`) |
|
|
199
|
-
| `search` | boolean | Enable searching countries |
|
|
200
|
-
| `placeholder` | string | Placeholder text |
|
|
223
|
+
##### `"radio"` Field
|
|
201
224
|
|
|
202
|
-
|
|
225
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
226
|
+
| -- | -- | -------------------- | -- |
|
|
227
|
+
| `options` | array | Radio button options; `{ value, label }[]` | `[{ value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "other", label: "Other" }]` |
|
|
203
228
|
|
|
204
|
-
|
|
205
|
-
|-----|------|-------------|
|
|
206
|
-
| `rows` | number | Number of visible rows |
|
|
229
|
+
##### `"switch"` Field
|
|
207
230
|
|
|
208
|
-
|
|
231
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
232
|
+
| -- | -- | --------- | -- |
|
|
233
|
+
| `text` | string | Description/label shown next to switch | `"Enable notifications"`, `"Is admin"` |
|
|
234
|
+
| `options` | array | Optional radio-like options (radio fallback) | `[{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]` |
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
|-----|------|-------------|
|
|
212
|
-
| `accept` | string | MIME type filter (default: `image/*`) |
|
|
213
|
-
| `dragDrop` | boolean | Enable drag-and-drop upload |
|
|
236
|
+
##### `"phone"` Field
|
|
214
237
|
|
|
215
|
-
|
|
238
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
239
|
+
| ---- | -- | --------- | - |
|
|
240
|
+
| `countriesList` | boolean | Show country selector dropdown | `true`, `false` (default: `false`) |
|
|
241
|
+
| `defaultCountry` | string | Default country ISO code | `"US"`, `"GB"`, `"IN"`, `"CA"`, `"AU"` |
|
|
242
|
+
| `search` | boolean | Enable searching countries in selector | `true`, `false` (default: `false`) |
|
|
243
|
+
| `placeholder` | string | Placeholder text | `"+1 (555) 000-0000"` |
|
|
216
244
|
|
|
217
|
-
|
|
218
|
-
|-----|------|-------------|
|
|
219
|
-
| `editorKey` | string | TinyMCE API key |
|
|
220
|
-
| `fontFamily` | string | Default font family |
|
|
221
|
-
| `height` | number | Editor height in pixels |
|
|
222
|
-
| `imageUploadHandler` | function | `(blobInfo) => Promise<string>`. Returns image URL |
|
|
245
|
+
##### `"textarea"` Field
|
|
223
246
|
|
|
224
|
-
|
|
247
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
248
|
+
| - | -- | -- | - |
|
|
249
|
+
| `rows` | number | Number of visible rows | `3`, `5`, `10` |
|
|
225
250
|
|
|
226
|
-
|
|
227
|
-
|-----|------|-------------|
|
|
228
|
-
| `options` | array | `{ value, label }[]` |
|
|
251
|
+
##### `"image"` Field
|
|
229
252
|
|
|
230
|
-
|
|
253
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
254
|
+
| - | -- | -- | - |
|
|
255
|
+
| `accept` | string | MIME type filter | `"image/*"` (default), `"image/jpeg,image/png"` |
|
|
256
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
257
|
+
| `cropImage` | boolean | Enable image cropping modal | `true`, `false` (default: `false`) |
|
|
258
|
+
| `aspectRatio` | number | Crop aspect ratio (width:height) | `1` (1:1 square), `16/9`, `4/3`, `1.5` |
|
|
231
259
|
|
|
232
|
-
|
|
233
|
-
|-----|------|-------------|
|
|
234
|
-
| `accept` | string | MIME type filter (default: `video/*`) |
|
|
235
|
-
| `dragDrop` | boolean | Enable drag-and-drop upload |
|
|
236
|
-
| `maxSize` | number | Maximum file size in bytes |
|
|
260
|
+
##### `"video"` Field
|
|
237
261
|
|
|
238
|
-
|
|
262
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
263
|
+
| - | -- | -- | - |
|
|
264
|
+
| `accept` | string | MIME type filter | `"video/*"` (default), `"video/mp4,video/webm"` |
|
|
265
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
266
|
+
| `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
|
|
239
267
|
|
|
240
|
-
|
|
241
|
-
|-----|------|-------------|
|
|
242
|
-
| `renderType` | string | Rendering type for group fields |
|
|
268
|
+
##### `"audio"` Field
|
|
243
269
|
|
|
244
|
-
|
|
270
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
271
|
+
| - | -- | -- | - |
|
|
272
|
+
| `accept` | string | MIME type filter | `"audio/*"` (default), `"audio/mpeg,audio/wav"` |
|
|
273
|
+
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
274
|
+
| `maxSize` | number | Maximum file size in bytes | `5242880` (5MB), `10485760` (10MB) |
|
|
245
275
|
|
|
246
|
-
|
|
247
|
-
|-----|------|-------------|
|
|
248
|
-
| `renderType` | string | Rendering type for card group fields |
|
|
276
|
+
##### `"tinyEditor"` Field
|
|
249
277
|
|
|
250
|
-
|
|
278
|
+
| Property | Type | Description | Accepted Values / Example |
|
|
279
|
+
| --------- | ------ | ------------ | - |
|
|
280
|
+
| `editorKey` | string | TinyMCE API key (required) | Get from `import.meta.env.VITE_EDITOR_KEY` or pass directly |
|
|
281
|
+
| `fontFamily` | string | Default font family in editor | `"Arial"`, `"Georgia"`, `"Courier New"` |
|
|
282
|
+
| `height` | number | Editor height in pixels | `300`, `500` |
|
|
283
|
+
| `imageUploadHandler` | function | Async image upload handler | `async (blobInfo) => Promise<string>` (returns image URL) |
|
|
251
284
|
|
|
252
|
-
`
|
|
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 |
|
|
253
291
|
|
|
254
292
|
---
|
|
255
293
|
|
|
256
|
-
###
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
|
268
|
-
|
|
269
|
-
| `variant`
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `disabled` | boolean | `false` | Disable button interaction |
|
|
277
|
-
|
|
278
|
-
#### Chip Props
|
|
279
|
-
|
|
280
|
-
| Prop | Type | Default | Description |
|
|
281
|
-
|------|------|---------|-------------|
|
|
282
|
-
| `label` | string | Required | Badge text |
|
|
283
|
-
| `variant` | string | `contained` | Style variant: `contained`, `outline`, `soft` |
|
|
284
|
-
| `color` | string | `blue` | Color: `blue`, `teal`, `purple`, `yellow`, `green`, `red`, `gray` |
|
|
285
|
-
|
|
286
|
-
#### Modal Props (Direct Usage)
|
|
287
|
-
|
|
288
|
-
| Prop | Type | Description |
|
|
289
|
-
|------|------|-------------|
|
|
290
|
-
| `isOpen` | boolean | Show/hide modal |
|
|
291
|
-
| `onClose` | function | Callback when modal closes |
|
|
292
|
-
| `icon` | React node | Icon element displayed in modal header |
|
|
293
|
-
| `title` | string | Modal title |
|
|
294
|
-
| `size` | string | Size: `sm`, `md`, `lg`, `xl`, `full` |
|
|
295
|
-
| `actionButtons` | array | `[{ type, label, color, variant, onClick, disabled }]` |
|
|
296
|
-
| `loadingBtn` | boolean | Show loading state on action button |
|
|
297
|
-
|
|
298
|
-
#### FilterDrawer Props
|
|
299
|
-
|
|
300
|
-
| Prop | Type | Description |
|
|
301
|
-
|------|------|-------------|
|
|
302
|
-
| `isOpen` | boolean | Show/hide filter drawer |
|
|
303
|
-
| `onClose` | function | Callback when drawer closes |
|
|
304
|
-
| `config` | object | Fields array using `formFieldType` schema |
|
|
305
|
-
| `onApply` | function | Callback: `(filters: object) => void` |
|
|
294
|
+
### View Field Schema
|
|
295
|
+
|
|
296
|
+
Used by `viewModal.fields` to display data in view/details modals.
|
|
297
|
+
|
|
298
|
+
| Key | Type | Description | Accepted Values / Example |
|
|
299
|
+
| ----- | -- | - | - |
|
|
300
|
+
| `key` | string | Property name to display | `"username"`, `"email"`, `"birth_date"` |
|
|
301
|
+
| `label` | string | Display label for the field | `"User Name"`, `"Email Address"` |
|
|
302
|
+
| `type` | string | Display type (similar to form field types) | `"text"`, `"date"`, `"chip"`, `"image"`, `"avatar"`, `"group"`, `"cardGroup"` |
|
|
303
|
+
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` |
|
|
304
|
+
| `imageKey` | string | Image property for avatar types | `"profileImage"`, `"avatarUrl"` |
|
|
305
|
+
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` |
|
|
306
|
+
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` |
|
|
307
|
+
| `variant` | string | Chip display variant | `"contained"`, `"outline"`, `"soft"` |
|
|
308
|
+
| `chipOptions` | array | Map values to chip labels and colors | `[{ value: "active", label: "Active", color: "green" }]` |
|
|
309
|
+
| `defaultColor` | string | Default chip color | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"` |
|
|
310
|
+
| `className` | string | Custom CSS class for display element | Tailwind classes |
|
|
311
|
+
| `blockClass` | string | Custom CSS class for field block/wrapper | Tailwind classes |
|
|
312
|
+
| `icon` | ReactNode | Icon element to display with field | `<UserIcon />`, `<MailIcon />` |
|
|
313
|
+
| `renderCondition` | function | Show field based on data | `(data: Record<string, any>) => boolean` |
|
|
306
314
|
|
|
307
315
|
---
|
|
308
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` |
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Advanced Features
|
|
469
|
+
|
|
470
|
+
### 1. Export CSV
|
|
471
|
+
|
|
472
|
+
Export table data as a CSV file with custom field selection:
|
|
473
|
+
|
|
474
|
+
```js
|
|
475
|
+
const config = {
|
|
476
|
+
// ... other config
|
|
477
|
+
tableConfig: {
|
|
478
|
+
table_head: [...],
|
|
479
|
+
data: [...]
|
|
480
|
+
exportCSV: {
|
|
481
|
+
enabled: true,
|
|
482
|
+
fileName: "users_export.csv",
|
|
483
|
+
fields: [
|
|
484
|
+
{ label: "ID", key: "id" },
|
|
485
|
+
{ label: "Name", key: "name" },
|
|
486
|
+
{ label: "Email", key: "email" },
|
|
487
|
+
{ label: "Status", key: "status" }
|
|
488
|
+
]
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### 2. Conditional Field Rendering
|
|
495
|
+
|
|
496
|
+
Show/hide form fields based on other field values using `renderCondition`:
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
const formFields = [
|
|
500
|
+
{
|
|
501
|
+
key: "userType",
|
|
502
|
+
label: "User Type",
|
|
503
|
+
type: "select",
|
|
504
|
+
options: [
|
|
505
|
+
{ value: "admin", label: "Administrator" },
|
|
506
|
+
{ value: "user", label: "Regular User" }
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
key: "adminLevel",
|
|
511
|
+
label: "Admin Level",
|
|
512
|
+
type: "select",
|
|
513
|
+
renderCondition: (formData) => formData.userType === "admin", // Only show if userType is admin
|
|
514
|
+
options: [
|
|
515
|
+
{ value: "superadmin", label: "Super Admin" },
|
|
516
|
+
{ value: "moderator", label: "Moderator" }
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
key: "department",
|
|
521
|
+
label: "Department",
|
|
522
|
+
type: "text",
|
|
523
|
+
renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
|
|
524
|
+
}
|
|
525
|
+
];
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 3. Custom Field Validation
|
|
529
|
+
|
|
530
|
+
Add custom validation logic to form fields:
|
|
531
|
+
|
|
532
|
+
```js
|
|
533
|
+
const formFields = [
|
|
534
|
+
{
|
|
535
|
+
key: "email",
|
|
536
|
+
label: "Email",
|
|
537
|
+
type: "email",
|
|
538
|
+
customValidation: (value) => {
|
|
539
|
+
// Return true/false or custom error message
|
|
540
|
+
if (!value.includes("@company.com")) {
|
|
541
|
+
return "Email must be a company email"; // Error message
|
|
542
|
+
}
|
|
543
|
+
return true; // Valid
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
key: "age",
|
|
548
|
+
label: "Age",
|
|
549
|
+
type: "number",
|
|
550
|
+
customValidation: (value) => {
|
|
551
|
+
if (value < 18) return "Must be 18 or older";
|
|
552
|
+
if (value > 120) return "Please enter a valid age";
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
];
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### 4. Custom Table Cell Rendering
|
|
560
|
+
|
|
561
|
+
Use the `render` function for complex cell display:
|
|
562
|
+
|
|
563
|
+
```js
|
|
564
|
+
const tableConfig = {
|
|
565
|
+
table_head: [
|
|
566
|
+
{ key: "id", title: "ID", type: "index" },
|
|
567
|
+
{
|
|
568
|
+
key: "name",
|
|
569
|
+
title: "Name",
|
|
570
|
+
render: (row, rowIndex) => (
|
|
571
|
+
<div className="font-bold text-blue-600">{row.name.toUpperCase()}</div>
|
|
572
|
+
)
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
key: "price",
|
|
576
|
+
title: "Price",
|
|
577
|
+
render: (row) => (
|
|
578
|
+
<span className="text-green-600 font-semibold">
|
|
579
|
+
${row.price.toFixed(2)}
|
|
580
|
+
</span>
|
|
581
|
+
)
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
key: "actions",
|
|
585
|
+
title: "Actions",
|
|
586
|
+
render: (row) => (
|
|
587
|
+
<button onClick={() => console.log(row)}>
|
|
588
|
+
Custom Action
|
|
589
|
+
</button>
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
],
|
|
593
|
+
data: [...]
|
|
594
|
+
};
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### 5. View Modal Variants
|
|
598
|
+
|
|
599
|
+
Display details in different layouts with view modal variants:
|
|
600
|
+
|
|
601
|
+
```js
|
|
602
|
+
// Default variant - standard grid layout
|
|
603
|
+
const config = {
|
|
604
|
+
modalConfig: {
|
|
605
|
+
viewModal: {
|
|
606
|
+
title: "User Details",
|
|
607
|
+
variant: "default", // Standard grid layout
|
|
608
|
+
fields: [
|
|
609
|
+
{ key: "name", label: "Full Name", type: "text" },
|
|
610
|
+
{ key: "email", label: "Email", type: "text" }
|
|
611
|
+
]
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// Card variant - each field in an elevated card
|
|
617
|
+
const cardConfig = {
|
|
618
|
+
modalConfig: {
|
|
619
|
+
viewModal: {
|
|
620
|
+
title: "User Details",
|
|
621
|
+
variant: "card", // Each field is a separate card
|
|
622
|
+
styles: {
|
|
623
|
+
containerClass: "grid grid-cols-12 gap-4",
|
|
624
|
+
cardGroupClass: "col-span-6 bg-white rounded-lg shadow p-4"
|
|
625
|
+
},
|
|
626
|
+
fields: [...]
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// Split variant - property sheet style with dividing lines
|
|
632
|
+
const splitConfig = {
|
|
633
|
+
modalConfig: {
|
|
634
|
+
viewModal: {
|
|
635
|
+
title: "User Details",
|
|
636
|
+
variant: "split", // Clean property-sheet layout
|
|
637
|
+
fields: [...]
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### 6. Custom View Component
|
|
644
|
+
|
|
645
|
+
Use a fully custom component for the view modal:
|
|
646
|
+
|
|
647
|
+
```js
|
|
648
|
+
const CustomUserView = ({ data }) => (
|
|
649
|
+
<div className="space-y-4">
|
|
650
|
+
<div className="flex items-center gap-4">
|
|
651
|
+
<img
|
|
652
|
+
src={data.avatarUrl}
|
|
653
|
+
alt={data.name}
|
|
654
|
+
className="w-16 h-16 rounded-full"
|
|
655
|
+
/>
|
|
656
|
+
<div>
|
|
657
|
+
<h2 className="text-xl font-bold">{data.name}</h2>
|
|
658
|
+
<p className="text-gray-600">{data.email}</p>
|
|
659
|
+
</div>
|
|
660
|
+
</div>
|
|
661
|
+
<div className="grid grid-cols-2 gap-4">
|
|
662
|
+
<div>
|
|
663
|
+
<label className="text-sm font-semibold">Phone</label>
|
|
664
|
+
<p>{data.phone}</p>
|
|
665
|
+
</div>
|
|
666
|
+
<div>
|
|
667
|
+
<label className="text-sm font-semibold">Status</label>
|
|
668
|
+
<p>{data.status}</p>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const config = {
|
|
675
|
+
modalConfig: {
|
|
676
|
+
viewModal: {
|
|
677
|
+
title: "User Details",
|
|
678
|
+
component: CustomUserView // Use custom component
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### 7. Row Click Handler
|
|
685
|
+
|
|
686
|
+
Handle clicks on table rows:
|
|
687
|
+
|
|
688
|
+
```js
|
|
689
|
+
const config = {
|
|
690
|
+
tableConfig: {
|
|
691
|
+
table_head: [...],
|
|
692
|
+
data: [...],
|
|
693
|
+
rowClick: (row, rowIndex) => {
|
|
694
|
+
console.log(`Row ${rowIndex} clicked:`, row);
|
|
695
|
+
// Open custom drawer, navigate, or perform any action
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 8. Server-Side Filtering
|
|
702
|
+
|
|
703
|
+
Implement server-side filtering with custom filter fields:
|
|
704
|
+
|
|
705
|
+
```js
|
|
706
|
+
const config = {
|
|
707
|
+
tableConfig: {
|
|
708
|
+
filter: {
|
|
709
|
+
enabled: true,
|
|
710
|
+
useServerSideFilters: true // Enable server-side filtering
|
|
711
|
+
},
|
|
712
|
+
filterConfig: {
|
|
713
|
+
fields: [
|
|
714
|
+
{
|
|
715
|
+
key: "status",
|
|
716
|
+
label: "Status",
|
|
717
|
+
type: "select",
|
|
718
|
+
options: [
|
|
719
|
+
{ value: "active", label: "Active" },
|
|
720
|
+
{ value: "inactive", label: "Inactive" }
|
|
721
|
+
]
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
key: "createdFrom",
|
|
725
|
+
label: "Created From",
|
|
726
|
+
type: "date"
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
key: "createdTo",
|
|
730
|
+
label: "Created To",
|
|
731
|
+
type: "date"
|
|
732
|
+
}
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
onFilterApply: (filters) => {
|
|
736
|
+
console.log("Filters applied:", filters);
|
|
737
|
+
// Filters will be passed to fetchData as additional params
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
fetchData: async ({
|
|
741
|
+
search,
|
|
742
|
+
rows_per_page,
|
|
743
|
+
current_page,
|
|
744
|
+
sort_by,
|
|
745
|
+
sort_order,
|
|
746
|
+
...filters // Additional filter params passed here
|
|
747
|
+
}) => {
|
|
748
|
+
const resp = await api.get("/users", {
|
|
749
|
+
params: {
|
|
750
|
+
q: search,
|
|
751
|
+
limit: rows_per_page,
|
|
752
|
+
page: current_page,
|
|
753
|
+
sort_by,
|
|
754
|
+
sort_order,
|
|
755
|
+
...filters // Include filters in API call
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
return { data: resp.items, pagination: resp.pagination };
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### 9. Input Masking
|
|
764
|
+
|
|
765
|
+
Format input with masks using pattern syntax:
|
|
766
|
+
|
|
767
|
+
```js
|
|
768
|
+
const formFields = [
|
|
769
|
+
{
|
|
770
|
+
key: "phone",
|
|
771
|
+
label: "Phone Number",
|
|
772
|
+
type: "text",
|
|
773
|
+
mask: "(99) 99999-9999", // Mask pattern
|
|
774
|
+
maskApplyOnValue: true, // Apply mask to default value
|
|
775
|
+
placeholder: "(00) 00000-0000"
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
key: "zipCode",
|
|
779
|
+
label: "ZIP Code",
|
|
780
|
+
type: "text",
|
|
781
|
+
mask: "99999-999", // Pattern: 9 = digit, A = letter, X = alphanumeric, * = any
|
|
782
|
+
placeholder: "00000-000"
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
key: "creditCard",
|
|
786
|
+
label: "Credit Card",
|
|
787
|
+
type: "text",
|
|
788
|
+
mask: "9999 9999 9999 9999",
|
|
789
|
+
placeholder: "0000 0000 0000 0000"
|
|
790
|
+
}
|
|
791
|
+
];
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Mask Pattern Reference:**
|
|
795
|
+
- `9` — Digit (0-9)
|
|
796
|
+
- `A` — Letter (a-zA-Z)
|
|
797
|
+
- `X` — Alphanumeric (a-zA-Z0-9)
|
|
798
|
+
- `*` — Any character
|
|
799
|
+
- Any other character is literal (e.g., `-`, `/`, ` `)
|
|
800
|
+
|
|
801
|
+
### 10. Image Cropping
|
|
802
|
+
|
|
803
|
+
Enable image cropping in image picker:
|
|
804
|
+
|
|
805
|
+
```js
|
|
806
|
+
const formFields = [
|
|
807
|
+
{
|
|
808
|
+
key: "profileImage",
|
|
809
|
+
label: "Profile Picture",
|
|
810
|
+
type: "image",
|
|
811
|
+
cropImage: true, // Enable cropping modal
|
|
812
|
+
aspectRatio: 1, // 1:1 square ratio
|
|
813
|
+
dragDrop: true
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
key: "bannerImage",
|
|
817
|
+
label: "Banner Image",
|
|
818
|
+
type: "image",
|
|
819
|
+
cropImage: true,
|
|
820
|
+
aspectRatio: 16 / 9, // 16:9 widescreen ratio
|
|
821
|
+
dragDrop: true
|
|
822
|
+
}
|
|
823
|
+
];
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### 11. Notifications/Snackbar
|
|
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
|
|
855
|
+
|
|
856
|
+
Configure sorting with custom options and auto-generation:
|
|
857
|
+
|
|
858
|
+
```js
|
|
859
|
+
const config = {
|
|
860
|
+
tableConfig: {
|
|
861
|
+
sort: {
|
|
862
|
+
enabled: true,
|
|
863
|
+
useServerSideSorting: false, // Client-side sorting
|
|
864
|
+
autoGenerate: true, // Auto-generate sort options from table headers
|
|
865
|
+
fields: ["name", "email", "createdAt"], // Fields to make sortable
|
|
866
|
+
defaultValue: "name", // Default sort field
|
|
867
|
+
clearLabel: "Clear Sort",
|
|
868
|
+
onChange: (payload) => {
|
|
869
|
+
console.log("Sort changed:", payload);
|
|
870
|
+
// payload contains: { value, option, key, order, type }
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
table_head: [
|
|
874
|
+
{ key: "name", title: "Name" },
|
|
875
|
+
{ key: "email", title: "Email" },
|
|
876
|
+
{ key: "createdAt", title: "Created Date" }
|
|
877
|
+
]
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
```
|
|
881
|
+
|
|
309
882
|
### Examples
|
|
310
|
-
|
|
883
|
+
|
|
884
|
+
#### Example 1: Minimal Client-Side CRUD
|
|
885
|
+
|
|
886
|
+
```js
|
|
887
|
+
import Crud from 'react-admin-crud-manager';
|
|
888
|
+
|
|
889
|
+
const users = [
|
|
890
|
+
{ id: 1, name: "John Doe", email: "john@example.com", status: "active" },
|
|
891
|
+
{ id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" }
|
|
892
|
+
];
|
|
893
|
+
|
|
894
|
+
function App() {
|
|
895
|
+
const config = {
|
|
896
|
+
title: "Users",
|
|
897
|
+
fetchData: async () => ({ data: users, pagination: null }),
|
|
898
|
+
isStaticData: true,
|
|
899
|
+
tableConfig: {
|
|
900
|
+
table_head: [
|
|
901
|
+
{ key: "id", title: "ID", type: "index" },
|
|
902
|
+
{ key: "name", title: "Name" },
|
|
903
|
+
{ key: "email", title: "Email" },
|
|
904
|
+
{ key: "status", title: "Status", type: "chip", chipOptions: [
|
|
905
|
+
{ value: "active", label: "Active", color: "green" },
|
|
906
|
+
{ value: "inactive", label: "Inactive", color: "red" }
|
|
907
|
+
]}
|
|
908
|
+
],
|
|
909
|
+
search: { enabled: true },
|
|
910
|
+
pagination: { enabled: true, rows_per_page: 10 }
|
|
911
|
+
},
|
|
912
|
+
modalConfig: {
|
|
913
|
+
addModal: {
|
|
914
|
+
title: "Add User",
|
|
915
|
+
formFields: [
|
|
916
|
+
{ key: "name", label: "Name", type: "text", required: true },
|
|
917
|
+
{ key: "email", label: "Email", type: "email", required: true },
|
|
918
|
+
{
|
|
919
|
+
key: "status",
|
|
920
|
+
label: "Status",
|
|
921
|
+
type: "select",
|
|
922
|
+
options: [
|
|
923
|
+
{ value: "active", label: "Active" },
|
|
924
|
+
{ value: "inactive", label: "Inactive" }
|
|
925
|
+
]
|
|
926
|
+
}
|
|
927
|
+
],
|
|
928
|
+
handleSubmit: async (formData) => ({
|
|
929
|
+
newObject: { ...formData, id: Math.max(...users.map(u => u.id)) + 1 }
|
|
930
|
+
})
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
return <Crud config={config} />;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
export default App;
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### Example 2: Server-Side CRUD with Advanced Features
|
|
942
|
+
|
|
943
|
+
```js
|
|
944
|
+
import Crud from 'react-admin-crud-manager';
|
|
945
|
+
import axios from 'axios';
|
|
946
|
+
import { PlusIcon, EditIcon, TrashIcon } from 'lucide-react';
|
|
947
|
+
|
|
948
|
+
function App() {
|
|
949
|
+
const api = axios.create({ baseURL: 'https://api.example.com' });
|
|
950
|
+
|
|
951
|
+
const config = {
|
|
952
|
+
title: "Products",
|
|
953
|
+
description: "Manage your products inventory",
|
|
954
|
+
buttonText: "Add Product",
|
|
955
|
+
fetchData: async ({
|
|
956
|
+
search,
|
|
957
|
+
rows_per_page,
|
|
958
|
+
current_page,
|
|
959
|
+
sort_by,
|
|
960
|
+
sort_order,
|
|
961
|
+
category,
|
|
962
|
+
minPrice,
|
|
963
|
+
maxPrice
|
|
964
|
+
}) => {
|
|
965
|
+
const resp = await api.get("/products", {
|
|
966
|
+
params: {
|
|
967
|
+
q: search,
|
|
968
|
+
limit: rows_per_page,
|
|
969
|
+
page: current_page,
|
|
970
|
+
sort_by,
|
|
971
|
+
sort_order,
|
|
972
|
+
category,
|
|
973
|
+
minPrice,
|
|
974
|
+
maxPrice
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
return {
|
|
978
|
+
data: resp.data.items,
|
|
979
|
+
pagination: {
|
|
980
|
+
current_page: resp.data.page,
|
|
981
|
+
rows_per_page: resp.data.limit,
|
|
982
|
+
total_pages: resp.data.totalPages,
|
|
983
|
+
total_records: resp.data.total
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
},
|
|
987
|
+
tableConfig: {
|
|
988
|
+
table_head: [
|
|
989
|
+
{ key: "id", title: "ID", type: "index" },
|
|
990
|
+
{ key: "image", title: "Image", type: "avatar", imageKey: "image", titleKey: "name" },
|
|
991
|
+
{ key: "name", title: "Product Name" },
|
|
992
|
+
{ key: "price", title: "Price", render: (row) => `$${row.price.toFixed(2)}` },
|
|
993
|
+
{
|
|
994
|
+
key: "category",
|
|
995
|
+
title: "Category",
|
|
996
|
+
type: "chip",
|
|
997
|
+
variant: "soft",
|
|
998
|
+
chipOptions: [
|
|
999
|
+
{ value: "electronics", label: "Electronics", color: "blue" },
|
|
1000
|
+
{ value: "clothing", label: "Clothing", color: "purple" }
|
|
1001
|
+
]
|
|
1002
|
+
}
|
|
1003
|
+
],
|
|
1004
|
+
search: { enabled: true, useServerSideSearch: true, searchKeys: ["name", "sku"] },
|
|
1005
|
+
filter: { enabled: true, useServerSideFilters: true },
|
|
1006
|
+
pagination: { enabled: true, useServerSidePagination: true },
|
|
1007
|
+
sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
|
|
1008
|
+
exportCSV: {
|
|
1009
|
+
enabled: true,
|
|
1010
|
+
fileName: "products.csv",
|
|
1011
|
+
fields: [
|
|
1012
|
+
{ label: "ID", key: "id" },
|
|
1013
|
+
{ label: "Name", key: "name" },
|
|
1014
|
+
{ label: "Price", key: "price" }
|
|
1015
|
+
]
|
|
1016
|
+
},
|
|
1017
|
+
filterConfig: {
|
|
1018
|
+
fields: [
|
|
1019
|
+
{
|
|
1020
|
+
key: "category",
|
|
1021
|
+
label: "Category",
|
|
1022
|
+
type: "select",
|
|
1023
|
+
options: [
|
|
1024
|
+
{ value: "electronics", label: "Electronics" },
|
|
1025
|
+
{ value: "clothing", label: "Clothing" }
|
|
1026
|
+
]
|
|
1027
|
+
},
|
|
1028
|
+
{ key: "minPrice", label: "Min Price", type: "number" },
|
|
1029
|
+
{ key: "maxPrice", label: "Max Price", type: "number" }
|
|
1030
|
+
]
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
modalConfig: {
|
|
1034
|
+
addModal: {
|
|
1035
|
+
title: "Add Product",
|
|
1036
|
+
size: "lg",
|
|
1037
|
+
icon: <PlusIcon />,
|
|
1038
|
+
formFields: [
|
|
1039
|
+
{ key: "name", label: "Product Name", type: "text", required: true, parentClass: "col-span-12" },
|
|
1040
|
+
{ key: "price", label: "Price", type: "number", required: true, parentClass: "col-span-6" },
|
|
1041
|
+
{
|
|
1042
|
+
key: "category",
|
|
1043
|
+
label: "Category",
|
|
1044
|
+
type: "select",
|
|
1045
|
+
required: true,
|
|
1046
|
+
parentClass: "col-span-6",
|
|
1047
|
+
options: [
|
|
1048
|
+
{ value: "electronics", label: "Electronics" },
|
|
1049
|
+
{ value: "clothing", label: "Clothing" }
|
|
1050
|
+
]
|
|
1051
|
+
},
|
|
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
|
+
{
|
|
1055
|
+
key: "image",
|
|
1056
|
+
label: "Product Image",
|
|
1057
|
+
type: "image",
|
|
1058
|
+
cropImage: true,
|
|
1059
|
+
aspectRatio: 1,
|
|
1060
|
+
dragDrop: true,
|
|
1061
|
+
parentClass: "col-span-12"
|
|
1062
|
+
}
|
|
1063
|
+
],
|
|
1064
|
+
handleSubmit: async (formData) => {
|
|
1065
|
+
const resp = await api.post("/products", formData);
|
|
1066
|
+
return { newObject: resp.data, message: "Product added successfully!" };
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
editModal: {
|
|
1070
|
+
title: "Edit Product",
|
|
1071
|
+
size: "lg",
|
|
1072
|
+
icon: <EditIcon />,
|
|
1073
|
+
formFields: [
|
|
1074
|
+
{ key: "name", label: "Product Name", type: "text", required: true, parentClass: "col-span-12" },
|
|
1075
|
+
{ key: "price", label: "Price", type: "number", required: true, parentClass: "col-span-6" },
|
|
1076
|
+
{ key: "stock", label: "Stock", type: "number", required: true, parentClass: "col-span-6" }
|
|
1077
|
+
],
|
|
1078
|
+
handleSubmit: async (formData, item) => {
|
|
1079
|
+
const resp = await api.put(`/products/${item.id}`, formData);
|
|
1080
|
+
return { newObject: resp.data, targetObject: item };
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
deleteModal: {
|
|
1084
|
+
title: "Delete Product",
|
|
1085
|
+
icon: <TrashIcon />,
|
|
1086
|
+
confirmText: "Are you sure you want to delete this product?",
|
|
1087
|
+
referenceKey: "name",
|
|
1088
|
+
action: async (item) => {
|
|
1089
|
+
await api.delete(`/products/${item.id}`);
|
|
1090
|
+
return { targetObject: item };
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
viewModal: {
|
|
1094
|
+
title: "Product Details",
|
|
1095
|
+
variant: "card",
|
|
1096
|
+
fields: [
|
|
1097
|
+
{ key: "id", label: "ID" },
|
|
1098
|
+
{ key: "name", label: "Product Name" },
|
|
1099
|
+
{ key: "price", label: "Price" },
|
|
1100
|
+
{ key: "stock", label: "Stock" },
|
|
1101
|
+
{ key: "category", label: "Category", type: "chip" }
|
|
1102
|
+
]
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
return <Crud config={config} />;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
export default App;
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
#### Example 3: Complex Form with Conditional Fields and Validation
|
|
1114
|
+
|
|
1115
|
+
```js
|
|
1116
|
+
const config = {
|
|
1117
|
+
title: "Advanced User Registration",
|
|
1118
|
+
tableConfig: {
|
|
1119
|
+
table_head: [
|
|
1120
|
+
{ key: "id", title: "ID", type: "index" },
|
|
1121
|
+
{ key: "name", title: "Full Name" },
|
|
1122
|
+
{ key: "email", title: "Email" },
|
|
1123
|
+
{ key: "userType", title: "Type", type: "chip" }
|
|
1124
|
+
]
|
|
1125
|
+
},
|
|
1126
|
+
modalConfig: {
|
|
1127
|
+
addModal: {
|
|
1128
|
+
title: "Register New User",
|
|
1129
|
+
formFields: [
|
|
1130
|
+
{
|
|
1131
|
+
key: "name",
|
|
1132
|
+
label: "Full Name",
|
|
1133
|
+
type: "text",
|
|
1134
|
+
required: true,
|
|
1135
|
+
parentClass: "col-span-12",
|
|
1136
|
+
customValidation: (value) => {
|
|
1137
|
+
if (value.trim().split(" ").length < 2) {
|
|
1138
|
+
return "Please enter your full name";
|
|
1139
|
+
}
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
key: "email",
|
|
1145
|
+
label: "Email Address",
|
|
1146
|
+
type: "email",
|
|
1147
|
+
required: true,
|
|
1148
|
+
parentClass: "col-span-12",
|
|
1149
|
+
customValidation: (value) => {
|
|
1150
|
+
if (!value.includes("@company.com")) {
|
|
1151
|
+
return "Must use company email";
|
|
1152
|
+
}
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
key: "phone",
|
|
1158
|
+
label: "Phone",
|
|
1159
|
+
type: "phone",
|
|
1160
|
+
countriesList: true,
|
|
1161
|
+
mask: "(99) 99999-9999",
|
|
1162
|
+
parentClass: "col-span-12"
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
key: "userType",
|
|
1166
|
+
label: "User Type",
|
|
1167
|
+
type: "select",
|
|
1168
|
+
required: true,
|
|
1169
|
+
parentClass: "col-span-6",
|
|
1170
|
+
options: [
|
|
1171
|
+
{ value: "admin", label: "Administrator" },
|
|
1172
|
+
{ value: "user", label: "Regular User" }
|
|
1173
|
+
]
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
key: "adminLevel",
|
|
1177
|
+
label: "Admin Level",
|
|
1178
|
+
type: "select",
|
|
1179
|
+
parentClass: "col-span-6",
|
|
1180
|
+
renderCondition: (data) => data.userType === "admin",
|
|
1181
|
+
options: [
|
|
1182
|
+
{ value: "superadmin", label: "Super Admin" },
|
|
1183
|
+
{ value: "moderator", label: "Moderator" }
|
|
1184
|
+
]
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
key: "permissions",
|
|
1188
|
+
label: "Permissions",
|
|
1189
|
+
type: "checkbox",
|
|
1190
|
+
parentClass: "col-span-12",
|
|
1191
|
+
multiple: true,
|
|
1192
|
+
renderCondition: (data) => data.userType === "admin",
|
|
1193
|
+
options: [
|
|
1194
|
+
{ value: "read", label: "Can Read" },
|
|
1195
|
+
{ value: "write", label: "Can Write" },
|
|
1196
|
+
{ value: "delete", label: "Can Delete" }
|
|
1197
|
+
]
|
|
1198
|
+
}
|
|
1199
|
+
],
|
|
1200
|
+
handleSubmit: async (formData) => {
|
|
1201
|
+
const resp = await api.post("/users", formData);
|
|
1202
|
+
return {
|
|
1203
|
+
newObject: resp.data,
|
|
1204
|
+
message: `User ${formData.name} created successfully!`
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
---
|
|
1213
|
+
|
|
1214
|
+
## Best Practices & Common Patterns
|
|
1215
|
+
|
|
1216
|
+
### 1. State Management with Static Data
|
|
1217
|
+
|
|
1218
|
+
For client-side CRUD operations, use `isStaticData: true` and manage data updates through modal handlers:
|
|
311
1219
|
|
|
312
1220
|
```js
|
|
1221
|
+
const [users, setUsers] = useState(initialUsers);
|
|
1222
|
+
|
|
313
1223
|
const config = {
|
|
314
|
-
title: 'Users',
|
|
315
1224
|
fetchData: async () => ({ data: users, pagination: null }),
|
|
316
1225
|
isStaticData: true,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
1226
|
+
modalConfig: {
|
|
1227
|
+
addModal: {
|
|
1228
|
+
handleSubmit: async (formData) => {
|
|
1229
|
+
const newUser = { ...formData, id: Date.now() };
|
|
1230
|
+
setUsers([...users, newUser]);
|
|
1231
|
+
return { newObject: newUser };
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
editModal: {
|
|
1235
|
+
handleSubmit: async (formData, item) => {
|
|
1236
|
+
const updated = { ...item, ...formData };
|
|
1237
|
+
setUsers(users.map(u => u.id === item.id ? updated : u));
|
|
1238
|
+
return { newObject: updated, targetObject: item };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
### 2. Server-Side Operations Best Practices
|
|
1246
|
+
|
|
1247
|
+
```js
|
|
1248
|
+
// Always provide pagination info back to the component
|
|
1249
|
+
const fetchData = async (params) => {
|
|
1250
|
+
const resp = await api.get("/items", { params });
|
|
1251
|
+
return {
|
|
1252
|
+
data: resp.data.items || [],
|
|
1253
|
+
pagination: {
|
|
1254
|
+
current_page: resp.data.meta.currentPage,
|
|
1255
|
+
rows_per_page: resp.data.meta.perPage,
|
|
1256
|
+
total_pages: resp.data.meta.totalPages,
|
|
1257
|
+
total_records: resp.data.meta.total
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
};
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
### 3. Error Handling in Modal Submissions
|
|
1264
|
+
|
|
1265
|
+
```js
|
|
1266
|
+
const handleSubmit = async (formData) => {
|
|
1267
|
+
try {
|
|
1268
|
+
const resp = await api.post("/items", formData);
|
|
1269
|
+
return {
|
|
1270
|
+
newObject: resp.data,
|
|
1271
|
+
message: "Item created successfully!" // Optional success message
|
|
1272
|
+
};
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
// Throw error to trigger snackbar error notification
|
|
1275
|
+
throw new Error(error.response?.data?.message || "Failed to create item");
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
### 4. Dynamic Form Fields Based on Data
|
|
1281
|
+
|
|
1282
|
+
Combine `renderCondition` and `defaultValue` for dynamic forms:
|
|
1283
|
+
|
|
1284
|
+
```js
|
|
1285
|
+
const formFields = [
|
|
1286
|
+
{
|
|
1287
|
+
key: "type",
|
|
1288
|
+
type: "select",
|
|
1289
|
+
options: [
|
|
1290
|
+
{ value: "personal", label: "Personal" },
|
|
1291
|
+
{ value: "business", label: "Business" }
|
|
1292
|
+
]
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
key: "companyName",
|
|
1296
|
+
label: "Company Name",
|
|
1297
|
+
type: "text",
|
|
1298
|
+
renderCondition: (data) => data.type === "business",
|
|
1299
|
+
required: (data) => data.type === "business"
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
key: "phone",
|
|
1303
|
+
type: "phone",
|
|
1304
|
+
renderCondition: (data) => data.type === "personal"
|
|
1305
|
+
}
|
|
1306
|
+
];
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
### 5. Custom Table Rendering for Complex Data
|
|
1310
|
+
|
|
1311
|
+
```js
|
|
1312
|
+
const tableConfig = {
|
|
1313
|
+
table_head: [
|
|
1314
|
+
{
|
|
1315
|
+
key: "user",
|
|
1316
|
+
title: "User",
|
|
1317
|
+
render: (row) => (
|
|
1318
|
+
<div className="flex items-center gap-3">
|
|
1319
|
+
<img src={row.avatar} className="w-8 h-8 rounded-full" />
|
|
1320
|
+
<div>
|
|
1321
|
+
<p className="font-semibold">{row.name}</p>
|
|
1322
|
+
<p className="text-xs text-gray-500">{row.email}</p>
|
|
1323
|
+
</div>
|
|
1324
|
+
</div>
|
|
1325
|
+
)
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
key: "metrics",
|
|
1329
|
+
title: "Performance",
|
|
1330
|
+
render: (row) => (
|
|
1331
|
+
<div className="flex gap-2">
|
|
1332
|
+
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
|
|
1333
|
+
{row.completed}%
|
|
1334
|
+
</span>
|
|
1335
|
+
</div>
|
|
1336
|
+
)
|
|
1337
|
+
}
|
|
1338
|
+
]
|
|
1339
|
+
};
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
### 6. Validation Patterns
|
|
1343
|
+
|
|
1344
|
+
```js
|
|
1345
|
+
const formFields = [
|
|
1346
|
+
{
|
|
1347
|
+
key: "email",
|
|
1348
|
+
type: "email",
|
|
1349
|
+
customValidation: async (value) => {
|
|
1350
|
+
// Async validation example
|
|
1351
|
+
const exists = await checkEmailExists(value);
|
|
1352
|
+
if (exists) return "Email already in use";
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
320
1355
|
},
|
|
1356
|
+
{
|
|
1357
|
+
key: "password",
|
|
1358
|
+
type: "password",
|
|
1359
|
+
customValidation: (value) => {
|
|
1360
|
+
if (value.length < 8) return "Password must be at least 8 characters";
|
|
1361
|
+
if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
|
|
1362
|
+
if (!/[0-9]/.test(value)) return "Must contain a number";
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
];
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
### 7. Handling File Uploads
|
|
1370
|
+
|
|
1371
|
+
```js
|
|
1372
|
+
const handleImageUpload = async (file) => {
|
|
1373
|
+
const formData = new FormData();
|
|
1374
|
+
formData.append("file", file);
|
|
1375
|
+
const resp = await api.post("/upload", formData);
|
|
1376
|
+
return resp.data.url;
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
const config = {
|
|
321
1380
|
modalConfig: {
|
|
322
|
-
addModal: {
|
|
1381
|
+
addModal: {
|
|
1382
|
+
formFields: [
|
|
1383
|
+
{
|
|
1384
|
+
key: "image",
|
|
1385
|
+
type: "image",
|
|
1386
|
+
dragDrop: true,
|
|
1387
|
+
cropImage: true,
|
|
1388
|
+
aspectRatio: 1
|
|
1389
|
+
}
|
|
1390
|
+
],
|
|
1391
|
+
handleSubmit: async (formData) => {
|
|
1392
|
+
// Image data includes base64 or file data
|
|
1393
|
+
const imageUrl = await handleImageUpload(formData.image);
|
|
1394
|
+
const resp = await api.post("/items", {
|
|
1395
|
+
...formData,
|
|
1396
|
+
image: imageUrl
|
|
1397
|
+
});
|
|
1398
|
+
return { newObject: resp.data };
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
323
1401
|
}
|
|
324
1402
|
};
|
|
325
1403
|
```
|
|
326
1404
|
|
|
327
|
-
|
|
1405
|
+
### 8. Conditional Rendering with Row Data
|
|
328
1406
|
|
|
329
1407
|
```js
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
1408
|
+
const viewModal = {
|
|
1409
|
+
fields: [
|
|
1410
|
+
{
|
|
1411
|
+
key: "status",
|
|
1412
|
+
label: "Status",
|
|
1413
|
+
type: "chip",
|
|
1414
|
+
renderCondition: (data) => data.status !== null,
|
|
1415
|
+
chipOptions: [
|
|
1416
|
+
{ value: "active", label: "Active", color: "green" },
|
|
1417
|
+
{ value: "suspended", label: "Suspended", color: "red" }
|
|
1418
|
+
]
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
key: "suspendReason",
|
|
1422
|
+
label: "Suspension Reason",
|
|
1423
|
+
renderCondition: (data) => data.status === "suspended" // Only show if suspended
|
|
1424
|
+
}
|
|
1425
|
+
]
|
|
1426
|
+
};
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
### 9. Multiple Sort Options (Server-Side)
|
|
1430
|
+
|
|
1431
|
+
```js
|
|
1432
|
+
const tableConfig = {
|
|
1433
|
+
sort: {
|
|
1434
|
+
enabled: true,
|
|
1435
|
+
useServerSideSorting: true,
|
|
1436
|
+
options: [
|
|
1437
|
+
{ value: "recent", label: "Most Recent", key: "createdAt", order: "desc" },
|
|
1438
|
+
{ value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
|
|
1439
|
+
{ value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
|
|
1440
|
+
{ value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
|
|
1441
|
+
{ value: "popular", label: "Most Popular", key: "views", order: "desc" }
|
|
1442
|
+
],
|
|
1443
|
+
onChange: (payload) => {
|
|
1444
|
+
console.log(`Sorted by: ${payload.option?.label}`);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
333
1447
|
};
|
|
334
1448
|
```
|
|
335
1449
|
|
|
336
1450
|
---
|
|
337
1451
|
|
|
338
1452
|
## License
|
|
1453
|
+
|
|
339
1454
|
MIT
|