react-admin-crud-manager 1.2.14 → 1.2.16
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 +730 -1389
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,378 +1,577 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-admin-crud-manager
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A plug-and-play React CRUD admin component with full support for tables, modals, forms, filters, sorting, pagination, and more — all driven by a single `config` object.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Config Reference](#config-reference)
|
|
12
|
+
- [Top-Level Config](#top-level-config)
|
|
13
|
+
- [tableConfig](#tableconfig)
|
|
14
|
+
- [Table Column Object](#table-column-object-table_head)
|
|
15
|
+
- [Sorting](#sorting-sort)
|
|
16
|
+
- [modalConfig](#modalconfig)
|
|
17
|
+
- [Add & Edit Modal](#add--edit-modal)
|
|
18
|
+
- [Delete Modal](#delete-modal)
|
|
19
|
+
- [View Modal](#view-modal)
|
|
20
|
+
- [Form Field Schema](#form-field-schema)
|
|
21
|
+
- [View Field Schema](#view-field-schema)
|
|
22
|
+
- [Features & Examples](#features--examples)
|
|
23
|
+
- [Client-Side CRUD](#1-client-side-crud-minimal-setup)
|
|
24
|
+
- [Server-Side CRUD](#2-server-side-crud-with-advanced-features)
|
|
25
|
+
- [Conditional Fields & Validation](#3-conditional-fields--custom-validation)
|
|
26
|
+
- [Export CSV](#4-export-csv)
|
|
27
|
+
- [Server-Side Filtering](#5-server-side-filtering)
|
|
28
|
+
- [Image Cropping](#6-image-cropping)
|
|
29
|
+
- [Input Masking](#7-input-masking)
|
|
30
|
+
- [Custom Table Cell Rendering](#8-custom-table-cell-rendering)
|
|
31
|
+
- [Custom Toolbar Buttons](#9-custom-toolbar-buttons)
|
|
32
|
+
- [Row Click Handler](#10-row-click-handler)
|
|
33
|
+
- [View Modal Variants](#11-view-modal-variants)
|
|
34
|
+
- [Custom View Component](#12-custom-view-component)
|
|
35
|
+
- [CSS Customization](#css-customization)
|
|
36
|
+
- [Primary Color Override](#primary-color-override)
|
|
37
|
+
- [CSS Class Reference](#css-class-reference)
|
|
38
|
+
- [License](#license)
|
|
6
39
|
|
|
7
|
-
|
|
8
|
-
- Modular, customizable UI components (Table, Modal, Form, etc.)
|
|
9
|
-
- Built with React 18+ and TypeScript
|
|
10
|
-
- Tailwind CSS for styling
|
|
11
|
-
- Server-side and client-side data handling
|
|
12
|
-
- Sorting, filtering, searching, and pagination support
|
|
13
|
-
- Rich form fields including text, select, image upload, file upload, rich text editor, and more
|
|
40
|
+
---
|
|
14
41
|
|
|
15
42
|
## Installation
|
|
16
43
|
|
|
17
|
-
```
|
|
44
|
+
```bash
|
|
18
45
|
npm install react-admin-crud-manager
|
|
19
46
|
```
|
|
20
47
|
|
|
21
|
-
|
|
48
|
+
---
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
## Quick Start
|
|
24
51
|
|
|
25
|
-
```
|
|
52
|
+
```jsx
|
|
26
53
|
import Crud from "react-admin-crud-manager";
|
|
27
54
|
|
|
28
55
|
function App() {
|
|
29
56
|
const config = {
|
|
30
57
|
title: "Users",
|
|
31
|
-
fetchData: async () => {
|
|
32
|
-
|
|
58
|
+
fetchData: async () => ({ data: [] }),
|
|
59
|
+
tableConfig: {
|
|
60
|
+
table_head: [
|
|
61
|
+
{ key: "id", title: "ID", type: "index" },
|
|
62
|
+
{ key: "name", title: "Name" },
|
|
63
|
+
{ key: "email", title: "Email" },
|
|
64
|
+
],
|
|
33
65
|
},
|
|
34
|
-
// ...other config options
|
|
35
66
|
};
|
|
67
|
+
|
|
36
68
|
return <Crud config={config} />;
|
|
37
69
|
}
|
|
38
70
|
```
|
|
39
71
|
|
|
40
|
-
## Components
|
|
41
|
-
|
|
42
|
-
- `Crud`: Main CRUD page component
|
|
43
|
-
|
|
44
|
-
## Props
|
|
45
|
-
|
|
46
|
-
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.
|
|
47
|
-
|
|
48
72
|
---
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
## Config Reference
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
### Top-Level Config
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
| Property | Type | Required | Description |
|
|
79
|
+
| ----------------- | -------- | :------: | ----------------------------------------------------------------------------- |
|
|
80
|
+
| `title` | string | ✅ | Page title |
|
|
81
|
+
| `description` | string | — | Optional subtitle / description |
|
|
82
|
+
| `buttonText` | string | — | Label for the Add button (default: `"Add"`) |
|
|
83
|
+
| `fetchData` | function | ✅ | Async function to load data — see signature below |
|
|
84
|
+
| `fetchRowDetails` | function | — | Async function to load extra details for a row (used by view modal) |
|
|
85
|
+
| `isStaticData` | boolean | — | `false` (default). Set `true` for client-side-only add / edit / delete |
|
|
86
|
+
| `tableConfig` | object | ✅ | Table configuration — [see tableConfig](#tableconfig) |
|
|
87
|
+
| `modalConfig` | object | — | Modal definitions — [see modalConfig](#modalconfig) |
|
|
88
|
+
| `filterConfig` | object | — | Filter drawer field definitions — [see Form Field Schema](#form-field-schema) |
|
|
55
89
|
|
|
56
|
-
|
|
90
|
+
**`fetchData` signature:**
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| ------------ | ----------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
78
|
-
| `table_head` | array of column objects | Column definitions | Array of table column objects (see [Table column object](#table-column-object-table_head)) |
|
|
79
|
-
| `search` | object | Search functionality config | `{ enabled: true, useServerSideSearch?: false, searchKeys?: ["name", "email"] }` |
|
|
80
|
-
| `filter` | object | Filter drawer config | `{ enabled: true, useServerSideFilters?: false }` |
|
|
81
|
-
| `pagination` | object | Pagination controls | `{ enabled: true, useServerSidePagination?: false }` |
|
|
82
|
-
| `sort` | object | Sorting configuration | Enables and controls sorting behavior for the data table. (see [Table Sorting Config](#table-sorting-config)) |
|
|
83
|
-
| `exportCSV` | object | Export data as CSV | `{ enabled: true, fileName: "users.csv", fields: [{ label: "Name", key: "name" }, ...] }` |
|
|
84
|
-
| `rowClick` | function or boolean | Callback on table row click | `(row: object, rowIndex: number) => void` or `true` (setting true will open details) |
|
|
85
|
-
|
|
86
|
-
`customButtons` can also be passed in `tableConfig` to render extra header toolbar buttons with custom click handlers. See [Custom Toolbar Buttons (Header)](#12-custom-toolbar-buttons-header).
|
|
87
|
-
|
|
88
|
-
#### Table Column Object (`table_head[]`)
|
|
89
|
-
|
|
90
|
-
| Key | Type | Description | Accepted Values / Example |
|
|
91
|
-
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
-
| `key` | string | Property name in row objects | `"id"`, `"name"`, `"email"` (must exist in data objects) |
|
|
93
|
-
| `title` | string | Column header text | `"User ID"`, `"Full Name"`, `"Email Address"` |
|
|
94
|
-
| `type` | string | Column renderer type | `"plain"` (default), `"index"` (row number), `"group"` (avatar+text), `"chip"` (badge), `"date"`, `"avatar"`, `"menu_actions"` |
|
|
95
|
-
| `imageKey` | string | Image property for avatar/group types | `"profileImage"`, `"avatarUrl"` (path to image in data object) |
|
|
96
|
-
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` (property key in data object) |
|
|
97
|
-
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` (property key in data object) |
|
|
98
|
-
| `onClickDetails` | boolean | Clicking cell opens view details modal | `true`, `false` (default: `false`) |
|
|
99
|
-
| `variant` | string | Chip styling variant | `"contained"`, `"outline"`, `"soft"` (used with `type: "chip"`) |
|
|
100
|
-
| `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" }]` |
|
|
101
|
-
| `defaultColor` | string | Default color for chips (if no match in chipOptions) | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"`, etc. |
|
|
102
|
-
| `className` | string | Custom CSS class for cell content | Tailwind classes: `"font-bold text-sm text-gray-600"` |
|
|
103
|
-
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` (uses date-fns patterns) |
|
|
104
|
-
| `menuList` | array | Action menu items; array of `{ title: string, type: string, variant?: string, icon?: ReactNode, onClick?: Function }` | `[{ title: "Edit", type: "edit", icon: <EditIcon /> }, { title: "Open", type: "custom", onClick: (event, row) => setShowModal(true) }]` |
|
|
105
|
-
| `render` | function | Custom cell renderer (overrides built-in logic) | `(row: object, rowIndex: number) => ReactNode`; e.g., `(row) => <span>{row.name.toUpperCase()}</span>` |
|
|
92
|
+
```js
|
|
93
|
+
async ({
|
|
94
|
+
search,
|
|
95
|
+
rows_per_page,
|
|
96
|
+
current_page,
|
|
97
|
+
sort_by,
|
|
98
|
+
sort_order,
|
|
99
|
+
...filters // any extra keys from filterConfig
|
|
100
|
+
}) => ({
|
|
101
|
+
data: [], // array of row objects
|
|
102
|
+
pagination: {
|
|
103
|
+
// omit for client-side pagination
|
|
104
|
+
current_page,
|
|
105
|
+
rows_per_page,
|
|
106
|
+
total_pages,
|
|
107
|
+
total_records,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
106
111
|
|
|
107
112
|
---
|
|
108
113
|
|
|
109
|
-
###
|
|
114
|
+
### tableConfig
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `options` | array | No | Custom sorting options. If not provided, options will be auto-generated from sortable table columns. |
|
|
116
|
+
| Key | Type | Description |
|
|
117
|
+
| ----------------- | ------------------ | -------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `table_head` | array | Column definitions — [see Table Column Object](#table-column-object-table_head) |
|
|
119
|
+
| `search` | object | `{ enabled, useServerSideSearch?, searchKeys? }` |
|
|
120
|
+
| `filter` | object | `{ enabled, useServerSideFilters? }` |
|
|
121
|
+
| `pagination` | object | `{ enabled, useServerSidePagination? }` |
|
|
122
|
+
| `sort` | object | Sorting config — [see Sorting](#sorting-sort) |
|
|
123
|
+
| `exportCSV` | object | `{ enabled, fileName, fields: [{ label, key }] }` |
|
|
124
|
+
| `rowClick` | function \| `true` | Called on row click `(row, rowIndex) => void`, or `true` to open the view details modal |
|
|
125
|
+
| `customButtons` | array | Extra buttons in the table toolbar — [see Custom Toolbar Buttons](#9-custom-toolbar-buttons) |
|
|
126
|
+
| `customMenuItems` | array | Items in the 3-dot overflow menu in the toolbar |
|
|
123
127
|
|
|
124
128
|
---
|
|
125
129
|
|
|
126
|
-
###
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
| `title` | string | No | Modal title | `"Delete User"`, `"Confirm Delete"` |
|
|
145
|
-
| `icon` | ReactNode | No | Icon element displayed in modal header | `<TrashIcon />`, `<WarningIcon />` |
|
|
146
|
-
| `size` | string | No | Modal width | `"sm"` (default), `"md"`, `"lg"`, `"xl"`, `"full"` |
|
|
147
|
-
| `confirmText` | string | No | Confirmation message text | `"Are you sure you want to delete this user?"`, `"This action cannot be undone."` |
|
|
148
|
-
| `referenceKey` | string | No | Property key to display as confirmation reference | `"name"`, `"email"` (shows the value from selected item) |
|
|
149
|
-
| `actionButtons` | array | No | Custom action buttons | `[{ type: "button", label: "Delete", color: "error", variant: "contained", onClick: async (event , selectedItem) => Promise<{ targetObject }>},... ]` |
|
|
150
|
-
|
|
151
|
-
#### View Modal (`viewModal`)
|
|
152
|
-
|
|
153
|
-
| Property | Type | Required | Description | Accepted Values / Example |
|
|
154
|
-
| ----------------- | --------------- | -------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
155
|
-
| `title` | string | Yes | Modal title | `"User Details"`, `"View Profile"` |
|
|
156
|
-
| `icon` | ReactNode | No | Icon element displayed in modal header | `<EyeIcon />`, `<InfoIcon />` |
|
|
157
|
-
| `size` | string | No | Modal width | `"sm"`, `"md"` (default), `"lg"`, `"xl"`, `"full"` |
|
|
158
|
-
| `component` | React component | No | Custom component to render (receives `data` prop) | `(props) => <CustomViewComponent data={props.data} />` |
|
|
159
|
-
| `variant` | string | No | View layout style | `"default"`, `"card"`, `"split"` |
|
|
160
|
-
| `fields` | array | No | View field objects (if not using custom component) | Array of field objects (see [View Field Schema](#view-field-schema)) |
|
|
161
|
-
| `styles` | object | No | Custom CSS classes for various view elements | `{ containerClass: "...", rowClass: "...", labelClass: "...", valueClass: "...", ... }` |
|
|
162
|
-
| `modalClassNames` | object | No | Custom classes for modal structure | `{ overlay: "...", container: "...", header: "...", body: "...", footer: "...", closeButton: "..." }` |
|
|
163
|
-
| `footer` | object | No | Footer configuration | `{ cancelButton: true, cancelText: "Close" }` |
|
|
130
|
+
### Table Column Object (`table_head[]`)
|
|
131
|
+
|
|
132
|
+
| Key | Type | Description |
|
|
133
|
+
| ---------------- | -------- | --------------------------------------------------------------------------------------- |
|
|
134
|
+
| `key` | string | Property name in the row data object |
|
|
135
|
+
| `title` | string | Column header text |
|
|
136
|
+
| `type` | string | Renderer: `plain` (default), `index`, `group`, `chip`, `date`, `avatar`, `menu_actions` |
|
|
137
|
+
| `imageKey` | string | Property path to image URL (for `avatar` / `group` types) |
|
|
138
|
+
| `titleKey` | string | Property path to title text (for `avatar` / `group` types) |
|
|
139
|
+
| `subtitleKey` | string | Property path to subtitle text (for `avatar` / `group` types) |
|
|
140
|
+
| `onClickDetails` | boolean | Opens view modal when cell is clicked |
|
|
141
|
+
| `variant` | string | Chip style: `contained`, `outline`, `soft` |
|
|
142
|
+
| `chipOptions` | array | `[{ value, label, color? }]` — maps data values to chip labels/colors |
|
|
143
|
+
| `defaultColor` | string | Fallback chip color when no `chipOptions` match |
|
|
144
|
+
| `className` | string | Tailwind classes for the cell content |
|
|
145
|
+
| `format` | string | Date format pattern (e.g. `"DD MMM YYYY"`) — used with `type: "date"` |
|
|
146
|
+
| `menuList` | array | `[{ title, type, variant?, icon?, onClick? }]` — used with `type: "menu_actions"` |
|
|
147
|
+
| `render` | function | Custom cell renderer: `(row, rowIndex) => ReactNode` |
|
|
164
148
|
|
|
165
149
|
---
|
|
166
150
|
|
|
167
|
-
###
|
|
168
|
-
|
|
169
|
-
Used by `modalConfig.*.formFields`, `filterConfig.fields`, and `viewModal.fields`. All form fields follow the `FormField` shape.
|
|
170
|
-
|
|
171
|
-
#### Common Field Properties (All Types)
|
|
172
|
-
|
|
173
|
-
| Key | Type | Required | Description | Accepted Values / Example |
|
|
174
|
-
| ------------------ | -------- | -------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
175
|
-
| `key` | string | Yes | Property name/identifier for form data | `"username"`, `"email"`, `"birth_date"` |
|
|
176
|
-
| `label` | string | No | Human-readable label for the field | `"User Name"`, `"Email Address"`, `"Date of Birth"` |
|
|
177
|
-
| `type` | string | Yes | Field type determining the input/renderer | `"text"`, `"number"`, `"email"`, `"password"`, `"select"`, `"checkbox"`, `"radio"`, `"switch"`, `"phone"`, `"textarea"`, `"image"`, `"video"`, `"audio"`, `"file"`, `"tinyEditor"`, `"group"` |
|
|
178
|
-
| `required` | boolean | No | Field must have a value for form submission | `true`, `false` (default: `false`) |
|
|
179
|
-
| `minLength` | number | No | Minimum character length (for text fields) | `5`, `10`, `50` |
|
|
180
|
-
| `placeholder` | string | No | Placeholder text shown in empty field | `"Enter your name"`, `"user@example.com"` |
|
|
181
|
-
| `disabled` | boolean | No | Field is disabled and read-only | `true`, `false` (default: `false`) |
|
|
182
|
-
| `parentClass` | string | No | Custom CSS classes for field wrapper (grid) | Tailwind classes: `"col-span-6"`, `"col-span-12"` |
|
|
183
|
-
| `renderCondition` | function | No | Show field based on form data | `(formData: Record<string, any>) => boolean`; e.g., `(data) => data.userType === 'admin'` |
|
|
184
|
-
| `customValidation` | function | No | Custom validation logic | `(value: any) => boolean \| string`; return `false` for invalid, error message string, or `true` for valid |
|
|
185
|
-
| `className` | string | No | Custom CSS class for input element | Tailwind classes: `"bg-gray-100 rounded-lg"` |
|
|
186
|
-
|
|
187
|
-
#### Type-Specific Properties
|
|
188
|
-
|
|
189
|
-
##### `"text"` Field
|
|
190
|
-
|
|
191
|
-
| Property | Type | Description | Example |
|
|
192
|
-
| ------------------ | ------- | ------------------------- | ---------------------------------------------------------------------------- |
|
|
193
|
-
| `pattern` | string | Regex validation pattern | `"^[a-zA-Z ]*$"` (letters and spaces only) |
|
|
194
|
-
| `mask` | string | Input mask pattern | `"(99) 99999-9999"` (format: 9=digit, A=letter, X=alphanumeric, \*=any char) |
|
|
195
|
-
| `maskApplyOnValue` | boolean | Apply mask formData value | `true`, `false` (default: `true`) |
|
|
151
|
+
### Sorting (`sort`)
|
|
196
152
|
|
|
197
|
-
|
|
153
|
+
| Property | Type | Description |
|
|
154
|
+
| ---------------------- | -------- | --------------------------------------------------------------- |
|
|
155
|
+
| `enabled` | boolean | Enable sorting (default: `true`) |
|
|
156
|
+
| `useServerSideSorting` | boolean | Pass sort params to `fetchData` instead of sorting client-side |
|
|
157
|
+
| `fields` | string[] | Sortable field keys |
|
|
158
|
+
| `autoGenerate` | boolean | Auto-generate sort options from table column headers |
|
|
159
|
+
| `defaultValue` | string | Default selected sort option (e.g. `"createdAt_desc"`) |
|
|
160
|
+
| `clearLabel` | string | Label for the "clear sort" option |
|
|
161
|
+
| `onChange` | function | `({ value, key, order, type }) => void` — called on sort change |
|
|
162
|
+
| `options` | array | `[{ value, label, key, order, type }]` — custom sort options |
|
|
198
163
|
|
|
199
|
-
|
|
200
|
-
| --------------------- | ------- | ---------------------- | ---------------------------------- |
|
|
201
|
-
| `negativeNumberAllow` | boolean | Allow negative numbers | `true`, `false` (default: `false`) |
|
|
202
|
-
|
|
203
|
-
##### `"select"` Field
|
|
204
|
-
|
|
205
|
-
| Property | Type | Description | Accepted Values / Example |
|
|
206
|
-
| ------------------- | ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
207
|
-
| `options` | array | Select options; `{ value, label, color? }[]` | `[{ value: "active", label: "Active", color: "green" }, { value: "inactive", label: "Inactive", color: "red" }]` |
|
|
208
|
-
| `countriesList` | boolean | Country selector dropdown | `true`, `false` (default: `false`) |
|
|
209
|
-
| `multiple` | boolean | Allow selecting multiple options | `true`, `false` (default: `false`) |
|
|
210
|
-
| `search` | boolean | Enable searchable dropdown | `true`, `false` (default: `false`) |
|
|
211
|
-
| `dropdownMaxHeight` | number | Max height of dropdown in pixels | `300`, `400` |
|
|
212
|
-
| `defaultValue` | any | Initial value for the field | `"John"`, `25`, `true`, `["option1", "option2"]` |
|
|
164
|
+
---
|
|
213
165
|
|
|
214
|
-
|
|
166
|
+
### modalConfig
|
|
215
167
|
|
|
216
|
-
|
|
217
|
-
| ---------- | ------- | -------------------------------------- | -------------------------------------------------------------------------------- |
|
|
218
|
-
| `options` | array | Checkbox options; `{ value, label }[]` | `[{ value: "read", label: "Can Read" }, { value: "write", label: "Can Write" }]` |
|
|
219
|
-
| `multiple` | boolean | Allow selecting multiple values | `true` (default: `true`); affects how data is stored |
|
|
168
|
+
Configure the add, edit, delete, and view modals under a single `modalConfig` object:
|
|
220
169
|
|
|
221
|
-
|
|
170
|
+
```js
|
|
171
|
+
modalConfig: {
|
|
172
|
+
addModal: { ... },
|
|
173
|
+
editModal: { ... },
|
|
174
|
+
deleteModal: { ... },
|
|
175
|
+
viewModal: { ... },
|
|
176
|
+
}
|
|
177
|
+
```
|
|
222
178
|
|
|
223
|
-
|
|
224
|
-
| --------- | ----- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
225
|
-
| `options` | array | Radio button options; `{ value, label }[]` | `[{ value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "other", label: "Other" }]` |
|
|
179
|
+
---
|
|
226
180
|
|
|
227
|
-
|
|
181
|
+
### Add & Edit Modal
|
|
228
182
|
|
|
229
|
-
| Property
|
|
230
|
-
|
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
183
|
+
| Property | Type | Description |
|
|
184
|
+
| --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `title` | string | Modal title |
|
|
186
|
+
| `icon` | ReactNode | Icon in the modal header |
|
|
187
|
+
| `size` | string | Width: `sm`, `md` (default), `lg`, `xl`, `full` |
|
|
188
|
+
| `formClass` | string | Tailwind classes for the form wrapper (e.g. `"grid grid-cols-12 gap-4"`) |
|
|
189
|
+
| `formFields` | array | Field definitions — [see Form Field Schema](#form-field-schema) |
|
|
190
|
+
| `handleSubmit` | function | **Add:** `async (formData) => { newObject, message? }` **Edit:** `async (formData, item) => { newObject, targetObject, message? }` |
|
|
191
|
+
| `actionButtons` | array | Custom footer buttons |
|
|
233
192
|
|
|
234
|
-
|
|
193
|
+
---
|
|
235
194
|
|
|
236
|
-
|
|
237
|
-
| ---------------- | ------- | -------------------------------------- | -------------------------------------- |
|
|
238
|
-
| `countriesList` | boolean | Show country selector dropdown | `true`, `false` (default: `false`) |
|
|
239
|
-
| `defaultCountry` | string | Default country ISO code | `"US"`, `"GB"`, `"IN"`, `"CA"`, `"AU"` |
|
|
240
|
-
| `search` | boolean | Enable searching countries in selector | `true`, `false` (default: `false`) |
|
|
241
|
-
| `placeholder` | string | Placeholder text | `"+1 (555) 000-0000"` |
|
|
195
|
+
### Delete Modal
|
|
242
196
|
|
|
243
|
-
|
|
197
|
+
| Property | Type | Description |
|
|
198
|
+
| --------------- | --------- | ------------------------------------------------------------------------------------- |
|
|
199
|
+
| `title` | string | Modal title |
|
|
200
|
+
| `icon` | ReactNode | Icon in the modal header |
|
|
201
|
+
| `size` | string | Width: `sm` (default), `md`, `lg`, `xl`, `full` |
|
|
202
|
+
| `confirmText` | string | Confirmation message shown to the user |
|
|
203
|
+
| `referenceKey` | string | Property from the selected row to display (e.g. `"name"`) |
|
|
204
|
+
| `actionButtons` | array | `[{ type, label, color, variant, onClick: async (event, item) => { targetObject } }]` |
|
|
244
205
|
|
|
245
|
-
|
|
246
|
-
| -------- | ------ | ---------------------- | ------------------------- |
|
|
247
|
-
| `rows` | number | Number of visible rows | `3`, `5`, `10` |
|
|
206
|
+
---
|
|
248
207
|
|
|
249
|
-
|
|
208
|
+
### View Modal
|
|
250
209
|
|
|
251
|
-
| Property
|
|
252
|
-
|
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
210
|
+
| Property | Type | Description |
|
|
211
|
+
| ----------------- | --------------- | ---------------------------------------------------------------------------------- |
|
|
212
|
+
| `title` | string | Modal title |
|
|
213
|
+
| `icon` | ReactNode | Icon in the modal header |
|
|
214
|
+
| `size` | string | Width: `sm`, `md` (default), `lg`, `xl`, `full` |
|
|
215
|
+
| `variant` | string | Layout style: `default`, `card`, `split` |
|
|
216
|
+
| `fields` | array | View field definitions — [see View Field Schema](#view-field-schema) |
|
|
217
|
+
| `component` | React component | Fully custom component — receives `{ data }` prop |
|
|
218
|
+
| `styles` | object | Custom classes: `containerClass`, `rowClass`, `labelClass`, `valueClass`, etc. |
|
|
219
|
+
| `modalClassNames` | object | Custom classes for modal parts: `overlay`, `container`, `header`, `body`, `footer` |
|
|
220
|
+
| `footer` | object | `{ cancelButton: true, cancelText: "Close" }` |
|
|
259
221
|
|
|
260
|
-
|
|
222
|
+
---
|
|
261
223
|
|
|
262
|
-
|
|
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 MB | `5`, `10`, `25` |
|
|
224
|
+
### Form Field Schema
|
|
267
225
|
|
|
268
|
-
|
|
226
|
+
Used in `addModal.formFields`, `editModal.formFields`, and `filterConfig.fields`.
|
|
227
|
+
|
|
228
|
+
#### Common Properties (all field types)
|
|
229
|
+
|
|
230
|
+
| Property | Type | Description |
|
|
231
|
+
| ------------------ | -------- | --------------------------------------------------------------- |
|
|
232
|
+
| `key` | string | Data property name — **required** |
|
|
233
|
+
| `label` | string | Display label |
|
|
234
|
+
| `type` | string | Field type — see list below — **required** |
|
|
235
|
+
| `required` | boolean | Marks the field as required |
|
|
236
|
+
| `minLength` | number | Minimum character length |
|
|
237
|
+
| `placeholder` | string | Input placeholder text |
|
|
238
|
+
| `disabled` | boolean | Makes the field read-only |
|
|
239
|
+
| `parentClass` | string | Tailwind class on the wrapper div (e.g. `"col-span-6"`) |
|
|
240
|
+
| `className` | string | Tailwind class on the input element |
|
|
241
|
+
| `renderCondition` | function | `(formData) => boolean` — show/hide field based on other values |
|
|
242
|
+
| `customValidation` | function | `(value) => true \| "error message"` — custom validation logic |
|
|
243
|
+
|
|
244
|
+
#### Available Field Types
|
|
245
|
+
|
|
246
|
+
| Type | Extra Properties |
|
|
247
|
+
| ------------ | ------------------------------------------------------------------------------------- |
|
|
248
|
+
| `text` | `pattern` (regex), `mask` (e.g. `"(99) 99999-9999"`), `maskApplyOnValue` |
|
|
249
|
+
| `number` | `negativeNumberAllow` |
|
|
250
|
+
| `email` | — |
|
|
251
|
+
| `password` | — |
|
|
252
|
+
| `textarea` | `rows` |
|
|
253
|
+
| `select` | `options`, `multiple`, `search`, `countriesList`, `dropdownMaxHeight`, `defaultValue` |
|
|
254
|
+
| `checkbox` | `options`, `multiple` |
|
|
255
|
+
| `radio` | `options` |
|
|
256
|
+
| `switch` | `text`, `options` |
|
|
257
|
+
| `phone` | `countriesList`, `defaultCountry`, `search` |
|
|
258
|
+
| `image` | `accept`, `dragDrop`, `cropImage`, `aspectRatio`, `multiple`, `maxImages` |
|
|
259
|
+
| `video` | `accept`, `dragDrop`, `maxSize` (MB) |
|
|
260
|
+
| `audio` | `accept`, `dragDrop`, `maxSize` (MB) |
|
|
261
|
+
| `file` | `accept` (e.g. `".pdf,.docx"`), `dragDrop`, `maxSize` (MB) |
|
|
262
|
+
| `tinyEditor` | `editorKey`, `fontFamily`, `height` |
|
|
263
|
+
| `group` | — |
|
|
264
|
+
|
|
265
|
+
**Mask pattern characters:** `9` = digit, `A` = letter, `X` = alphanumeric, `*` = any character. All other characters are treated as literals.
|
|
269
266
|
|
|
270
|
-
|
|
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 MB | `5`, `10`, `25` |
|
|
267
|
+
---
|
|
275
268
|
|
|
276
|
-
|
|
269
|
+
### View Field Schema
|
|
277
270
|
|
|
278
|
-
|
|
271
|
+
Used in `viewModal.fields`.
|
|
272
|
+
|
|
273
|
+
| Property | Type | Description |
|
|
274
|
+
| ----------------- | --------- | --------------------------------------------------------------- |
|
|
275
|
+
| `key` | string | Property name from row data |
|
|
276
|
+
| `label` | string | Display label |
|
|
277
|
+
| `type` | string | `text`, `date`, `chip`, `image`, `avatar`, `group`, `cardGroup` |
|
|
278
|
+
| `format` | string | Date format (e.g. `"DD MMM YYYY"`) |
|
|
279
|
+
| `imageKey` | string | Property path to image URL |
|
|
280
|
+
| `titleKey` | string | Property path to title text |
|
|
281
|
+
| `subtitleKey` | string | Property path to subtitle text |
|
|
282
|
+
| `variant` | string | Chip style: `contained`, `outline`, `soft` |
|
|
283
|
+
| `chipOptions` | array | `[{ value, label, color }]` |
|
|
284
|
+
| `defaultColor` | string | Fallback chip color |
|
|
285
|
+
| `className` | string | Tailwind class on the value element |
|
|
286
|
+
| `blockClass` | string | Tailwind class on the field wrapper |
|
|
287
|
+
| `icon` | ReactNode | Icon displayed beside the field |
|
|
288
|
+
| `renderCondition` | function | `(data) => boolean` — show/hide based on row data |
|
|
279
289
|
|
|
280
|
-
|
|
281
|
-
| ---------- | ------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
282
|
-
| `accept` | string | Allowed file types (same as native input accept) | `"*/*"` (default), `".pdf,.doc,.docx"`, `"application/pdf"`, `"image/*"` |
|
|
283
|
-
| `dragDrop` | boolean | Enable drag-and-drop upload | `true`, `false` (default: `false`) |
|
|
284
|
-
| `maxSize` | number | Maximum file size in MB | `5`, `10`, `25` |
|
|
290
|
+
---
|
|
285
291
|
|
|
286
|
-
|
|
292
|
+
## Features & Examples
|
|
287
293
|
|
|
288
|
-
|
|
289
|
-
- Shows file icon automatically based on extension (pdf/doc/xls/ppt/zip/txt/json, etc.)
|
|
290
|
-
- Shows selected file name and allows replacing/removing the file
|
|
294
|
+
### 1. Client-Side CRUD (Minimal Setup)
|
|
291
295
|
|
|
292
|
-
|
|
296
|
+
```jsx
|
|
297
|
+
import Crud from "react-admin-crud-manager";
|
|
293
298
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
{
|
|
297
|
-
key: "attachment",
|
|
298
|
-
label: "Attachment",
|
|
299
|
-
type: "file",
|
|
300
|
-
accept: ".pdf,.doc,.docx",
|
|
301
|
-
maxSize: 10, // MB
|
|
302
|
-
dragDrop: true,
|
|
303
|
-
required: true,
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
key: "contractFile",
|
|
307
|
-
label: "Contract",
|
|
308
|
-
type: "file",
|
|
309
|
-
accept: "application/pdf",
|
|
310
|
-
},
|
|
299
|
+
const users = [
|
|
300
|
+
{ id: 1, name: "John Doe", email: "john@example.com", status: "active" },
|
|
301
|
+
{ id: 2, name: "Jane Smith", email: "jane@example.com", status: "inactive" },
|
|
311
302
|
];
|
|
312
|
-
```
|
|
313
303
|
|
|
314
|
-
|
|
304
|
+
function App() {
|
|
305
|
+
const config = {
|
|
306
|
+
title: "Users",
|
|
307
|
+
isStaticData: true,
|
|
308
|
+
fetchData: async () => ({ data: users }),
|
|
309
|
+
tableConfig: {
|
|
310
|
+
table_head: [
|
|
311
|
+
{ key: "id", title: "ID", type: "index" },
|
|
312
|
+
{ key: "name", title: "Name" },
|
|
313
|
+
{ key: "email", title: "Email" },
|
|
314
|
+
{
|
|
315
|
+
key: "status",
|
|
316
|
+
title: "Status",
|
|
317
|
+
type: "chip",
|
|
318
|
+
chipOptions: [
|
|
319
|
+
{ value: "active", label: "Active", color: "green" },
|
|
320
|
+
{ value: "inactive", label: "Inactive", color: "red" },
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
search: { enabled: true, searchKeys: ["name", "email"] },
|
|
325
|
+
pagination: { enabled: true },
|
|
326
|
+
},
|
|
327
|
+
modalConfig: {
|
|
328
|
+
addModal: {
|
|
329
|
+
title: "Add User",
|
|
330
|
+
formFields: [
|
|
331
|
+
{ key: "name", label: "Name", type: "text", required: true },
|
|
332
|
+
{ key: "email", label: "Email", type: "email", required: true },
|
|
333
|
+
{
|
|
334
|
+
key: "status",
|
|
335
|
+
label: "Status",
|
|
336
|
+
type: "select",
|
|
337
|
+
options: [
|
|
338
|
+
{ value: "active", label: "Active" },
|
|
339
|
+
{ value: "inactive", label: "Inactive" },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
handleSubmit: async (formData) => ({
|
|
344
|
+
newObject: { ...formData, id: Date.now() },
|
|
345
|
+
}),
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
};
|
|
315
349
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
| `fontFamily` | string | Default font family in editor | `"Arial"`, `"Georgia"`, `"Courier New"` |
|
|
320
|
-
| `height` | number | Editor height in pixels | `300`, `500` |
|
|
350
|
+
return <Crud config={config} />;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
321
353
|
|
|
322
354
|
---
|
|
323
355
|
|
|
324
|
-
###
|
|
325
|
-
|
|
326
|
-
Used by `viewModal.fields` to display data in view/details modals.
|
|
327
|
-
|
|
328
|
-
| Key | Type | Description | Accepted Values / Example |
|
|
329
|
-
| ----------------- | --------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
330
|
-
| `key` | string | Property name to display | `"username"`, `"email"`, `"birth_date"` |
|
|
331
|
-
| `label` | string | Display label for the field | `"User Name"`, `"Email Address"` |
|
|
332
|
-
| `type` | string | Display type (similar to form field types) | `"text"`, `"date"`, `"chip"`, `"image"`, `"avatar"`, `"group"`, `"cardGroup"` |
|
|
333
|
-
| `format` | string | Date format pattern | `"DD MMM YYYY"`, `"YYYY-MM-DD"`, `"DD/MM/YYYY HH:mm"` |
|
|
334
|
-
| `imageKey` | string | Image property for avatar types | `"profileImage"`, `"avatarUrl"` |
|
|
335
|
-
| `titleKey` | string | Title property for group/avatar types | `"name"`, `"fullName"` |
|
|
336
|
-
| `subtitleKey` | string | Subtitle property for group/avatar types | `"email"`, `"department"` |
|
|
337
|
-
| `variant` | string | Chip display variant | `"contained"`, `"outline"`, `"soft"` |
|
|
338
|
-
| `chipOptions` | array | Map values to chip labels and colors | `[{ value: "active", label: "Active", color: "green" }]` |
|
|
339
|
-
| `defaultColor` | string | Default chip color | `"green"`, `"red"`, `"blue"`, `"yellow"`, `"purple"`, `"gray"` |
|
|
340
|
-
| `className` | string | Custom CSS class for display element | Tailwind classes |
|
|
341
|
-
| `blockClass` | string | Custom CSS class for field block/wrapper | Tailwind classes |
|
|
342
|
-
| `icon` | ReactNode | Icon element to display with field | `<UserIcon />`, `<MailIcon />` |
|
|
343
|
-
| `renderCondition` | function | Show field based on data | `(data: Record<string, any>) => boolean` |
|
|
356
|
+
### 2. Server-Side CRUD with Advanced Features
|
|
344
357
|
|
|
345
|
-
|
|
358
|
+
```jsx
|
|
359
|
+
import Crud from "react-admin-crud-manager";
|
|
360
|
+
import axios from "axios";
|
|
346
361
|
|
|
347
|
-
|
|
362
|
+
const api = axios.create({ baseURL: "https://api.example.com" });
|
|
348
363
|
|
|
349
|
-
|
|
364
|
+
const config = {
|
|
365
|
+
title: "Products",
|
|
366
|
+
description: "Manage your product inventory",
|
|
367
|
+
buttonText: "Add Product",
|
|
350
368
|
|
|
351
|
-
|
|
369
|
+
fetchData: async ({
|
|
370
|
+
search,
|
|
371
|
+
rows_per_page,
|
|
372
|
+
current_page,
|
|
373
|
+
sort_by,
|
|
374
|
+
sort_order,
|
|
375
|
+
...filters
|
|
376
|
+
}) => {
|
|
377
|
+
const resp = await api.get("/products", {
|
|
378
|
+
params: {
|
|
379
|
+
q: search,
|
|
380
|
+
limit: rows_per_page,
|
|
381
|
+
page: current_page,
|
|
382
|
+
sort_by,
|
|
383
|
+
sort_order,
|
|
384
|
+
...filters,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
return {
|
|
388
|
+
data: resp.data.items,
|
|
389
|
+
pagination: {
|
|
390
|
+
current_page: resp.data.page,
|
|
391
|
+
rows_per_page: resp.data.limit,
|
|
392
|
+
total_pages: resp.data.totalPages,
|
|
393
|
+
total_records: resp.data.total,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
},
|
|
352
397
|
|
|
353
|
-
```js
|
|
354
|
-
const config = {
|
|
355
|
-
// ... other config
|
|
356
398
|
tableConfig: {
|
|
357
|
-
table_head: [
|
|
358
|
-
|
|
399
|
+
table_head: [
|
|
400
|
+
{ key: "id", title: "ID", type: "index" },
|
|
401
|
+
{ key: "name", title: "Name" },
|
|
402
|
+
{
|
|
403
|
+
key: "price",
|
|
404
|
+
title: "Price",
|
|
405
|
+
render: (row) => `$${row.price.toFixed(2)}`,
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
key: "category",
|
|
409
|
+
title: "Category",
|
|
410
|
+
type: "chip",
|
|
411
|
+
variant: "soft",
|
|
412
|
+
chipOptions: [
|
|
413
|
+
{ value: "electronics", label: "Electronics", color: "blue" },
|
|
414
|
+
{ value: "clothing", label: "Clothing", color: "purple" },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
key: "actions",
|
|
419
|
+
title: "",
|
|
420
|
+
type: "menu_actions",
|
|
421
|
+
menuList: [
|
|
422
|
+
{ title: "Edit", type: "edit" },
|
|
423
|
+
{ title: "Delete", type: "delete" },
|
|
424
|
+
{ title: "View", type: "view" },
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
search: { enabled: true, useServerSideSearch: true },
|
|
429
|
+
filter: { enabled: true, useServerSideFilters: true },
|
|
430
|
+
pagination: { enabled: true, useServerSidePagination: true },
|
|
431
|
+
sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
|
|
359
432
|
exportCSV: {
|
|
360
433
|
enabled: true,
|
|
361
|
-
fileName: "
|
|
434
|
+
fileName: "products.csv",
|
|
362
435
|
fields: [
|
|
363
436
|
{ label: "ID", key: "id" },
|
|
364
437
|
{ label: "Name", key: "name" },
|
|
365
|
-
{ label: "
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
438
|
+
{ label: "Price", key: "price" },
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
filterConfig: {
|
|
442
|
+
fields: [
|
|
443
|
+
{
|
|
444
|
+
key: "category",
|
|
445
|
+
label: "Category",
|
|
446
|
+
type: "select",
|
|
447
|
+
options: [
|
|
448
|
+
{ value: "electronics", label: "Electronics" },
|
|
449
|
+
{ value: "clothing", label: "Clothing" },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
{ key: "minPrice", label: "Min Price", type: "number" },
|
|
453
|
+
{ key: "maxPrice", label: "Max Price", type: "number" },
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
modalConfig: {
|
|
459
|
+
addModal: {
|
|
460
|
+
title: "Add Product",
|
|
461
|
+
size: "lg",
|
|
462
|
+
formFields: [
|
|
463
|
+
{
|
|
464
|
+
key: "name",
|
|
465
|
+
label: "Name",
|
|
466
|
+
type: "text",
|
|
467
|
+
required: true,
|
|
468
|
+
parentClass: "col-span-12",
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
key: "price",
|
|
472
|
+
label: "Price",
|
|
473
|
+
type: "number",
|
|
474
|
+
required: true,
|
|
475
|
+
parentClass: "col-span-6",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
key: "stock",
|
|
479
|
+
label: "Stock",
|
|
480
|
+
type: "number",
|
|
481
|
+
required: true,
|
|
482
|
+
parentClass: "col-span-6",
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
key: "description",
|
|
486
|
+
label: "Description",
|
|
487
|
+
type: "textarea",
|
|
488
|
+
rows: 4,
|
|
489
|
+
parentClass: "col-span-12",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
key: "image",
|
|
493
|
+
label: "Image",
|
|
494
|
+
type: "image",
|
|
495
|
+
dragDrop: true,
|
|
496
|
+
cropImage: true,
|
|
497
|
+
aspectRatio: 1,
|
|
498
|
+
parentClass: "col-span-12",
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
handleSubmit: async (formData) => {
|
|
502
|
+
const resp = await api.post("/products", formData);
|
|
503
|
+
return { newObject: resp.data, message: "Product added!" };
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
editModal: {
|
|
508
|
+
title: "Edit Product",
|
|
509
|
+
size: "lg",
|
|
510
|
+
formFields: [
|
|
511
|
+
{
|
|
512
|
+
key: "name",
|
|
513
|
+
label: "Name",
|
|
514
|
+
type: "text",
|
|
515
|
+
required: true,
|
|
516
|
+
parentClass: "col-span-12",
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
key: "price",
|
|
520
|
+
label: "Price",
|
|
521
|
+
type: "number",
|
|
522
|
+
required: true,
|
|
523
|
+
parentClass: "col-span-6",
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
key: "stock",
|
|
527
|
+
label: "Stock",
|
|
528
|
+
type: "number",
|
|
529
|
+
required: true,
|
|
530
|
+
parentClass: "col-span-6",
|
|
531
|
+
},
|
|
532
|
+
],
|
|
533
|
+
handleSubmit: async (formData, item) => {
|
|
534
|
+
const resp = await api.put(`/products/${item.id}`, formData);
|
|
535
|
+
return { newObject: resp.data, targetObject: item };
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
deleteModal: {
|
|
540
|
+
title: "Delete Product",
|
|
541
|
+
confirmText: "Are you sure you want to delete this product?",
|
|
542
|
+
referenceKey: "name",
|
|
543
|
+
actionButtons: [
|
|
544
|
+
{
|
|
545
|
+
type: "button",
|
|
546
|
+
label: "Delete",
|
|
547
|
+
color: "error",
|
|
548
|
+
variant: "contained",
|
|
549
|
+
onClick: async (event, item) => {
|
|
550
|
+
await api.delete(`/products/${item.id}`);
|
|
551
|
+
return { targetObject: item };
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
viewModal: {
|
|
558
|
+
title: "Product Details",
|
|
559
|
+
variant: "card",
|
|
560
|
+
fields: [
|
|
561
|
+
{ key: "id", label: "ID" },
|
|
562
|
+
{ key: "name", label: "Name" },
|
|
563
|
+
{ key: "price", label: "Price" },
|
|
564
|
+
{ key: "stock", label: "Stock" },
|
|
565
|
+
{ key: "category", label: "Category", type: "chip" },
|
|
566
|
+
],
|
|
567
|
+
},
|
|
568
|
+
},
|
|
370
569
|
};
|
|
371
570
|
```
|
|
372
571
|
|
|
373
|
-
|
|
572
|
+
---
|
|
374
573
|
|
|
375
|
-
|
|
574
|
+
### 3. Conditional Fields & Custom Validation
|
|
376
575
|
|
|
377
576
|
```js
|
|
378
577
|
const formFields = [
|
|
@@ -389,454 +588,254 @@ const formFields = [
|
|
|
389
588
|
key: "adminLevel",
|
|
390
589
|
label: "Admin Level",
|
|
391
590
|
type: "select",
|
|
392
|
-
renderCondition: (
|
|
591
|
+
renderCondition: (data) => data.userType === "admin", // only shown for admins
|
|
393
592
|
options: [
|
|
394
593
|
{ value: "superadmin", label: "Super Admin" },
|
|
395
594
|
{ value: "moderator", label: "Moderator" },
|
|
396
595
|
],
|
|
397
596
|
},
|
|
398
|
-
{
|
|
399
|
-
key: "department",
|
|
400
|
-
label: "Department",
|
|
401
|
-
type: "text",
|
|
402
|
-
renderCondition: (formData) => formData.userType === "user", // Only show if userType is user
|
|
403
|
-
},
|
|
404
|
-
];
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### 3. Custom Field Validation
|
|
408
|
-
|
|
409
|
-
Add custom validation logic to form fields:
|
|
410
|
-
|
|
411
|
-
```js
|
|
412
|
-
const formFields = [
|
|
413
597
|
{
|
|
414
598
|
key: "email",
|
|
415
599
|
label: "Email",
|
|
416
600
|
type: "email",
|
|
601
|
+
required: true,
|
|
417
602
|
customValidation: (value) => {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return "Email must be a company email"; // Error message
|
|
421
|
-
}
|
|
422
|
-
return true; // Valid
|
|
603
|
+
if (!value.includes("@company.com")) return "Must be a company email";
|
|
604
|
+
return true;
|
|
423
605
|
},
|
|
424
606
|
},
|
|
425
607
|
{
|
|
426
|
-
key: "
|
|
427
|
-
label: "
|
|
428
|
-
type: "
|
|
608
|
+
key: "password",
|
|
609
|
+
label: "Password",
|
|
610
|
+
type: "password",
|
|
429
611
|
customValidation: (value) => {
|
|
430
|
-
if (value <
|
|
431
|
-
if (value
|
|
612
|
+
if (value.length < 8) return "Minimum 8 characters";
|
|
613
|
+
if (!/[A-Z]/.test(value)) return "Must include an uppercase letter";
|
|
614
|
+
if (!/[0-9]/.test(value)) return "Must include a number";
|
|
432
615
|
return true;
|
|
433
616
|
},
|
|
434
617
|
},
|
|
435
618
|
];
|
|
436
619
|
```
|
|
437
620
|
|
|
438
|
-
|
|
621
|
+
---
|
|
439
622
|
|
|
440
|
-
|
|
623
|
+
### 4. Export CSV
|
|
441
624
|
|
|
442
625
|
```js
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
key: "price",
|
|
455
|
-
title: "Price",
|
|
456
|
-
render: (row) => (
|
|
457
|
-
<span className="text-green-600 font-semibold">
|
|
458
|
-
${row.price.toFixed(2)}
|
|
459
|
-
</span>
|
|
460
|
-
)
|
|
461
|
-
},
|
|
462
|
-
{
|
|
463
|
-
key: "actions",
|
|
464
|
-
title: "Actions",
|
|
465
|
-
render: (row) => (
|
|
466
|
-
<button onClick={() => console.log(row)}>
|
|
467
|
-
Custom Action
|
|
468
|
-
</button>
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
],
|
|
472
|
-
data: [...]
|
|
473
|
-
};
|
|
626
|
+
tableConfig: {
|
|
627
|
+
exportCSV: {
|
|
628
|
+
enabled: true,
|
|
629
|
+
fileName: "users_export.csv",
|
|
630
|
+
fields: [
|
|
631
|
+
{ label: "ID", key: "id" },
|
|
632
|
+
{ label: "Name", key: "name" },
|
|
633
|
+
{ label: "Email", key: "email" },
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
}
|
|
474
637
|
```
|
|
475
638
|
|
|
476
|
-
|
|
639
|
+
---
|
|
477
640
|
|
|
478
|
-
|
|
641
|
+
### 5. Server-Side Filtering
|
|
479
642
|
|
|
480
643
|
```js
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
// Card variant - each field in an elevated card
|
|
496
|
-
const cardConfig = {
|
|
497
|
-
modalConfig: {
|
|
498
|
-
viewModal: {
|
|
499
|
-
title: "User Details",
|
|
500
|
-
variant: "card", // Each field is a separate card
|
|
501
|
-
styles: {
|
|
502
|
-
containerClass: "grid grid-cols-12 gap-4",
|
|
503
|
-
cardGroupClass: "col-span-6 bg-white rounded-lg shadow p-4"
|
|
644
|
+
tableConfig: {
|
|
645
|
+
filter: { enabled: true, useServerSideFilters: true },
|
|
646
|
+
filterConfig: {
|
|
647
|
+
fields: [
|
|
648
|
+
{
|
|
649
|
+
key: "status", label: "Status", type: "select",
|
|
650
|
+
options: [
|
|
651
|
+
{ value: "active", label: "Active" },
|
|
652
|
+
{ value: "inactive", label: "Inactive" },
|
|
653
|
+
],
|
|
504
654
|
},
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
// Split variant - property sheet style with dividing lines
|
|
511
|
-
const splitConfig = {
|
|
512
|
-
modalConfig: {
|
|
513
|
-
viewModal: {
|
|
514
|
-
title: "User Details",
|
|
515
|
-
variant: "split", // Clean property-sheet layout
|
|
516
|
-
fields: [...]
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
### 6. Custom View Component
|
|
523
|
-
|
|
524
|
-
Use a fully custom component for the view modal:
|
|
525
|
-
|
|
526
|
-
```js
|
|
527
|
-
const CustomUserView = ({ data }) => (
|
|
528
|
-
<div className="space-y-4">
|
|
529
|
-
<div className="flex items-center gap-4">
|
|
530
|
-
<img
|
|
531
|
-
src={data.avatarUrl}
|
|
532
|
-
alt={data.name}
|
|
533
|
-
className="w-16 h-16 rounded-full"
|
|
534
|
-
/>
|
|
535
|
-
<div>
|
|
536
|
-
<h2 className="text-xl font-bold">{data.name}</h2>
|
|
537
|
-
<p className="text-gray-600">{data.email}</p>
|
|
538
|
-
</div>
|
|
539
|
-
</div>
|
|
540
|
-
<div className="grid grid-cols-2 gap-4">
|
|
541
|
-
<div>
|
|
542
|
-
<label className="text-sm font-semibold">Phone</label>
|
|
543
|
-
<p>{data.phone}</p>
|
|
544
|
-
</div>
|
|
545
|
-
<div>
|
|
546
|
-
<label className="text-sm font-semibold">Status</label>
|
|
547
|
-
<p>{data.status}</p>
|
|
548
|
-
</div>
|
|
549
|
-
</div>
|
|
550
|
-
</div>
|
|
551
|
-
);
|
|
552
|
-
|
|
553
|
-
const config = {
|
|
554
|
-
modalConfig: {
|
|
555
|
-
viewModal: {
|
|
556
|
-
title: "User Details",
|
|
557
|
-
component: CustomUserView, // Use custom component
|
|
558
|
-
},
|
|
655
|
+
{ key: "createdFrom", label: "From", type: "date" },
|
|
656
|
+
{ key: "createdTo", label: "To", type: "date" },
|
|
657
|
+
],
|
|
559
658
|
},
|
|
560
|
-
}
|
|
659
|
+
},
|
|
660
|
+
fetchData: async ({ search, rows_per_page, current_page, sort_by, sort_order, ...filters }) => {
|
|
661
|
+
const resp = await api.get("/users", {
|
|
662
|
+
params: { q: search, limit: rows_per_page, page: current_page, sort_by, sort_order, ...filters },
|
|
663
|
+
});
|
|
664
|
+
return { data: resp.data.items, pagination: resp.data.pagination };
|
|
665
|
+
},
|
|
561
666
|
```
|
|
562
667
|
|
|
563
|
-
|
|
668
|
+
---
|
|
564
669
|
|
|
565
|
-
|
|
670
|
+
### 6. Image Cropping
|
|
566
671
|
|
|
567
672
|
```js
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
console.log(`Row ${rowIndex} clicked:`, row);
|
|
575
|
-
// Open custom drawer, navigate, or perform any action
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
// For open details on row click
|
|
581
|
-
const config = {
|
|
582
|
-
tableConfig: {
|
|
583
|
-
table_head: [...],
|
|
584
|
-
data: [...],
|
|
585
|
-
rowClick: true,
|
|
586
|
-
}
|
|
587
|
-
};
|
|
673
|
+
{
|
|
674
|
+
key: "profileImage", label: "Profile Picture", type: "image",
|
|
675
|
+
cropImage: true,
|
|
676
|
+
aspectRatio: 1, // 1:1 square
|
|
677
|
+
dragDrop: true,
|
|
678
|
+
}
|
|
588
679
|
|
|
680
|
+
{
|
|
681
|
+
key: "banner", label: "Banner", type: "image",
|
|
682
|
+
cropImage: true,
|
|
683
|
+
aspectRatio: 16 / 9,
|
|
684
|
+
dragDrop: true,
|
|
685
|
+
}
|
|
589
686
|
```
|
|
590
687
|
|
|
591
|
-
|
|
688
|
+
---
|
|
592
689
|
|
|
593
|
-
|
|
690
|
+
### 7. Input Masking
|
|
594
691
|
|
|
595
692
|
```js
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
},
|
|
602
|
-
filterConfig: {
|
|
603
|
-
fields: [
|
|
604
|
-
{
|
|
605
|
-
key: "status",
|
|
606
|
-
label: "Status",
|
|
607
|
-
type: "select",
|
|
608
|
-
options: [
|
|
609
|
-
{ value: "active", label: "Active" },
|
|
610
|
-
{ value: "inactive", label: "Inactive" },
|
|
611
|
-
],
|
|
612
|
-
},
|
|
613
|
-
{
|
|
614
|
-
key: "createdFrom",
|
|
615
|
-
label: "Created From",
|
|
616
|
-
type: "date",
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
key: "createdTo",
|
|
620
|
-
label: "Created To",
|
|
621
|
-
type: "date",
|
|
622
|
-
},
|
|
623
|
-
],
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
fetchData: async ({
|
|
627
|
-
search,
|
|
628
|
-
rows_per_page,
|
|
629
|
-
current_page,
|
|
630
|
-
sort_by,
|
|
631
|
-
sort_order,
|
|
632
|
-
...filters // Additional filter params passed here
|
|
633
|
-
}) => {
|
|
634
|
-
const resp = await api.get("/users", {
|
|
635
|
-
params: {
|
|
636
|
-
q: search,
|
|
637
|
-
limit: rows_per_page,
|
|
638
|
-
page: current_page,
|
|
639
|
-
sort_by,
|
|
640
|
-
sort_order,
|
|
641
|
-
...filters, // Include filters in API call
|
|
642
|
-
},
|
|
643
|
-
});
|
|
644
|
-
return { data: resp.items, pagination: resp.pagination };
|
|
645
|
-
},
|
|
646
|
-
};
|
|
693
|
+
// Pattern: 9 = digit | A = letter | X = alphanumeric | * = any | other = literal
|
|
694
|
+
|
|
695
|
+
{ key: "phone", type: "text", mask: "(99) 99999-9999" }
|
|
696
|
+
{ key: "zip", type: "text", mask: "99999-999" }
|
|
697
|
+
{ key: "creditCard", type: "text", mask: "9999 9999 9999 9999" }
|
|
647
698
|
```
|
|
648
699
|
|
|
649
|
-
|
|
700
|
+
---
|
|
650
701
|
|
|
651
|
-
|
|
702
|
+
### 8. Custom Table Cell Rendering
|
|
652
703
|
|
|
653
|
-
```
|
|
654
|
-
|
|
704
|
+
```jsx
|
|
705
|
+
table_head: [
|
|
655
706
|
{
|
|
656
|
-
key: "
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
707
|
+
key: "price",
|
|
708
|
+
title: "Price",
|
|
709
|
+
render: (row) => (
|
|
710
|
+
<span className="text-green-600 font-semibold">
|
|
711
|
+
${row.price.toFixed(2)}
|
|
712
|
+
</span>
|
|
713
|
+
),
|
|
662
714
|
},
|
|
663
715
|
{
|
|
664
|
-
key: "
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
placeholder: "0000 0000 0000 0000",
|
|
716
|
+
key: "user",
|
|
717
|
+
title: "User",
|
|
718
|
+
render: (row) => (
|
|
719
|
+
<div className="flex items-center gap-3">
|
|
720
|
+
<img src={row.avatar} className="w-8 h-8 rounded-full" />
|
|
721
|
+
<div>
|
|
722
|
+
<p className="font-semibold">{row.name}</p>
|
|
723
|
+
<p className="text-xs text-gray-500">{row.email}</p>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
),
|
|
676
727
|
},
|
|
677
728
|
];
|
|
678
729
|
```
|
|
679
730
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
- `9` — Digit (0-9)
|
|
683
|
-
- `A` — Letter (a-zA-Z)
|
|
684
|
-
- `X` — Alphanumeric (a-zA-Z0-9)
|
|
685
|
-
- `*` — Any character
|
|
686
|
-
- Any other character is literal (e.g., `-`, `/`, ` `)
|
|
731
|
+
---
|
|
687
732
|
|
|
688
|
-
###
|
|
733
|
+
### 9. Custom Toolbar Buttons
|
|
689
734
|
|
|
690
|
-
|
|
735
|
+
```jsx
|
|
736
|
+
import { Upload, RefreshCw } from "lucide-react";
|
|
691
737
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
]
|
|
738
|
+
tableConfig: {
|
|
739
|
+
customButtons: [
|
|
740
|
+
{
|
|
741
|
+
key: "import", label: "Import",
|
|
742
|
+
icon: <Upload className="w-4 h-4" />,
|
|
743
|
+
color: "primary", variant: "contained",
|
|
744
|
+
onClick: (event, ctx) => {
|
|
745
|
+
// ctx: { data, filteredData, sortedData, paginatedData,
|
|
746
|
+
// searchTerm, appliedFilters, currentPage, pageSize, totalRecords }
|
|
747
|
+
console.log("Total records:", ctx.totalRecords);
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
key: "refresh", label: "Refresh",
|
|
752
|
+
icon: <RefreshCw className="w-4 h-4" />,
|
|
753
|
+
variant: "outlined",
|
|
754
|
+
onClick: async () => { /* refresh logic */ },
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
customMenuItems: [
|
|
758
|
+
{
|
|
759
|
+
key: "bulk-delete", label: "Bulk Delete",
|
|
760
|
+
onClick: (event, ctx) => console.log(ctx.filteredData),
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
}
|
|
711
764
|
```
|
|
712
765
|
|
|
713
|
-
|
|
766
|
+
**Button properties:** `key`, `label`, `icon`, `variant` (`contained` / `outlined` / `text`), `color` (`primary` / `success` / `error` / `default`), `className`, `disabled`, `show`, `onClick`
|
|
714
767
|
|
|
715
|
-
|
|
768
|
+
**Menu item properties:** `key`, `label`, `icon`, `className`, `disabled`, `show`, `onClick`
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
### 10. Row Click Handler
|
|
716
773
|
|
|
717
774
|
```js
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
useServerSideSorting: false, // Client-side sorting
|
|
723
|
-
autoGenerate: true, // Auto-generate sort options from table headers
|
|
724
|
-
fields: ["name", "email", "createdAt"], // Fields to make sortable
|
|
725
|
-
defaultValue: "name", // Default sort field
|
|
726
|
-
clearLabel: "Clear Sort",
|
|
727
|
-
onChange: (payload) => {
|
|
728
|
-
console.log("Sort changed:", payload);
|
|
729
|
-
// payload contains: { value, option, key, order, type }
|
|
730
|
-
},
|
|
731
|
-
options: [
|
|
732
|
-
{
|
|
733
|
-
value: "name_asc",
|
|
734
|
-
label: "Name (A-Z)",
|
|
735
|
-
key: "name",
|
|
736
|
-
order: "asc",
|
|
737
|
-
type: "string",
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
value: "name_desc",
|
|
741
|
-
label: "Name (Z-A)",
|
|
742
|
-
key: "name",
|
|
743
|
-
order: "desc",
|
|
744
|
-
type: "string",
|
|
745
|
-
},
|
|
746
|
-
],
|
|
747
|
-
},
|
|
748
|
-
table_head: [
|
|
749
|
-
{ key: "name", title: "Name" },
|
|
750
|
-
{ key: "email", title: "Email" },
|
|
751
|
-
{ key: "createdAt", title: "Created Date" },
|
|
752
|
-
],
|
|
775
|
+
// Custom handler
|
|
776
|
+
tableConfig: {
|
|
777
|
+
rowClick: (row, rowIndex) => {
|
|
778
|
+
console.log("Clicked row:", row);
|
|
753
779
|
},
|
|
754
|
-
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Open view modal on row click
|
|
783
|
+
tableConfig: {
|
|
784
|
+
rowClick: true,
|
|
785
|
+
}
|
|
755
786
|
```
|
|
756
787
|
|
|
757
|
-
|
|
788
|
+
---
|
|
758
789
|
|
|
759
|
-
|
|
790
|
+
### 11. View Modal Variants
|
|
760
791
|
|
|
761
792
|
```js
|
|
762
|
-
|
|
793
|
+
// default — standard grid layout
|
|
794
|
+
viewModal: { title: "Details", variant: "default", fields: [...] }
|
|
795
|
+
|
|
796
|
+
// card — each field in its own elevated card
|
|
797
|
+
viewModal: {
|
|
798
|
+
title: "Details", variant: "card",
|
|
799
|
+
styles: { containerClass: "grid grid-cols-12 gap-4" },
|
|
800
|
+
fields: [...],
|
|
801
|
+
}
|
|
763
802
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
table_head: [...],
|
|
767
|
-
search: { enabled: true },
|
|
768
|
-
filter: { enabled: true },
|
|
769
|
-
sort: { enabled: true },
|
|
770
|
-
customButtons: [
|
|
771
|
-
{
|
|
772
|
-
key: "import",
|
|
773
|
-
label: "Import",
|
|
774
|
-
icon: <Upload className="w-4 h-4" />,
|
|
775
|
-
color: "primary",
|
|
776
|
-
variant: "contained",
|
|
777
|
-
onClick: (event, ctx) => {
|
|
778
|
-
// ctx has: data, filteredData, sortedData, paginatedData,
|
|
779
|
-
// searchTerm, appliedFilters, currentPage, pageSize, totalRecords
|
|
780
|
-
console.log("Import clicked", ctx.totalRecords);
|
|
781
|
-
},
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
key: "refresh",
|
|
785
|
-
label: "Refresh",
|
|
786
|
-
icon: <RefreshCw className="w-4 h-4" />,
|
|
787
|
-
variant: "outlined",
|
|
788
|
-
onClick: async () => {
|
|
789
|
-
// your custom logic
|
|
790
|
-
},
|
|
791
|
-
},
|
|
792
|
-
],
|
|
793
|
-
customMenuItems: [
|
|
794
|
-
{
|
|
795
|
-
key: "bulk-actions",
|
|
796
|
-
label: "Bulk Action",
|
|
797
|
-
onClick: (event, ctx) => {
|
|
798
|
-
console.log("bulk action", ctx.filteredData.length);
|
|
799
|
-
},
|
|
800
|
-
},
|
|
801
|
-
{
|
|
802
|
-
key: "archive-all",
|
|
803
|
-
label: "Archive Filtered",
|
|
804
|
-
onClick: async (event, ctx) => {
|
|
805
|
-
// run async logic using current filtered records
|
|
806
|
-
},
|
|
807
|
-
},
|
|
808
|
-
],
|
|
809
|
-
},
|
|
810
|
-
};
|
|
803
|
+
// split — clean property-sheet with dividing lines
|
|
804
|
+
viewModal: { title: "Details", variant: "split", fields: [...] }
|
|
811
805
|
```
|
|
812
806
|
|
|
813
|
-
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
### 12. Custom View Component
|
|
814
810
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
-
|
|
822
|
-
-
|
|
823
|
-
|
|
811
|
+
```jsx
|
|
812
|
+
const UserProfile = ({ data }) => (
|
|
813
|
+
<div className="space-y-4">
|
|
814
|
+
<div className="flex items-center gap-4">
|
|
815
|
+
<img src={data.avatarUrl} className="w-16 h-16 rounded-full" />
|
|
816
|
+
<div>
|
|
817
|
+
<h2 className="text-xl font-bold">{data.name}</h2>
|
|
818
|
+
<p className="text-gray-500">{data.email}</p>
|
|
819
|
+
</div>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
);
|
|
824
823
|
|
|
825
|
-
|
|
824
|
+
modalConfig: {
|
|
825
|
+
viewModal: {
|
|
826
|
+
title: "User Details",
|
|
827
|
+
component: UserProfile, // receives { data } prop
|
|
828
|
+
},
|
|
829
|
+
}
|
|
830
|
+
```
|
|
826
831
|
|
|
827
|
-
|
|
828
|
-
- `label` (required): menu item text
|
|
829
|
-
- `icon` (optional): React node before label
|
|
830
|
-
- `className` (optional): custom class string
|
|
831
|
-
- `disabled` (optional): disable item
|
|
832
|
-
- `show` (optional): set `false` to hide item
|
|
833
|
-
- `onClick` (optional): `(event, context) => void | Promise<void>`
|
|
832
|
+
---
|
|
834
833
|
|
|
835
|
-
|
|
834
|
+
## CSS Customization
|
|
836
835
|
|
|
837
|
-
|
|
836
|
+
### Primary Color Override
|
|
838
837
|
|
|
839
|
-
|
|
838
|
+
Add these CSS variables to your global stylesheet:
|
|
840
839
|
|
|
841
840
|
```css
|
|
842
841
|
:root {
|
|
@@ -853,733 +852,75 @@ Simply add the following variables to your main CSS file (e.g., `root.css`, `glo
|
|
|
853
852
|
}
|
|
854
853
|
```
|
|
855
854
|
|
|
856
|
-
### CSS Class Customization
|
|
857
|
-
|
|
858
|
-
The following table lists all available CSS classes that can be overridden to customize the UI.
|
|
859
|
-
|
|
860
|
-
---
|
|
861
|
-
|
|
862
|
-
#### Class Reference
|
|
863
|
-
|
|
864
|
-
| Component | Key | Class Name | Description |
|
|
865
|
-
| ------------------ | ------------------ | ------------------------------- | --------------------------- |
|
|
866
|
-
| **Crud Page** | `root` | `crud_page` | Main container of CRUD page |
|
|
867
|
-
| | `deleteContent` | `crud_page_delete_content` | Delete confirmation content |
|
|
868
|
-
| **Button** | `root` | `crud_button` | Default button styling |
|
|
869
|
-
| **Chip** | `root` | `crud_chip` | Chip/tag element |
|
|
870
|
-
| **Spinner** | `root` | `crud_spinner` | Loading spinner |
|
|
871
|
-
| **Modal** | `root` | `crud_modal` | Modal root |
|
|
872
|
-
| | `overlay` | `crud_modal_overlay` | Modal overlay |
|
|
873
|
-
| | `container` | `crud_modal_container` | Modal container |
|
|
874
|
-
| | `header` | `crud_modal_header` | Modal header |
|
|
875
|
-
| | `title` | `crud_modal_title` | Modal title |
|
|
876
|
-
| | `closeButton` | `crud_modal_close_button` | Close button |
|
|
877
|
-
| | `body` | `crud_modal_body` | Modal body |
|
|
878
|
-
| | `footer` | `crud_modal_footer` | Modal footer |
|
|
879
|
-
| | `actionButton` | `crud_modal_action_button` | Modal action button |
|
|
880
|
-
| | `loadingIndicator` | `crud_modal_loading_indicator` | Modal loading state |
|
|
881
|
-
| **Table** | `root` | `crud_table` | Table wrapper |
|
|
882
|
-
| | `toolbar` | `crud_table_toolbar` | Table toolbar |
|
|
883
|
-
| | `searchField` | `crud_table_search_field` | Search field wrapper |
|
|
884
|
-
| | `searchInput` | `crud_table_search_input` | Search input |
|
|
885
|
-
| | `container` | `crud_table_container` | Table container |
|
|
886
|
-
| | `element` | `crud_table_element` | Table element |
|
|
887
|
-
| | `head` | `crud_table_head` | Table head |
|
|
888
|
-
| | `headRow` | `crud_table_head_row` | Header row |
|
|
889
|
-
| | `headCell` | `crud_table_head_cell` | Header cell |
|
|
890
|
-
| | `body` | `crud_table_body` | Table body |
|
|
891
|
-
| | `row` | `crud_table_row` | Table row |
|
|
892
|
-
| | `cell` | `crud_table_cell` | Table cell |
|
|
893
|
-
| | `noData` | `crud_table_no_data` | No data state |
|
|
894
|
-
| | `actionButton` | `crud_table_action_button` | Row action button |
|
|
895
|
-
| | `menu` | `crud_table_menu` | Action menu |
|
|
896
|
-
| | `menuItem` | `crud_table_menu_item` | Menu item |
|
|
897
|
-
| | `pagination` | `crud_table_pagination` | Pagination wrapper |
|
|
898
|
-
| **Table Skeleton** | `root` | `crud_table_skeleton` | Skeleton loader wrapper |
|
|
899
|
-
| | `table` | `crud_table_skeleton_table` | Skeleton table |
|
|
900
|
-
| **Sort Dropdown** | `root` | `crud_sort_dropdown` | Sort dropdown root |
|
|
901
|
-
| | `trigger` | `crud_sort_dropdown_trigger` | Dropdown trigger |
|
|
902
|
-
| | `menu` | `crud_sort_dropdown_menu` | Dropdown menu |
|
|
903
|
-
| | `item` | `crud_sort_dropdown_item` | Dropdown item |
|
|
904
|
-
| **Image Preview** | `root` | `crud_image_preview` | Image preview root |
|
|
905
|
-
| | `container` | `crud_image_preview_container` | Image container |
|
|
906
|
-
| | `image` | `crud_image_preview_image` | Image element |
|
|
907
|
-
| **Filter Drawer** | `overlay` | `crud_filter_overlay` | Drawer overlay |
|
|
908
|
-
| | `panel` | `crud_filter_panel` | Drawer panel |
|
|
909
|
-
| | `header` | `crud_filter_header` | Drawer header |
|
|
910
|
-
| | `body` | `crud_filter_body` | Drawer body |
|
|
911
|
-
| | `footer` | `crud_filter_footer` | Drawer footer |
|
|
912
|
-
| **Form** | `root` | `crud_form` | Form wrapper |
|
|
913
|
-
| | `loading` | `crud_form_loading` | Form loading state |
|
|
914
|
-
| **Field** | `wrapper` | `crud_field_wrapper` | Field wrapper |
|
|
915
|
-
| | `label` | `crud_field_label` | Field label |
|
|
916
|
-
| | `input` | `crud_field_input` | Input field |
|
|
917
|
-
| | `error` | `crud_field_error` | Error message |
|
|
918
|
-
| **Media Picker** | `image` | `crud_media_image_picker` | Image picker |
|
|
919
|
-
| | `multiImage` | `crud_media_multi_image_picker` | Multi-image picker |
|
|
920
|
-
| | `audio` | `crud_media_audio_picker` | Audio picker |
|
|
921
|
-
| | `video` | `crud_media_video_picker` | Video picker |
|
|
922
|
-
| | `dropzone` | `crud_media_dropzone` | File dropzone |
|
|
923
|
-
| | `cropModal` | `crud_media_crop_modal` | Crop modal |
|
|
924
|
-
| **Details** | `root` | `crud_details` | Details root |
|
|
925
|
-
| | `container` | `crud_details_container` | Details container |
|
|
926
|
-
| | `row` | `crud_details_row` | Details row |
|
|
927
|
-
|
|
928
855
|
---
|
|
929
856
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
return <Crud config={config} />;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
export default App;
|
|
1002
|
-
```
|
|
1003
|
-
|
|
1004
|
-
#### Example 2: Server-Side CRUD with Advanced Features
|
|
1005
|
-
|
|
1006
|
-
```js
|
|
1007
|
-
import Crud from "react-admin-crud-manager";
|
|
1008
|
-
import axios from "axios";
|
|
1009
|
-
import { PlusIcon, EditIcon, TrashIcon } from "lucide-react";
|
|
1010
|
-
|
|
1011
|
-
function App() {
|
|
1012
|
-
const api = axios.create({ baseURL: "https://api.example.com" });
|
|
1013
|
-
|
|
1014
|
-
const config = {
|
|
1015
|
-
title: "Products",
|
|
1016
|
-
description: "Manage your products inventory",
|
|
1017
|
-
buttonText: "Add Product",
|
|
1018
|
-
fetchData: async ({
|
|
1019
|
-
search,
|
|
1020
|
-
rows_per_page,
|
|
1021
|
-
current_page,
|
|
1022
|
-
sort_by,
|
|
1023
|
-
sort_order,
|
|
1024
|
-
category,
|
|
1025
|
-
minPrice,
|
|
1026
|
-
maxPrice,
|
|
1027
|
-
}) => {
|
|
1028
|
-
const resp = await api.get("/products", {
|
|
1029
|
-
params: {
|
|
1030
|
-
q: search,
|
|
1031
|
-
limit: rows_per_page,
|
|
1032
|
-
page: current_page,
|
|
1033
|
-
sort_by,
|
|
1034
|
-
sort_order,
|
|
1035
|
-
category,
|
|
1036
|
-
minPrice,
|
|
1037
|
-
maxPrice,
|
|
1038
|
-
},
|
|
1039
|
-
});
|
|
1040
|
-
return {
|
|
1041
|
-
data: resp.data.items,
|
|
1042
|
-
pagination: {
|
|
1043
|
-
current_page: resp.data.page,
|
|
1044
|
-
rows_per_page: resp.data.limit,
|
|
1045
|
-
total_pages: resp.data.totalPages,
|
|
1046
|
-
total_records: resp.data.total,
|
|
1047
|
-
},
|
|
1048
|
-
};
|
|
1049
|
-
},
|
|
1050
|
-
tableConfig: {
|
|
1051
|
-
table_head: [
|
|
1052
|
-
{ key: "id", title: "ID", type: "index" },
|
|
1053
|
-
{
|
|
1054
|
-
key: "image",
|
|
1055
|
-
title: "Image",
|
|
1056
|
-
type: "avatar",
|
|
1057
|
-
imageKey: "image",
|
|
1058
|
-
titleKey: "name",
|
|
1059
|
-
},
|
|
1060
|
-
{ key: "name", title: "Product Name" },
|
|
1061
|
-
{
|
|
1062
|
-
key: "price",
|
|
1063
|
-
title: "Price",
|
|
1064
|
-
render: (row) => `$${row.price.toFixed(2)}`,
|
|
1065
|
-
},
|
|
1066
|
-
{
|
|
1067
|
-
key: "category",
|
|
1068
|
-
title: "Category",
|
|
1069
|
-
type: "chip",
|
|
1070
|
-
variant: "soft",
|
|
1071
|
-
chipOptions: [
|
|
1072
|
-
{ value: "electronics", label: "Electronics", color: "blue" },
|
|
1073
|
-
{ value: "clothing", label: "Clothing", color: "purple" },
|
|
1074
|
-
],
|
|
1075
|
-
},
|
|
1076
|
-
],
|
|
1077
|
-
search: {
|
|
1078
|
-
enabled: true,
|
|
1079
|
-
useServerSideSearch: true,
|
|
1080
|
-
},
|
|
1081
|
-
filter: { enabled: true, useServerSideFilters: true },
|
|
1082
|
-
pagination: { enabled: true, useServerSidePagination: true },
|
|
1083
|
-
sort: { enabled: true, useServerSideSorting: true, autoGenerate: true },
|
|
1084
|
-
exportCSV: {
|
|
1085
|
-
enabled: true,
|
|
1086
|
-
fileName: "products.csv",
|
|
1087
|
-
fields: [
|
|
1088
|
-
{ label: "ID", key: "id" },
|
|
1089
|
-
{ label: "Name", key: "name" },
|
|
1090
|
-
{ label: "Price", key: "price" },
|
|
1091
|
-
],
|
|
1092
|
-
},
|
|
1093
|
-
filterConfig: {
|
|
1094
|
-
fields: [
|
|
1095
|
-
{
|
|
1096
|
-
key: "category",
|
|
1097
|
-
label: "Category",
|
|
1098
|
-
type: "select",
|
|
1099
|
-
options: [
|
|
1100
|
-
{ value: "electronics", label: "Electronics" },
|
|
1101
|
-
{ value: "clothing", label: "Clothing" },
|
|
1102
|
-
],
|
|
1103
|
-
},
|
|
1104
|
-
{ key: "minPrice", label: "Min Price", type: "number" },
|
|
1105
|
-
{ key: "maxPrice", label: "Max Price", type: "number" },
|
|
1106
|
-
],
|
|
1107
|
-
},
|
|
1108
|
-
},
|
|
1109
|
-
modalConfig: {
|
|
1110
|
-
addModal: {
|
|
1111
|
-
title: "Add Product",
|
|
1112
|
-
size: "lg",
|
|
1113
|
-
icon: <PlusIcon />,
|
|
1114
|
-
formFields: [
|
|
1115
|
-
{
|
|
1116
|
-
key: "name",
|
|
1117
|
-
label: "Product Name",
|
|
1118
|
-
type: "text",
|
|
1119
|
-
required: true,
|
|
1120
|
-
parentClass: "col-span-12",
|
|
1121
|
-
},
|
|
1122
|
-
{
|
|
1123
|
-
key: "price",
|
|
1124
|
-
label: "Price",
|
|
1125
|
-
type: "number",
|
|
1126
|
-
required: true,
|
|
1127
|
-
parentClass: "col-span-6",
|
|
1128
|
-
},
|
|
1129
|
-
{
|
|
1130
|
-
key: "category",
|
|
1131
|
-
label: "Category",
|
|
1132
|
-
type: "select",
|
|
1133
|
-
required: true,
|
|
1134
|
-
parentClass: "col-span-6",
|
|
1135
|
-
options: [
|
|
1136
|
-
{ value: "electronics", label: "Electronics" },
|
|
1137
|
-
{ value: "clothing", label: "Clothing" },
|
|
1138
|
-
],
|
|
1139
|
-
},
|
|
1140
|
-
{
|
|
1141
|
-
key: "stock",
|
|
1142
|
-
label: "Stock",
|
|
1143
|
-
type: "number",
|
|
1144
|
-
required: true,
|
|
1145
|
-
parentClass: "col-span-6",
|
|
1146
|
-
},
|
|
1147
|
-
{
|
|
1148
|
-
key: "description",
|
|
1149
|
-
label: "Description",
|
|
1150
|
-
type: "textarea",
|
|
1151
|
-
rows: 4,
|
|
1152
|
-
parentClass: "col-span-12",
|
|
1153
|
-
},
|
|
1154
|
-
{
|
|
1155
|
-
key: "image",
|
|
1156
|
-
label: "Product Image",
|
|
1157
|
-
type: "image",
|
|
1158
|
-
cropImage: true,
|
|
1159
|
-
aspectRatio: 1,
|
|
1160
|
-
dragDrop: true,
|
|
1161
|
-
parentClass: "col-span-12",
|
|
1162
|
-
},
|
|
1163
|
-
],
|
|
1164
|
-
handleSubmit: async (formData) => {
|
|
1165
|
-
const resp = await api.post("/products", formData);
|
|
1166
|
-
return {
|
|
1167
|
-
newObject: resp.data,
|
|
1168
|
-
message: "Product added successfully!",
|
|
1169
|
-
};
|
|
1170
|
-
},
|
|
1171
|
-
},
|
|
1172
|
-
editModal: {
|
|
1173
|
-
title: "Edit Product",
|
|
1174
|
-
size: "lg",
|
|
1175
|
-
icon: <EditIcon />,
|
|
1176
|
-
formFields: [
|
|
1177
|
-
{
|
|
1178
|
-
key: "name",
|
|
1179
|
-
label: "Product Name",
|
|
1180
|
-
type: "text",
|
|
1181
|
-
required: true,
|
|
1182
|
-
parentClass: "col-span-12",
|
|
1183
|
-
},
|
|
1184
|
-
{
|
|
1185
|
-
key: "price",
|
|
1186
|
-
label: "Price",
|
|
1187
|
-
type: "number",
|
|
1188
|
-
required: true,
|
|
1189
|
-
parentClass: "col-span-6",
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
key: "stock",
|
|
1193
|
-
label: "Stock",
|
|
1194
|
-
type: "number",
|
|
1195
|
-
required: true,
|
|
1196
|
-
parentClass: "col-span-6",
|
|
1197
|
-
},
|
|
1198
|
-
],
|
|
1199
|
-
handleSubmit: async (formData, item) => {
|
|
1200
|
-
const resp = await api.put(`/products/${item.id}`, formData);
|
|
1201
|
-
return { newObject: resp.data, targetObject: item };
|
|
1202
|
-
},
|
|
1203
|
-
},
|
|
1204
|
-
deleteModal: {
|
|
1205
|
-
title: "Delete Product",
|
|
1206
|
-
icon: <TrashIcon />,
|
|
1207
|
-
confirmText: "Are you sure you want to delete this product?",
|
|
1208
|
-
referenceKey: "name",
|
|
1209
|
-
actionButtons: [
|
|
1210
|
-
{
|
|
1211
|
-
type: "button",
|
|
1212
|
-
label: "Delete",
|
|
1213
|
-
color: "error",
|
|
1214
|
-
variant: "contained",
|
|
1215
|
-
onClick: async (event, item) => {
|
|
1216
|
-
event.preventDefault();
|
|
1217
|
-
await api.delete(`/products/${item.id}`);
|
|
1218
|
-
return { targetObject: item };
|
|
1219
|
-
},
|
|
1220
|
-
},
|
|
1221
|
-
],
|
|
1222
|
-
},
|
|
1223
|
-
viewModal: {
|
|
1224
|
-
title: "Product Details",
|
|
1225
|
-
variant: "card",
|
|
1226
|
-
fields: [
|
|
1227
|
-
{ key: "id", label: "ID" },
|
|
1228
|
-
{ key: "name", label: "Product Name" },
|
|
1229
|
-
{ key: "price", label: "Price" },
|
|
1230
|
-
{ key: "stock", label: "Stock" },
|
|
1231
|
-
{ key: "category", label: "Category", type: "chip" },
|
|
1232
|
-
],
|
|
1233
|
-
},
|
|
1234
|
-
},
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
return <Crud config={config} />;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
export default App;
|
|
1241
|
-
```
|
|
1242
|
-
|
|
1243
|
-
#### Example 3: Complex Form with Conditional Fields and Validation
|
|
1244
|
-
|
|
1245
|
-
```js
|
|
1246
|
-
const config = {
|
|
1247
|
-
title: "Advanced User Registration",
|
|
1248
|
-
tableConfig: {
|
|
1249
|
-
table_head: [
|
|
1250
|
-
{ key: "id", title: "ID", type: "index" },
|
|
1251
|
-
{ key: "name", title: "Full Name" },
|
|
1252
|
-
{ key: "email", title: "Email" },
|
|
1253
|
-
{ key: "userType", title: "Type", type: "chip" },
|
|
1254
|
-
],
|
|
1255
|
-
},
|
|
1256
|
-
modalConfig: {
|
|
1257
|
-
addModal: {
|
|
1258
|
-
title: "Register New User",
|
|
1259
|
-
formFields: [
|
|
1260
|
-
{
|
|
1261
|
-
key: "name",
|
|
1262
|
-
label: "Full Name",
|
|
1263
|
-
type: "text",
|
|
1264
|
-
required: true,
|
|
1265
|
-
parentClass: "col-span-12",
|
|
1266
|
-
customValidation: (value) => {
|
|
1267
|
-
if (value.trim().split(" ").length < 2) {
|
|
1268
|
-
return "Please enter your full name";
|
|
1269
|
-
}
|
|
1270
|
-
return true;
|
|
1271
|
-
},
|
|
1272
|
-
},
|
|
1273
|
-
{
|
|
1274
|
-
key: "email",
|
|
1275
|
-
label: "Email Address",
|
|
1276
|
-
type: "email",
|
|
1277
|
-
required: true,
|
|
1278
|
-
parentClass: "col-span-12",
|
|
1279
|
-
customValidation: (value) => {
|
|
1280
|
-
if (!value.includes("@company.com")) {
|
|
1281
|
-
return "Must use company email";
|
|
1282
|
-
}
|
|
1283
|
-
return true;
|
|
1284
|
-
},
|
|
1285
|
-
},
|
|
1286
|
-
{
|
|
1287
|
-
key: "phone",
|
|
1288
|
-
label: "Phone",
|
|
1289
|
-
type: "phone",
|
|
1290
|
-
countriesList: true,
|
|
1291
|
-
mask: "(99) 99999-9999",
|
|
1292
|
-
parentClass: "col-span-12",
|
|
1293
|
-
},
|
|
1294
|
-
{
|
|
1295
|
-
key: "userType",
|
|
1296
|
-
label: "User Type",
|
|
1297
|
-
type: "select",
|
|
1298
|
-
required: true,
|
|
1299
|
-
parentClass: "col-span-6",
|
|
1300
|
-
options: [
|
|
1301
|
-
{ value: "admin", label: "Administrator" },
|
|
1302
|
-
{ value: "user", label: "Regular User" },
|
|
1303
|
-
],
|
|
1304
|
-
},
|
|
1305
|
-
{
|
|
1306
|
-
key: "adminLevel",
|
|
1307
|
-
label: "Admin Level",
|
|
1308
|
-
type: "select",
|
|
1309
|
-
parentClass: "col-span-6",
|
|
1310
|
-
renderCondition: (data) => data.userType === "admin",
|
|
1311
|
-
options: [
|
|
1312
|
-
{ value: "superadmin", label: "Super Admin" },
|
|
1313
|
-
{ value: "moderator", label: "Moderator" },
|
|
1314
|
-
],
|
|
1315
|
-
},
|
|
1316
|
-
{
|
|
1317
|
-
key: "permissions",
|
|
1318
|
-
label: "Permissions",
|
|
1319
|
-
type: "checkbox",
|
|
1320
|
-
parentClass: "col-span-12",
|
|
1321
|
-
multiple: true,
|
|
1322
|
-
renderCondition: (data) => data.userType === "admin",
|
|
1323
|
-
options: [
|
|
1324
|
-
{ value: "read", label: "Can Read" },
|
|
1325
|
-
{ value: "write", label: "Can Write" },
|
|
1326
|
-
{ value: "delete", label: "Can Delete" },
|
|
1327
|
-
],
|
|
1328
|
-
},
|
|
1329
|
-
],
|
|
1330
|
-
handleSubmit: async (formData) => {
|
|
1331
|
-
const resp = await api.post("/users", formData);
|
|
1332
|
-
return {
|
|
1333
|
-
newObject: resp.data,
|
|
1334
|
-
message: `User ${formData.name} created successfully!`,
|
|
1335
|
-
};
|
|
1336
|
-
},
|
|
1337
|
-
},
|
|
1338
|
-
},
|
|
1339
|
-
};
|
|
1340
|
-
```
|
|
1341
|
-
|
|
1342
|
-
---
|
|
1343
|
-
|
|
1344
|
-
## Best Practices & Common Patterns
|
|
1345
|
-
|
|
1346
|
-
### 1. State Management with Static Data
|
|
1347
|
-
|
|
1348
|
-
For client-side CRUD operations, use `isStaticData: true` and manage data updates through modal handlers:
|
|
1349
|
-
|
|
1350
|
-
```js
|
|
1351
|
-
const [users, setUsers] = useState(initialUsers);
|
|
1352
|
-
|
|
1353
|
-
const config = {
|
|
1354
|
-
fetchData: async () => ({ data: users, pagination: null }),
|
|
1355
|
-
isStaticData: true,
|
|
1356
|
-
modalConfig: {
|
|
1357
|
-
addModal: {
|
|
1358
|
-
handleSubmit: async (formData) => {
|
|
1359
|
-
const newUser = { ...formData, id: Date.now() };
|
|
1360
|
-
setUsers([...users, newUser]);
|
|
1361
|
-
return { newObject: newUser };
|
|
1362
|
-
},
|
|
1363
|
-
},
|
|
1364
|
-
editModal: {
|
|
1365
|
-
handleSubmit: async (formData, item) => {
|
|
1366
|
-
const updated = { ...item, ...formData };
|
|
1367
|
-
setUsers(users.map((u) => (u.id === item.id ? updated : u)));
|
|
1368
|
-
return { newObject: updated, targetObject: item };
|
|
1369
|
-
},
|
|
1370
|
-
},
|
|
1371
|
-
},
|
|
1372
|
-
};
|
|
1373
|
-
```
|
|
1374
|
-
|
|
1375
|
-
### 2. Server-Side Operations Best Practices
|
|
1376
|
-
|
|
1377
|
-
```js
|
|
1378
|
-
// Always provide pagination info back to the component
|
|
1379
|
-
const fetchData = async (params) => {
|
|
1380
|
-
const resp = await api.get("/items", { params });
|
|
1381
|
-
return {
|
|
1382
|
-
data: resp.data.items || [],
|
|
1383
|
-
pagination: {
|
|
1384
|
-
current_page: resp.data.meta.currentPage,
|
|
1385
|
-
rows_per_page: resp.data.meta.perPage,
|
|
1386
|
-
total_pages: resp.data.meta.totalPages,
|
|
1387
|
-
total_records: resp.data.meta.total,
|
|
1388
|
-
},
|
|
1389
|
-
};
|
|
1390
|
-
};
|
|
1391
|
-
```
|
|
1392
|
-
|
|
1393
|
-
### 3. Error Handling in Modal Submissions
|
|
1394
|
-
|
|
1395
|
-
```js
|
|
1396
|
-
const handleSubmit = async (formData) => {
|
|
1397
|
-
try {
|
|
1398
|
-
const resp = await api.post("/items", formData);
|
|
1399
|
-
return {
|
|
1400
|
-
newObject: resp.data,
|
|
1401
|
-
message: "Item created successfully!", // Optional success message
|
|
1402
|
-
};
|
|
1403
|
-
} catch (error) {
|
|
1404
|
-
// Throw error to trigger snackbar error notification
|
|
1405
|
-
throw new Error(error.response?.data?.message || "Failed to create item");
|
|
1406
|
-
}
|
|
1407
|
-
};
|
|
1408
|
-
```
|
|
1409
|
-
|
|
1410
|
-
### 4. Dynamic Form Fields Based on Data
|
|
1411
|
-
|
|
1412
|
-
Combine `renderCondition` and `defaultValue` for dynamic forms:
|
|
1413
|
-
|
|
1414
|
-
```js
|
|
1415
|
-
const formFields = [
|
|
1416
|
-
{
|
|
1417
|
-
key: "type",
|
|
1418
|
-
type: "select",
|
|
1419
|
-
options: [
|
|
1420
|
-
{ value: "personal", label: "Personal" },
|
|
1421
|
-
{ value: "business", label: "Business" },
|
|
1422
|
-
],
|
|
1423
|
-
},
|
|
1424
|
-
{
|
|
1425
|
-
key: "companyName",
|
|
1426
|
-
label: "Company Name",
|
|
1427
|
-
type: "text",
|
|
1428
|
-
renderCondition: (data) => data.type === "business",
|
|
1429
|
-
},
|
|
1430
|
-
{
|
|
1431
|
-
key: "phone",
|
|
1432
|
-
type: "phone",
|
|
1433
|
-
renderCondition: (data) => data.type === "personal",
|
|
1434
|
-
},
|
|
1435
|
-
];
|
|
1436
|
-
```
|
|
1437
|
-
|
|
1438
|
-
### 5. Custom Table Rendering for Complex Data
|
|
1439
|
-
|
|
1440
|
-
```js
|
|
1441
|
-
const tableConfig = {
|
|
1442
|
-
table_head: [
|
|
1443
|
-
{
|
|
1444
|
-
key: "user",
|
|
1445
|
-
title: "User",
|
|
1446
|
-
render: (row) => (
|
|
1447
|
-
<div className="flex items-center gap-3">
|
|
1448
|
-
<img src={row.avatar} className="w-8 h-8 rounded-full" />
|
|
1449
|
-
<div>
|
|
1450
|
-
<p className="font-semibold">{row.name}</p>
|
|
1451
|
-
<p className="text-xs text-gray-500">{row.email}</p>
|
|
1452
|
-
</div>
|
|
1453
|
-
</div>
|
|
1454
|
-
),
|
|
1455
|
-
},
|
|
1456
|
-
{
|
|
1457
|
-
key: "metrics",
|
|
1458
|
-
title: "Performance",
|
|
1459
|
-
render: (row) => (
|
|
1460
|
-
<div className="flex gap-2">
|
|
1461
|
-
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
|
|
1462
|
-
{row.completed}%
|
|
1463
|
-
</span>
|
|
1464
|
-
</div>
|
|
1465
|
-
),
|
|
1466
|
-
},
|
|
1467
|
-
],
|
|
1468
|
-
};
|
|
1469
|
-
```
|
|
1470
|
-
|
|
1471
|
-
### 6. Validation Patterns
|
|
1472
|
-
|
|
1473
|
-
```js
|
|
1474
|
-
const formFields = [
|
|
1475
|
-
{
|
|
1476
|
-
key: "email",
|
|
1477
|
-
type: "email",
|
|
1478
|
-
customValidation: (value) => {
|
|
1479
|
-
// Async validation example
|
|
1480
|
-
const exists = checkEmailExists(value);
|
|
1481
|
-
if (exists) return "Email already in use";
|
|
1482
|
-
return true;
|
|
1483
|
-
},
|
|
1484
|
-
},
|
|
1485
|
-
{
|
|
1486
|
-
key: "password",
|
|
1487
|
-
type: "password",
|
|
1488
|
-
customValidation: (value) => {
|
|
1489
|
-
if (value.length < 8) return "Password must be at least 8 characters";
|
|
1490
|
-
if (!/[A-Z]/.test(value)) return "Must contain uppercase letter";
|
|
1491
|
-
if (!/[0-9]/.test(value)) return "Must contain a number";
|
|
1492
|
-
return true;
|
|
1493
|
-
},
|
|
1494
|
-
},
|
|
1495
|
-
];
|
|
1496
|
-
```
|
|
1497
|
-
|
|
1498
|
-
### 7. Handling File Uploads
|
|
1499
|
-
|
|
1500
|
-
```js
|
|
1501
|
-
const handleImageUpload = async (file) => {
|
|
1502
|
-
const formData = new FormData();
|
|
1503
|
-
formData.append("file", file);
|
|
1504
|
-
const resp = await api.post("/upload", formData);
|
|
1505
|
-
return resp.data.url;
|
|
1506
|
-
};
|
|
1507
|
-
|
|
1508
|
-
const config = {
|
|
1509
|
-
modalConfig: {
|
|
1510
|
-
addModal: {
|
|
1511
|
-
formFields: [
|
|
1512
|
-
{
|
|
1513
|
-
key: "image",
|
|
1514
|
-
type: "image",
|
|
1515
|
-
dragDrop: true,
|
|
1516
|
-
cropImage: true,
|
|
1517
|
-
aspectRatio: 1,
|
|
1518
|
-
},
|
|
1519
|
-
],
|
|
1520
|
-
handleSubmit: async (formData) => {
|
|
1521
|
-
// Image data includes base64 or file data
|
|
1522
|
-
const imageUrl = await handleImageUpload(formData.image);
|
|
1523
|
-
const resp = await api.post("/items", {
|
|
1524
|
-
...formData,
|
|
1525
|
-
image: imageUrl,
|
|
1526
|
-
});
|
|
1527
|
-
return { newObject: resp.data };
|
|
1528
|
-
},
|
|
1529
|
-
},
|
|
1530
|
-
},
|
|
1531
|
-
};
|
|
1532
|
-
```
|
|
1533
|
-
|
|
1534
|
-
### 8. Conditional Rendering with Row Data
|
|
1535
|
-
|
|
1536
|
-
```js
|
|
1537
|
-
const viewModal = {
|
|
1538
|
-
fields: [
|
|
1539
|
-
{
|
|
1540
|
-
key: "status",
|
|
1541
|
-
label: "Status",
|
|
1542
|
-
type: "chip",
|
|
1543
|
-
renderCondition: (data) => data.status !== null,
|
|
1544
|
-
chipOptions: [
|
|
1545
|
-
{ value: "active", label: "Active", color: "green" },
|
|
1546
|
-
{ value: "suspended", label: "Suspended", color: "red" },
|
|
1547
|
-
],
|
|
1548
|
-
},
|
|
1549
|
-
{
|
|
1550
|
-
key: "suspendReason",
|
|
1551
|
-
label: "Suspension Reason",
|
|
1552
|
-
renderCondition: (data) => data.status === "suspended", // Only show if suspended
|
|
1553
|
-
},
|
|
1554
|
-
],
|
|
1555
|
-
};
|
|
1556
|
-
```
|
|
1557
|
-
|
|
1558
|
-
### 9. Multiple Sort Options (Server-Side)
|
|
1559
|
-
|
|
1560
|
-
```js
|
|
1561
|
-
const tableConfig = {
|
|
1562
|
-
sort: {
|
|
1563
|
-
enabled: true,
|
|
1564
|
-
useServerSideSorting: true,
|
|
1565
|
-
options: [
|
|
1566
|
-
{
|
|
1567
|
-
value: "recent",
|
|
1568
|
-
label: "Most Recent",
|
|
1569
|
-
key: "createdAt",
|
|
1570
|
-
order: "desc",
|
|
1571
|
-
},
|
|
1572
|
-
{ value: "oldest", label: "Oldest", key: "createdAt", order: "asc" },
|
|
1573
|
-
{ value: "name-asc", label: "Name (A-Z)", key: "name", order: "asc" },
|
|
1574
|
-
{ value: "name-desc", label: "Name (Z-A)", key: "name", order: "desc" },
|
|
1575
|
-
{ value: "popular", label: "Most Popular", key: "views", order: "desc" },
|
|
1576
|
-
],
|
|
1577
|
-
onChange: (payload) => {
|
|
1578
|
-
console.log(`Sorted by: ${payload.option?.label}`);
|
|
1579
|
-
},
|
|
1580
|
-
},
|
|
1581
|
-
};
|
|
1582
|
-
```
|
|
857
|
+
### CSS Class Reference
|
|
858
|
+
|
|
859
|
+
All classes are prefixed with `crud_` and can be overridden in any stylesheet (CSS, SCSS, Tailwind `@apply`, or CSS-in-JS).
|
|
860
|
+
|
|
861
|
+
| Component | Class Name | Description |
|
|
862
|
+
| ----------------- | ------------------------------- | ------------------------ |
|
|
863
|
+
| **Page** | `crud_page` | Main page wrapper |
|
|
864
|
+
| | `crud_page_delete_content` | Delete confirmation area |
|
|
865
|
+
| **Button** | `crud_button` | Button element |
|
|
866
|
+
| **Chip** | `crud_chip` | Chip / badge element |
|
|
867
|
+
| **Spinner** | `crud_spinner` | Loading spinner |
|
|
868
|
+
| **Modal** | `crud_modal` | Modal root |
|
|
869
|
+
| | `crud_modal_overlay` | Backdrop overlay |
|
|
870
|
+
| | `crud_modal_container` | Modal box |
|
|
871
|
+
| | `crud_modal_header` | Modal header |
|
|
872
|
+
| | `crud_modal_title` | Modal title text |
|
|
873
|
+
| | `crud_modal_close_button` | Close (×) button |
|
|
874
|
+
| | `crud_modal_body` | Modal body |
|
|
875
|
+
| | `crud_modal_footer` | Modal footer |
|
|
876
|
+
| | `crud_modal_action_button` | Footer action buttons |
|
|
877
|
+
| | `crud_modal_loading_indicator` | Loading state overlay |
|
|
878
|
+
| **Table** | `crud_table` | Table wrapper |
|
|
879
|
+
| | `crud_table_toolbar` | Toolbar (search, filter) |
|
|
880
|
+
| | `crud_table_search_field` | Search field wrapper |
|
|
881
|
+
| | `crud_table_search_input` | Search input |
|
|
882
|
+
| | `crud_table_container` | Table scroll container |
|
|
883
|
+
| | `crud_table_element` | `<table>` element |
|
|
884
|
+
| | `crud_table_head` | `<thead>` |
|
|
885
|
+
| | `crud_table_head_row` | Header `<tr>` |
|
|
886
|
+
| | `crud_table_head_cell` | Header `<th>` |
|
|
887
|
+
| | `crud_table_body` | `<tbody>` |
|
|
888
|
+
| | `crud_table_row` | Body `<tr>` |
|
|
889
|
+
| | `crud_table_cell` | Body `<td>` |
|
|
890
|
+
| | `crud_table_no_data` | Empty state |
|
|
891
|
+
| | `crud_table_action_button` | Row action button |
|
|
892
|
+
| | `crud_table_menu` | Action menu |
|
|
893
|
+
| | `crud_table_menu_item` | Menu item |
|
|
894
|
+
| | `crud_table_pagination` | Pagination controls |
|
|
895
|
+
| **Skeleton** | `crud_table_skeleton` | Skeleton wrapper |
|
|
896
|
+
| | `crud_table_skeleton_table` | Skeleton table |
|
|
897
|
+
| **Sort** | `crud_sort_dropdown` | Sort dropdown root |
|
|
898
|
+
| | `crud_sort_dropdown_trigger` | Dropdown trigger button |
|
|
899
|
+
| | `crud_sort_dropdown_menu` | Dropdown menu |
|
|
900
|
+
| | `crud_sort_dropdown_item` | Dropdown item |
|
|
901
|
+
| **Image Preview** | `crud_image_preview` | Preview root |
|
|
902
|
+
| | `crud_image_preview_container` | Preview container |
|
|
903
|
+
| | `crud_image_preview_image` | Preview `<img>` |
|
|
904
|
+
| **Filter Drawer** | `crud_filter_overlay` | Drawer backdrop |
|
|
905
|
+
| | `crud_filter_panel` | Drawer panel |
|
|
906
|
+
| | `crud_filter_header` | Drawer header |
|
|
907
|
+
| | `crud_filter_body` | Drawer body |
|
|
908
|
+
| | `crud_filter_footer` | Drawer footer |
|
|
909
|
+
| **Form** | `crud_form` | Form wrapper |
|
|
910
|
+
| | `crud_form_loading` | Form loading overlay |
|
|
911
|
+
| **Field** | `crud_field_wrapper` | Field wrapper |
|
|
912
|
+
| | `crud_field_label` | Field label |
|
|
913
|
+
| | `crud_field_input` | Input element |
|
|
914
|
+
| | `crud_field_error` | Error message |
|
|
915
|
+
| **Media Picker** | `crud_media_image_picker` | Single image picker |
|
|
916
|
+
| | `crud_media_multi_image_picker` | Multi-image picker |
|
|
917
|
+
| | `crud_media_audio_picker` | Audio file picker |
|
|
918
|
+
| | `crud_media_video_picker` | Video file picker |
|
|
919
|
+
| | `crud_media_dropzone` | Drag-and-drop zone |
|
|
920
|
+
| | `crud_media_crop_modal` | Image crop modal |
|
|
921
|
+
| **Details** | `crud_details` | Details root |
|
|
922
|
+
| | `crud_details_container` | Details container |
|
|
923
|
+
| | `crud_details_row` | Details row |
|
|
1583
924
|
|
|
1584
925
|
---
|
|
1585
926
|
|