react-admin-crud-manager 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +1206 -210
  2. package/dist/index.cjs.js +20 -55
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.es.js +4092 -2529
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/types/components/Details/Details.d.ts +11 -0
  7. package/dist/types/components/Details/components/CardGroup.d.ts +9 -1
  8. package/dist/types/components/Details/components/DetailRow.d.ts +9 -1
  9. package/dist/types/components/Details/components/GroupRow.d.ts +9 -1
  10. package/dist/types/components/Filter/FilterDrawer.d.ts +2 -2
  11. package/dist/types/components/Form/components/AudioPicker.d.ts +2 -1
  12. package/dist/types/components/Form/components/Checkbox.d.ts +2 -1
  13. package/dist/types/components/Form/components/ImagePicker.d.ts +2 -1
  14. package/dist/types/components/Form/components/Input.d.ts +1 -0
  15. package/dist/types/components/Form/components/MultiImagePicker.d.ts +21 -0
  16. package/dist/types/components/Form/components/PhoneInput.d.ts +2 -1
  17. package/dist/types/components/Form/components/Radio.d.ts +2 -1
  18. package/dist/types/components/Form/components/RenderFields.d.ts +3 -2
  19. package/dist/types/components/Form/components/Select.d.ts +5 -2
  20. package/dist/types/components/Form/components/Switch.d.ts +1 -0
  21. package/dist/types/components/Form/components/TextArea.d.ts +2 -0
  22. package/dist/types/components/Form/components/TinyEditor.d.ts +3 -1
  23. package/dist/types/components/Form/components/VideoPicker.d.ts +2 -1
  24. package/dist/types/components/Modal/Modal.d.ts +10 -1
  25. package/dist/types/lib/crudClasses.d.ts +94 -0
  26. package/dist/types/types/crudtypes.d.ts +51 -0
  27. package/package.json +10 -8
  28. 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. Import the built CSS for Tailwind styles
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,34 @@ function App() {
44
36
  ```
45
37
 
46
38
  ## Components
39
+
47
40
  - `Crud`: Main CRUD page component
48
- - `Table`, `Modal`, `Form`, etc.: Available for advanced use
49
41
 
50
42
  ## Props
43
+
51
44
  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
45
 
53
46
  ---
54
47
 
55
48
  ### Crud (default export)
49
+
56
50
  `<Crud config={config} />`
51
+
57
52
  - `config` (object) — required. Top-level configuration object used by the CRUD page.
58
53
 
59
54
  #### Key properties of `config`
60
55
 
61
- | Property | Type | Required | Description |
62
- |----------|------|----------|-------------|
63
- | `title` | string | Yes | Title of the CRUD page |
64
- | `description` | string | No | Optional description text |
65
- | `buttonText` | string | No | Custom text for action buttons |
66
- | `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. |
67
- | `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
68
- | `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
69
- | `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
70
- | `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
71
- | `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
56
+ | Property | Type | Required | Description |
57
+ | ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `title` | string | Yes | Title of the CRUD page |
59
+ | `description` | string | No | Optional description text |
60
+ | `buttonText` | string | No | Custom text for add button |
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. |
62
+ | `fetchRowDetails` | function | No | Async function to fetch additional details for a row. Signature: `async (item) => Promise<any>`. Used for view modal or details. |
63
+ | `isStaticData` | boolean | No | Default: `false`. If true, add/edit/delete apply client-side instead of re-fetching |
64
+ | `tableConfig` | object | Yes | Table configuration — [see tableConfig](#tableconfig-configuration) |
65
+ | `modalConfig` | object | No | Modal definitions — [see modalConfig](#modalconfig-definitions) |
66
+ | `filterConfig` | object | No | Filter drawer configuration — [see Form Field Schema](#form-field-schema) |
72
67
 
73
68
  ---
74
69
 
@@ -76,39 +71,36 @@ Below is a complete reference of the public props accepted by this package (type
76
71
 
77
72
  #### Table Configuration Keys
78
73
 
79
- | Key | Type | Required | Description |
80
- |-----|------|----------|-------------|
81
- | `table_head` | array of column objects | Yes | Column definitions [see Table column object](#table-column-object-table_head) |
82
- | `data` | array | No | Rows shown in the table |
83
- | `loading` | boolean | No | Show skeleton loader when true |
84
- | `search` | object | No | `{ enabled?: boolean, placeholder?: string, useServerSideSearch?: boolean, searchKeys?: string[] }` |
85
- | `filter` | object | No | `{ enabled?: boolean, useServerSideFilters?: boolean }` |
86
- | `pagination` | object | No | `{ enabled?: boolean, rows_per_page?: number, useServerSidePagination?: boolean, current_page?: number, total_pages?: number, total_records?: number }` |
87
- | `sort` | object | No | `{ enabled?: boolean, useServerSideSorting?: boolean, options?: Array<{ value: string, label: string, key: string, order: string, type?: string }>, fields?: string[], defaultValue?: string, autoGenerate?: boolean, clearLabel?: string, onChange?: (payload) => void }` |
88
- | `emptyMessage` | string | No | Message shown when table has no rows |
89
- | `onMenuAction` | function | No | Callback: `(actionType: string, item: object)` |
90
- | `setServerSidePaginationData` | function | No | Used to update server-side pagination/search state |
91
- | `onFilterApply` | function | No | Callback: `(filters: object)` |
92
- | `filterConfig` | object | No | Fields array rendered in the FilterDrawer |
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>
93
85
 
94
86
  #### Table Column Object (`table_head[]`)
95
87
 
96
- | Key | Type | Required | Description |
97
- |-----|------|----------|-------------|
98
- | `key` | string | Yes | Property name in row objects |
99
- | `title` | string | No | Column header text |
100
- | `type` | string | No | Column type: `plain` (default), `index`, `group`, `chip`, `date`, `avatar`, `menu_actions` |
101
- | `imageKey` | string | No | Used with `group`/`avatar` types |
102
- | `titleKey` | string | No | Used with `group`/`avatar` types |
103
- | `subtitleKey` | string | No | Used with `group`/`avatar` types |
104
- | `onClickDetails` | boolean | No | If true, clicking cell triggers view action |
105
- | `variant` | string | No | For chips: `contained`, `outline`, `soft` |
106
- | `chipOptions` | array | No | Array of `{ value: string|number|boolean, label: string, color?: string }` |
107
- | `defaultColor` | string | No | Default chip color key (e.g., `green`) |
108
- | `className` | string | No | Custom CSS class for cell |
109
- | `format` | string | No | Date format (e.g., `DD MMM YYYY`) |
110
- | `menuList` | array | No | Menu action objects: `{ title, type, variant?, icon? }` |
111
- | `render` | function | No | Custom renderer: `(row, rowIndex) => ReactNode`. Overrides built-in renderers |
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>` |
112
104
 
113
105
  ---
114
106
 
@@ -116,224 +108,1228 @@ Below is a complete reference of the public props accepted by this package (type
116
108
 
117
109
  #### Add & Edit Modal (`addModal`, `editModal`)
118
110
 
119
- | Property | Type | Required | Description |
120
- |----------|------|----------|-------------|
121
- | `title` | string | Yes | Modal title |
122
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
123
- | `formClass` | string | No | Custom CSS class for form |
124
- | `formFields` | array | Yes | Form field objects [see Form Field Schema](#form-field-schema) |
125
- | `handleSubmit` | function | Yes | Async callback: `(formData) => Promise<{ newObject, targetObject? }>`. For add: return `{ newObject }`. For edit: return `{ newObject, targetObject }` |
126
- | `actionButtons` | array | No | Action button objects: `[{ type, label, color, variant, onClick, disabled, className }]` |
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 }]` |
127
120
 
128
121
  #### Delete Modal (`deleteModal`)
129
122
 
130
- | Property | Type | Required | Description |
131
- |----------|------|----------|-------------|
132
- | `title` | string | No | Modal title |
133
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
134
- | `confirmText` | string | No | Confirmation message text |
135
- | `referenceKey` | string | No | Property key of selectedItem to display as reference |
136
- | `action` | function | Yes | Async callback: `(selectedItem) => Promise<{ targetObject }>`. Should return the deleted row |
137
- | `actionButtons` | array | No | Action button objects: `[{ type, label, color, variant, onClick, disabled, className }]` |
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 | Type | Required | Description |
142
- |----------|------|----------|-------------|
143
- | `title` | string | Yes | Modal title |
144
- | `size` | string | No | Modal size: `sm`, `md` (default), `lg`, `xl`, `full` |
145
- | `component` | React component | No | Custom component to render. Receives `data` prop with row data |
146
- | `fields` | array | No | View field objects for displaying data |
147
- | `footer` | object | No | `{ cancelButton?: boolean, cancelText?: string }` |
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" }` |
148
145
 
149
146
  ---
150
147
 
151
148
  ### Form Field Schema
152
149
 
153
- Used by `modalConfig.*.formFields` and `filterConfig.fields`. Form fields follow the `formFieldType` shape used throughout the UI.
154
-
155
- #### Common Field Keys (All Types)
150
+ Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields`. All form fields follow the `FormField` shape.
156
151
 
157
- | Key | Type | Required | Description |
158
- |-----|------|----------|-------------|
159
- | `key` | string | Yes | Property name for form data |
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 |
152
+ #### Common Field Properties (All Types)
167
153
 
168
- #### Type-Specific Keys
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"` |
169
167
 
170
- ##### Select Field
168
+ #### Type-Specific Properties
171
169
 
172
- | Key | Type | Description |
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`) |
170
+ ##### `"text"` Field
178
171
 
179
- ##### Checkbox Field
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`) |
180
177
 
181
- | Key | Type | Description |
182
- |-----|------|-------------|
183
- | `options` | array | `{ value, label }[]` |
184
- | `multiple` | boolean | Allow selecting multiple values (component prop: `multiSelect`) |
178
+ ##### `"number"` Field
185
179
 
186
- ##### Switch Field
180
+ | Property | Type | Description | Example |
181
+ | --------------------- | ------- | ---------------------- | ---------------------------------- |
182
+ | `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
187
183
 
188
- | Key | Type | Description |
189
- |-----|------|-------------|
190
- | `options` | array | Optional radio-like options: `[{ label, value }]` |
191
- | `text` | string | Description text shown next to switch |
184
+ ##### `"select"` Field
192
185
 
193
- ##### Phone Field
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"]` |
194
194
 
195
- | Key | Type | Description |
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 |
195
+ ##### `"checkbox"` Field
201
196
 
202
- ##### Textarea Field
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 |
203
201
 
204
- | Key | Type | Description |
205
- |-----|------|-------------|
206
- | `rows` | number | Number of visible rows |
202
+ ##### `"radio"` Field
207
203
 
208
- ##### Image Field
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" }]` |
209
207
 
210
- | Key | Type | Description |
211
- |-----|------|-------------|
212
- | `accept` | string | MIME type filter (default: `image/*`) |
213
- | `dragDrop` | boolean | Enable drag-and-drop upload |
208
+ ##### `"switch"` Field
214
209
 
215
- ##### TinyEditor Field
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" }]` |
216
214
 
217
- | Key | Type | Description |
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 |
215
+ ##### `"phone"` Field
223
216
 
224
- ##### Radio Field
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"` |
225
223
 
226
- | Key | Type | Description |
227
- |-----|------|-------------|
228
- | `options` | array | `{ value, label }[]` |
224
+ ##### `"textarea"` Field
229
225
 
230
- ##### Video Field
226
+ | Property | Type | Description | Accepted Values / Example |
227
+ | -------- | ------ | ---------------------- | ------------------------- |
228
+ | `rows` | number | Number of visible rows | `3`, `5`, `10` |
231
229
 
232
- | Key | Type | Description |
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 |
230
+ ##### `"image"` Field
237
231
 
238
- ##### Group Field
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` |
239
240
 
240
- | Key | Type | Description |
241
- |-----|------|-------------|
242
- | `renderType` | string | Rendering type for group fields |
241
+ ##### `"video"` Field
243
242
 
244
- ##### CardGroup Field
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) |
245
248
 
246
- | Key | Type | Description |
247
- |-----|------|-------------|
248
- | `renderType` | string | Rendering type for card group fields |
249
+ ##### `"audio"` Field
249
250
 
250
- #### Return Values / onSubmit Handler
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) |
256
+
257
+ ##### `"tinyEditor"` Field
251
258
 
252
- `onSubmit(formData)` receives an object keyed by `field.key` values.
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` |
253
264
 
254
265
  ---
255
266
 
256
- ### Small/UI components (props summary)
257
-
258
- | Component | Key Props | Purpose |
259
- |-----------|-----------|----------|
260
- | **Button** | `variant`, `color`, `size`, `fullWidth`, `className`, `onClick`, `type`, `disabled` | Reusable button with multiple style variants (`contained`, `outlined`, `text`) and colors |
261
- | **Chip** | `label`, `variant`, `color` | Display small labeled badges with `contained`, `outline`, or `soft` styles |
262
- | **Modal** | `isOpen`, `onClose`, `icon`, `title`, `size`, `actionButtons`, `loadingBtn` | Modal dialog for forms, confirmations, and views with flexible sizing |
263
- | **FilterDrawer** | `isOpen`, `onClose`, `config`, `onApply` | Side panel for applying filters with customizable form fields |
264
-
265
- #### Button Props
266
-
267
- | Prop | Type | Default | Description |
268
- |------|------|---------|-------------|
269
- | `variant` | string | `contained` | Style variant: `contained`, `outlined`, `text` |
270
- | `color` | string | `primary` | Color: `primary`, `success`, `error`, `default` |
271
- | `size` | string | `default` | Size: `sm`, `md`, `lg`, `xl`, `default` |
272
- | `fullWidth` | boolean | `false` | Stretch button to full width |
273
- | `className` | string | — | Custom CSS class |
274
- | `onClick` | function | | Click event handler |
275
- | `type` | string | `button` | HTML button type: `button`, `submit`, `reset` |
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` |
267
+ ### View Field Schema
268
+
269
+ Used by `viewModal.fields` to display data in view/details modals.
270
+
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` |
306
287
 
307
288
  ---
308
289
 
290
+ ## Advanced Features
291
+
292
+ ### 1. Export CSV
293
+
294
+ Export table data as a CSV file with custom field selection:
295
+
296
+ ```js
297
+ const config = {
298
+ // ... other config
299
+ tableConfig: {
300
+ table_head: [...],
301
+ data: [...]
302
+ exportCSV: {
303
+ enabled: true,
304
+ fileName: "users_export.csv",
305
+ fields: [
306
+ { label: "ID", key: "id" },
307
+ { label: "Name", key: "name" },
308
+ { label: "Email", key: "email" },
309
+ { label: "Status", key: "status" }
310
+ ]
311
+ }
312
+ }
313
+ };
314
+ ```
315
+
316
+ ### 2. Conditional Field Rendering
317
+
318
+ Show/hide form fields based on other field values using `renderCondition`:
319
+
320
+ ```js
321
+ const formFields = [
322
+ {
323
+ key: "userType",
324
+ label: "User Type",
325
+ type: "select",
326
+ options: [
327
+ { value: "admin", label: "Administrator" },
328
+ { value: "user", label: "Regular User" },
329
+ ],
330
+ },
331
+ {
332
+ key: "adminLevel",
333
+ label: "Admin Level",
334
+ type: "select",
335
+ renderCondition: (formData) => formData.userType === "admin", // Only show if userType is admin
336
+ options: [
337
+ { value: "superadmin", label: "Super Admin" },
338
+ { value: "moderator", label: "Moderator" },
339
+ ],
340
+ },
341
+ {
342
+ key: "department",
343
+ label: "Department",
344
+ type: "text",
345
+ renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
346
+ },
347
+ ];
348
+ ```
349
+
350
+ ### 3. Custom Field Validation
351
+
352
+ Add custom validation logic to form fields:
353
+
354
+ ```js
355
+ const formFields = [
356
+ {
357
+ key: "email",
358
+ label: "Email",
359
+ type: "email",
360
+ customValidation: (value) => {
361
+ // Return true/false or custom error message
362
+ if (!value.includes("@company.com")) {
363
+ return "Email must be a company email"; // Error message
364
+ }
365
+ return true; // Valid
366
+ },
367
+ },
368
+ {
369
+ key: "age",
370
+ label: "Age",
371
+ type: "number",
372
+ customValidation: (value) => {
373
+ if (value < 18) return "Must be 18 or older";
374
+ if (value > 120) return "Please enter a valid age";
375
+ return true;
376
+ },
377
+ },
378
+ ];
379
+ ```
380
+
381
+ ### 4. Custom Table Cell Rendering
382
+
383
+ Use the `render` function for complex cell display:
384
+
385
+ ```js
386
+ const tableConfig = {
387
+ table_head: [
388
+ { key: "id", title: "ID", type: "index" },
389
+ {
390
+ key: "name",
391
+ title: "Name",
392
+ render: (row, rowIndex) => (
393
+ <div className="font-bold text-blue-600">{row.name.toUpperCase()}</div>
394
+ )
395
+ },
396
+ {
397
+ key: "price",
398
+ title: "Price",
399
+ render: (row) => (
400
+ <span className="text-green-600 font-semibold">
401
+ ${row.price.toFixed(2)}
402
+ </span>
403
+ )
404
+ },
405
+ {
406
+ key: "actions",
407
+ title: "Actions",
408
+ render: (row) => (
409
+ <button onClick={() => console.log(row)}>
410
+ Custom Action
411
+ </button>
412
+ )
413
+ }
414
+ ],
415
+ data: [...]
416
+ };
417
+ ```
418
+
419
+ ### 5. View Modal Variants
420
+
421
+ Display details in different layouts with view modal variants:
422
+
423
+ ```js
424
+ // Default variant - standard grid layout
425
+ const config = {
426
+ modalConfig: {
427
+ viewModal: {
428
+ title: "User Details",
429
+ variant: "default", // Standard grid layout
430
+ fields: [
431
+ { key: "name", label: "Full Name", type: "text" },
432
+ { key: "email", label: "Email", type: "text" }
433
+ ]
434
+ }
435
+ }
436
+ };
437
+
438
+ // Card variant - each field in an elevated card
439
+ const cardConfig = {
440
+ modalConfig: {
441
+ viewModal: {
442
+ title: "User Details",
443
+ variant: "card", // Each field is a separate card
444
+ styles: {
445
+ containerClass: "grid grid-cols-12 gap-4",
446
+ cardGroupClass: "col-span-6 bg-white rounded-lg shadow p-4"
447
+ },
448
+ fields: [...]
449
+ }
450
+ }
451
+ };
452
+
453
+ // Split variant - property sheet style with dividing lines
454
+ const splitConfig = {
455
+ modalConfig: {
456
+ viewModal: {
457
+ title: "User Details",
458
+ variant: "split", // Clean property-sheet layout
459
+ fields: [...]
460
+ }
461
+ }
462
+ };
463
+ ```
464
+
465
+ ### 6. Custom View Component
466
+
467
+ Use a fully custom component for the view modal:
468
+
469
+ ```js
470
+ const CustomUserView = ({ data }) => (
471
+ <div className="space-y-4">
472
+ <div className="flex items-center gap-4">
473
+ <img
474
+ src={data.avatarUrl}
475
+ alt={data.name}
476
+ className="w-16 h-16 rounded-full"
477
+ />
478
+ <div>
479
+ <h2 className="text-xl font-bold">{data.name}</h2>
480
+ <p className="text-gray-600">{data.email}</p>
481
+ </div>
482
+ </div>
483
+ <div className="grid grid-cols-2 gap-4">
484
+ <div>
485
+ <label className="text-sm font-semibold">Phone</label>
486
+ <p>{data.phone}</p>
487
+ </div>
488
+ <div>
489
+ <label className="text-sm font-semibold">Status</label>
490
+ <p>{data.status}</p>
491
+ </div>
492
+ </div>
493
+ </div>
494
+ );
495
+
496
+ const config = {
497
+ modalConfig: {
498
+ viewModal: {
499
+ title: "User Details",
500
+ component: CustomUserView, // Use custom component
501
+ },
502
+ },
503
+ };
504
+ ```
505
+
506
+ ### 7. Row Click Handler
507
+
508
+ Handle clicks on table rows:
509
+
510
+ ```js
511
+ // For a custom function
512
+ const config = {
513
+ tableConfig: {
514
+ table_head: [...],
515
+ data: [...],
516
+ rowClick: (row, rowIndex) => {
517
+ console.log(`Row ${rowIndex} clicked:`, row);
518
+ // Open custom drawer, navigate, or perform any action
519
+ }
520
+ }
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
+
532
+ ```
533
+
534
+ ### 8. Server-Side Filtering
535
+
536
+ Implement server-side filtering with custom filter fields:
537
+
538
+ ```js
539
+ const config = {
540
+ tableConfig: {
541
+ filter: {
542
+ enabled: true,
543
+ useServerSideFilters: true, // Enable server-side filtering
544
+ },
545
+ filterConfig: {
546
+ fields: [
547
+ {
548
+ key: "status",
549
+ label: "Status",
550
+ type: "select",
551
+ options: [
552
+ { value: "active", label: "Active" },
553
+ { value: "inactive", label: "Inactive" },
554
+ ],
555
+ },
556
+ {
557
+ key: "createdFrom",
558
+ label: "Created From",
559
+ type: "date",
560
+ },
561
+ {
562
+ key: "createdTo",
563
+ label: "Created To",
564
+ type: "date",
565
+ },
566
+ ],
567
+ },
568
+ },
569
+ fetchData: async ({
570
+ search,
571
+ rows_per_page,
572
+ current_page,
573
+ sort_by,
574
+ sort_order,
575
+ ...filters // Additional filter params passed here
576
+ }) => {
577
+ const resp = await api.get("/users", {
578
+ params: {
579
+ q: search,
580
+ limit: rows_per_page,
581
+ page: current_page,
582
+ sort_by,
583
+ sort_order,
584
+ ...filters, // Include filters in API call
585
+ },
586
+ });
587
+ return { data: resp.items, pagination: resp.pagination };
588
+ },
589
+ };
590
+ ```
591
+
592
+ ### 9. Input Masking
593
+
594
+ Format input with masks using pattern syntax:
595
+
596
+ ```js
597
+ const formFields = [
598
+ {
599
+ key: "phone",
600
+ label: "Phone Number",
601
+ type: "text",
602
+ mask: "(99) 99999-9999", // Mask pattern
603
+ maskApplyOnValue: true, // Apply mask to default value
604
+ placeholder: "(00) 00000-0000",
605
+ },
606
+ {
607
+ key: "zipCode",
608
+ label: "ZIP Code",
609
+ type: "text",
610
+ mask: "99999-999", // Pattern: 9 = digit, A = letter, X = alphanumeric, * = any
611
+ placeholder: "00000-000",
612
+ },
613
+ {
614
+ key: "creditCard",
615
+ label: "Credit Card",
616
+ type: "text",
617
+ mask: "9999 9999 9999 9999",
618
+ placeholder: "0000 0000 0000 0000",
619
+ },
620
+ ];
621
+ ```
622
+
623
+ **Mask Pattern Reference:**
624
+
625
+ - `9` — Digit (0-9)
626
+ - `A` — Letter (a-zA-Z)
627
+ - `X` — Alphanumeric (a-zA-Z0-9)
628
+ - `*` — Any character
629
+ - Any other character is literal (e.g., `-`, `/`, ` `)
630
+
631
+ ### 10. Image Cropping
632
+
633
+ Enable image cropping in image picker:
634
+
635
+ ```js
636
+ const formFields = [
637
+ {
638
+ key: "profileImage",
639
+ label: "Profile Picture",
640
+ type: "image",
641
+ cropImage: true, // Enable cropping modal
642
+ aspectRatio: 1, // 1:1 square ratio
643
+ dragDrop: true,
644
+ },
645
+ {
646
+ key: "bannerImage",
647
+ label: "Banner Image",
648
+ type: "image",
649
+ cropImage: true,
650
+ aspectRatio: 16 / 9, // 16:9 widescreen ratio
651
+ dragDrop: true,
652
+ },
653
+ ];
654
+ ```
655
+
656
+ ### 11. Sortable Columns
657
+
658
+ Configure sorting with custom options and auto-generation:
659
+
660
+ ```js
661
+ const config = {
662
+ tableConfig: {
663
+ sort: {
664
+ enabled: true,
665
+ useServerSideSorting: false, // Client-side sorting
666
+ autoGenerate: true, // Auto-generate sort options from table headers
667
+ fields: ["name", "email", "createdAt"], // Fields to make sortable
668
+ defaultValue: "name", // Default sort field
669
+ clearLabel: "Clear Sort",
670
+ onChange: (payload) => {
671
+ console.log("Sort changed:", payload);
672
+ // payload contains: { value, option, key, order, type }
673
+ },
674
+ },
675
+ table_head: [
676
+ { key: "name", title: "Name" },
677
+ { key: "email", title: "Email" },
678
+ { key: "createdAt", title: "Created Date" },
679
+ ],
680
+ },
681
+ };
682
+ ```
683
+
309
684
  ### Examples
310
- Minimal CRUD config (client-side):
685
+
686
+ #### Example 1: Minimal Client-Side CRUD
687
+
688
+ ```js
689
+ import Crud from "react-admin-crud-manager";
690
+
691
+ const users = [
692
+ { id: 1, name: "John Doe", email: "john@example.com", status: "active" },
693
+ { id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" },
694
+ ];
695
+
696
+ function App() {
697
+ const config = {
698
+ title: "Users",
699
+ fetchData: async () => ({ data: users, pagination: null }),
700
+ isStaticData: true,
701
+ tableConfig: {
702
+ table_head: [
703
+ { key: "id", title: "ID", type: "index" },
704
+ { key: "name", title: "Name" },
705
+ { key: "email", title: "Email" },
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
+ },
715
+ ],
716
+ search: { enabled: true, searchKeys: ["name", "email"] },
717
+ pagination: { enabled: true },
718
+ },
719
+ modalConfig: {
720
+ addModal: {
721
+ title: "Add User",
722
+ formFields: [
723
+ { key: "name", label: "Name", type: "text", required: true },
724
+ { key: "email", label: "Email", type: "email", required: true },
725
+ {
726
+ key: "status",
727
+ label: "Status",
728
+ type: "select",
729
+ options: [
730
+ { value: "active", label: "Active" },
731
+ { value: "inactive", label: "Inactive" },
732
+ ],
733
+ },
734
+ ],
735
+ handleSubmit: async (formData) => ({
736
+ newObject: {
737
+ ...formData,
738
+ id: Math.max(...users.map((u) => u.id)) + 1,
739
+ },
740
+ }),
741
+ },
742
+ },
743
+ };
744
+
745
+ return <Crud config={config} />;
746
+ }
747
+
748
+ export default App;
749
+ ```
750
+
751
+ #### Example 2: Server-Side CRUD with Advanced Features
752
+
753
+ ```js
754
+ import Crud from "react-admin-crud-manager";
755
+ import axios from "axios";
756
+ import { PlusIcon, EditIcon, TrashIcon } from "lucide-react";
757
+
758
+ function App() {
759
+ const api = axios.create({ baseURL: "https://api.example.com" });
760
+
761
+ const config = {
762
+ title: "Products",
763
+ description: "Manage your products inventory",
764
+ buttonText: "Add Product",
765
+ fetchData: async ({
766
+ search,
767
+ rows_per_page,
768
+ current_page,
769
+ sort_by,
770
+ sort_order,
771
+ category,
772
+ minPrice,
773
+ maxPrice,
774
+ }) => {
775
+ const resp = await api.get("/products", {
776
+ params: {
777
+ q: search,
778
+ limit: rows_per_page,
779
+ page: current_page,
780
+ sort_by,
781
+ sort_order,
782
+ category,
783
+ minPrice,
784
+ maxPrice,
785
+ },
786
+ });
787
+ return {
788
+ data: resp.data.items,
789
+ pagination: {
790
+ current_page: resp.data.page,
791
+ rows_per_page: resp.data.limit,
792
+ total_pages: resp.data.totalPages,
793
+ total_records: resp.data.total,
794
+ },
795
+ };
796
+ },
797
+ tableConfig: {
798
+ table_head: [
799
+ { key: "id", title: "ID", type: "index" },
800
+ {
801
+ key: "image",
802
+ title: "Image",
803
+ type: "avatar",
804
+ imageKey: "image",
805
+ titleKey: "name",
806
+ },
807
+ { key: "name", title: "Product Name" },
808
+ {
809
+ key: "price",
810
+ title: "Price",
811
+ render: (row) => `$${row.price.toFixed(2)}`,
812
+ },
813
+ {
814
+ key: "category",
815
+ title: "Category",
816
+ type: "chip",
817
+ variant: "soft",
818
+ chipOptions: [
819
+ { value: "electronics", label: "Electronics", color: "blue" },
820
+ { value: "clothing", label: "Clothing", color: "purple" },
821
+ ],
822
+ },
823
+ ],
824
+ search: {
825
+ enabled: true,
826
+ useServerSideSearch: true,
827
+ },
828
+ filter: { enabled: true, useServerSideFilters: true },
829
+ pagination: { enabled: true, useServerSidePagination: true },
830
+ sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
831
+ exportCSV: {
832
+ enabled: true,
833
+ fileName: "products.csv",
834
+ fields: [
835
+ { label: "ID", key: "id" },
836
+ { label: "Name", key: "name" },
837
+ { label: "Price", key: "price" },
838
+ ],
839
+ },
840
+ filterConfig: {
841
+ fields: [
842
+ {
843
+ key: "category",
844
+ label: "Category",
845
+ type: "select",
846
+ options: [
847
+ { value: "electronics", label: "Electronics" },
848
+ { value: "clothing", label: "Clothing" },
849
+ ],
850
+ },
851
+ { key: "minPrice", label: "Min Price", type: "number" },
852
+ { key: "maxPrice", label: "Max Price", type: "number" },
853
+ ],
854
+ },
855
+ },
856
+ modalConfig: {
857
+ addModal: {
858
+ title: "Add Product",
859
+ size: "lg",
860
+ icon: <PlusIcon />,
861
+ formFields: [
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
+ },
876
+ {
877
+ key: "category",
878
+ label: "Category",
879
+ type: "select",
880
+ required: true,
881
+ parentClass: "col-span-6",
882
+ options: [
883
+ { value: "electronics", label: "Electronics" },
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",
900
+ },
901
+ {
902
+ key: "image",
903
+ label: "Product Image",
904
+ type: "image",
905
+ cropImage: true,
906
+ aspectRatio: 1,
907
+ dragDrop: true,
908
+ parentClass: "col-span-12",
909
+ },
910
+ ],
911
+ handleSubmit: async (formData) => {
912
+ const resp = await api.post("/products", formData);
913
+ return {
914
+ newObject: resp.data,
915
+ message: "Product added successfully!",
916
+ };
917
+ },
918
+ },
919
+ editModal: {
920
+ title: "Edit Product",
921
+ size: "lg",
922
+ icon: <EditIcon />,
923
+ formFields: [
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
+ },
945
+ ],
946
+ handleSubmit: async (formData, item) => {
947
+ const resp = await api.put(`/products/${item.id}`, formData);
948
+ return { newObject: resp.data, targetObject: item };
949
+ },
950
+ },
951
+ deleteModal: {
952
+ title: "Delete Product",
953
+ icon: <TrashIcon />,
954
+ confirmText: "Are you sure you want to delete this product?",
955
+ referenceKey: "name",
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
+ ],
969
+ },
970
+ viewModal: {
971
+ title: "Product Details",
972
+ variant: "card",
973
+ fields: [
974
+ { key: "id", label: "ID" },
975
+ { key: "name", label: "Product Name" },
976
+ { key: "price", label: "Price" },
977
+ { key: "stock", label: "Stock" },
978
+ { key: "category", label: "Category", type: "chip" },
979
+ ],
980
+ },
981
+ },
982
+ };
983
+
984
+ return <Crud config={config} />;
985
+ }
986
+
987
+ export default App;
988
+ ```
989
+
990
+ #### Example 3: Complex Form with Conditional Fields and Validation
311
991
 
312
992
  ```js
313
993
  const config = {
314
- title: 'Users',
315
- fetchData: async () => ({ data: users, pagination: null }),
316
- isStaticData: true,
994
+ title: "Advanced User Registration",
317
995
  tableConfig: {
318
- table_head: [ { key: 'id', title: 'ID', type: 'index' }, { key: 'name', title: 'Name' } ],
319
- pagination: { enabled: true }
996
+ table_head: [
997
+ { key: "id", title: "ID", type: "index" },
998
+ { key: "name", title: "Full Name" },
999
+ { key: "email", title: "Email" },
1000
+ { key: "userType", title: "Type", type: "chip" },
1001
+ ],
320
1002
  },
321
1003
  modalConfig: {
322
- addModal: { title: 'Add user', handleSubmit: async (formData)=>({ newObject: formData }), formFields: [ { key: 'name', label: 'Name', type: 'text', required: true } ] }
1004
+ addModal: {
1005
+ title: "Register New User",
1006
+ formFields: [
1007
+ {
1008
+ key: "name",
1009
+ label: "Full Name",
1010
+ type: "text",
1011
+ required: true,
1012
+ parentClass: "col-span-12",
1013
+ customValidation: (value) => {
1014
+ if (value.trim().split(" ").length < 2) {
1015
+ return "Please enter your full name";
1016
+ }
1017
+ return true;
1018
+ },
1019
+ },
1020
+ {
1021
+ key: "email",
1022
+ label: "Email Address",
1023
+ type: "email",
1024
+ required: true,
1025
+ parentClass: "col-span-12",
1026
+ customValidation: (value) => {
1027
+ if (!value.includes("@company.com")) {
1028
+ return "Must use company email";
1029
+ }
1030
+ return true;
1031
+ },
1032
+ },
1033
+ {
1034
+ key: "phone",
1035
+ label: "Phone",
1036
+ type: "phone",
1037
+ countriesList: true,
1038
+ mask: "(99) 99999-9999",
1039
+ parentClass: "col-span-12",
1040
+ },
1041
+ {
1042
+ key: "userType",
1043
+ label: "User Type",
1044
+ type: "select",
1045
+ required: true,
1046
+ parentClass: "col-span-6",
1047
+ options: [
1048
+ { value: "admin", label: "Administrator" },
1049
+ { value: "user", label: "Regular User" },
1050
+ ],
1051
+ },
1052
+ {
1053
+ key: "adminLevel",
1054
+ label: "Admin Level",
1055
+ type: "select",
1056
+ parentClass: "col-span-6",
1057
+ renderCondition: (data) => data.userType === "admin",
1058
+ options: [
1059
+ { value: "superadmin", label: "Super Admin" },
1060
+ { value: "moderator", label: "Moderator" },
1061
+ ],
1062
+ },
1063
+ {
1064
+ key: "permissions",
1065
+ label: "Permissions",
1066
+ type: "checkbox",
1067
+ parentClass: "col-span-12",
1068
+ multiple: true,
1069
+ renderCondition: (data) => data.userType === "admin",
1070
+ options: [
1071
+ { value: "read", label: "Can Read" },
1072
+ { value: "write", label: "Can Write" },
1073
+ { value: "delete", label: "Can Delete" },
1074
+ ],
1075
+ },
1076
+ ],
1077
+ handleSubmit: async (formData) => {
1078
+ const resp = await api.post("/users", formData);
1079
+ return {
1080
+ newObject: resp.data,
1081
+ message: `User ${formData.name} created successfully!`,
1082
+ };
1083
+ },
1084
+ },
1085
+ },
1086
+ };
1087
+ ```
1088
+
1089
+ ---
1090
+
1091
+ ## Best Practices & Common Patterns
1092
+
1093
+ ### 1. State Management with Static Data
1094
+
1095
+ For client-side CRUD operations, use `isStaticData: true` and manage data updates through modal handlers:
1096
+
1097
+ ```js
1098
+ const [users, setUsers] = useState(initialUsers);
1099
+
1100
+ const config = {
1101
+ fetchData: async () => ({ data: users, pagination: null }),
1102
+ isStaticData: true,
1103
+ modalConfig: {
1104
+ addModal: {
1105
+ handleSubmit: async (formData) => {
1106
+ const newUser = { ...formData, id: Date.now() };
1107
+ setUsers([...users, newUser]);
1108
+ return { newObject: newUser };
1109
+ },
1110
+ },
1111
+ editModal: {
1112
+ handleSubmit: async (formData, item) => {
1113
+ const updated = { ...item, ...formData };
1114
+ setUsers(users.map((u) => (u.id === item.id ? updated : u)));
1115
+ return { newObject: updated, targetObject: item };
1116
+ },
1117
+ },
1118
+ },
1119
+ };
1120
+ ```
1121
+
1122
+ ### 2. Server-Side Operations Best Practices
1123
+
1124
+ ```js
1125
+ // Always provide pagination info back to the component
1126
+ const fetchData = async (params) => {
1127
+ const resp = await api.get("/items", { params });
1128
+ return {
1129
+ data: resp.data.items || [],
1130
+ pagination: {
1131
+ current_page: resp.data.meta.currentPage,
1132
+ rows_per_page: resp.data.meta.perPage,
1133
+ total_pages: resp.data.meta.totalPages,
1134
+ total_records: resp.data.meta.total,
1135
+ },
1136
+ };
1137
+ };
1138
+ ```
1139
+
1140
+ ### 3. Error Handling in Modal Submissions
1141
+
1142
+ ```js
1143
+ const handleSubmit = async (formData) => {
1144
+ try {
1145
+ const resp = await api.post("/items", formData);
1146
+ return {
1147
+ newObject: resp.data,
1148
+ message: "Item created successfully!", // Optional success message
1149
+ };
1150
+ } catch (error) {
1151
+ // Throw error to trigger snackbar error notification
1152
+ throw new Error(error.response?.data?.message || "Failed to create item");
323
1153
  }
324
1154
  };
325
1155
  ```
326
1156
 
327
- Server-side listing (fetchData must return { data, pagination }):
1157
+ ### 4. Dynamic Form Fields Based on Data
1158
+
1159
+ Combine `renderCondition` and `defaultValue` for dynamic forms:
328
1160
 
329
1161
  ```js
330
- const fetchData = async ({ search, rows_per_page, current_page, sort_by, sort_order }) => {
331
- const resp = await api.get('/users', { params: { q: search, limit: rows_per_page, page: current_page, sort_by, sort_order } });
332
- return { data: resp.items, pagination: { current_page: resp.page, rows_per_page: resp.limit, total_pages: resp.totalPages, total_records: resp.total } };
1162
+ const formFields = [
1163
+ {
1164
+ key: "type",
1165
+ type: "select",
1166
+ options: [
1167
+ { value: "personal", label: "Personal" },
1168
+ { value: "business", label: "Business" },
1169
+ ],
1170
+ },
1171
+ {
1172
+ key: "companyName",
1173
+ label: "Company Name",
1174
+ type: "text",
1175
+ renderCondition: (data) => data.type === "business",
1176
+ },
1177
+ {
1178
+ key: "phone",
1179
+ type: "phone",
1180
+ renderCondition: (data) => data.type === "personal",
1181
+ },
1182
+ ];
1183
+ ```
1184
+
1185
+ ### 5. Custom Table Rendering for Complex Data
1186
+
1187
+ ```js
1188
+ const tableConfig = {
1189
+ table_head: [
1190
+ {
1191
+ key: "user",
1192
+ title: "User",
1193
+ render: (row) => (
1194
+ <div className="flex items-center gap-3">
1195
+ <img src={row.avatar} className="w-8 h-8 rounded-full" />
1196
+ <div>
1197
+ <p className="font-semibold">{row.name}</p>
1198
+ <p className="text-xs text-gray-500">{row.email}</p>
1199
+ </div>
1200
+ </div>
1201
+ ),
1202
+ },
1203
+ {
1204
+ key: "metrics",
1205
+ title: "Performance",
1206
+ render: (row) => (
1207
+ <div className="flex gap-2">
1208
+ <span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
1209
+ {row.completed}%
1210
+ </span>
1211
+ </div>
1212
+ ),
1213
+ },
1214
+ ],
1215
+ };
1216
+ ```
1217
+
1218
+ ### 6. Validation Patterns
1219
+
1220
+ ```js
1221
+ const formFields = [
1222
+ {
1223
+ key: "email",
1224
+ type: "email",
1225
+ customValidation: (value) => {
1226
+ // Async validation example
1227
+ const exists = checkEmailExists(value);
1228
+ if (exists) return "Email already in use";
1229
+ return true;
1230
+ },
1231
+ },
1232
+ {
1233
+ key: "password",
1234
+ type: "password",
1235
+ customValidation: (value) => {
1236
+ if (value.length < 8) return "Password must be at least 8 characters";
1237
+ if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
1238
+ if (!/[0-9]/.test(value)) return "Must contain a number";
1239
+ return true;
1240
+ },
1241
+ },
1242
+ ];
1243
+ ```
1244
+
1245
+ ### 7. Handling File Uploads
1246
+
1247
+ ```js
1248
+ const handleImageUpload = async (file) => {
1249
+ const formData = new FormData();
1250
+ formData.append("file", file);
1251
+ const resp = await api.post("/upload", formData);
1252
+ return resp.data.url;
1253
+ };
1254
+
1255
+ const config = {
1256
+ modalConfig: {
1257
+ addModal: {
1258
+ formFields: [
1259
+ {
1260
+ key: "image",
1261
+ type: "image",
1262
+ dragDrop: true,
1263
+ cropImage: true,
1264
+ aspectRatio: 1,
1265
+ },
1266
+ ],
1267
+ handleSubmit: async (formData) => {
1268
+ // Image data includes base64 or file data
1269
+ const imageUrl = await handleImageUpload(formData.image);
1270
+ const resp = await api.post("/items", {
1271
+ ...formData,
1272
+ image: imageUrl,
1273
+ });
1274
+ return { newObject: resp.data };
1275
+ },
1276
+ },
1277
+ },
1278
+ };
1279
+ ```
1280
+
1281
+ ### 8. Conditional Rendering with Row Data
1282
+
1283
+ ```js
1284
+ const viewModal = {
1285
+ fields: [
1286
+ {
1287
+ key: "status",
1288
+ label: "Status",
1289
+ type: "chip",
1290
+ renderCondition: (data) => data.status !== null,
1291
+ chipOptions: [
1292
+ { value: "active", label: "Active", color: "green" },
1293
+ { value: "suspended", label: "Suspended", color: "red" },
1294
+ ],
1295
+ },
1296
+ {
1297
+ key: "suspendReason",
1298
+ label: "Suspension Reason",
1299
+ renderCondition: (data) => data.status === "suspended", // Only show if suspended
1300
+ },
1301
+ ],
1302
+ };
1303
+ ```
1304
+
1305
+ ### 9. Multiple Sort Options (Server-Side)
1306
+
1307
+ ```js
1308
+ const tableConfig = {
1309
+ sort: {
1310
+ enabled: true,
1311
+ useServerSideSorting: true,
1312
+ options: [
1313
+ {
1314
+ value: "recent",
1315
+ label: "Most Recent",
1316
+ key: "createdAt",
1317
+ order: "desc",
1318
+ },
1319
+ { value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
1320
+ { value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
1321
+ { value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
1322
+ { value: "popular", label: "Most Popular", key: "views", order: "desc" },
1323
+ ],
1324
+ onChange: (payload) => {
1325
+ console.log(`Sorted by: ${payload.option?.label}`);
1326
+ },
1327
+ },
333
1328
  };
334
1329
  ```
335
1330
 
336
1331
  ---
337
1332
 
338
1333
  ## License
1334
+
339
1335
  MIT