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