proje-react-panel 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules.md +11 -1
- package/AUTH_LAYOUT_EXAMPLE.md +343 -0
- package/AUTH_LAYOUT_GUIDE.md +819 -0
- package/COLOR_SYSTEM_GUIDE.md +296 -0
- package/DASHBOARD_GUIDE.md +531 -0
- package/IMPLEMENTATION_GUIDE.md +899 -0
- package/README.md +18 -1
- package/dist/api/ApiConfig.d.ts +11 -0
- package/dist/api/AuthApi.d.ts +2 -5
- package/dist/api/CrudApi.d.ts +11 -12
- package/dist/components/DashboardContainer.d.ts +7 -0
- package/dist/components/DashboardGrid.d.ts +9 -0
- package/dist/components/DashboardItem.d.ts +10 -0
- package/dist/components/ThemeSwitcher.d.ts +7 -0
- package/dist/components/dashboard/Dashboard.d.ts +7 -0
- package/dist/components/dashboard/DashboardGrid.d.ts +7 -0
- package/dist/components/dashboard/DashboardItem.d.ts +6 -0
- package/dist/components/dashboard/index.d.ts +3 -0
- package/dist/decorators/auth/DefaultLoginForm.d.ts +4 -0
- package/dist/index.cjs.js +15 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +15 -1
- package/dist/store/themeStore.d.ts +23 -0
- package/dist/types/Login.d.ts +8 -0
- package/package.json +3 -1
- package/src/api/ApiConfig.ts +63 -0
- package/src/api/AuthApi.ts +8 -0
- package/src/api/CrudApi.ts +96 -60
- package/src/components/dashboard/Dashboard.tsx +11 -0
- package/src/components/dashboard/DashboardGrid.tsx +14 -0
- package/src/components/dashboard/DashboardItem.tsx +9 -0
- package/src/components/dashboard/index.ts +3 -0
- package/src/decorators/auth/DefaultLoginForm.ts +32 -0
- package/src/index.ts +26 -0
- package/src/styles/base/_variables.scss +45 -0
- package/src/styles/components/button.scss +3 -3
- package/src/styles/components/checkbox.scss +6 -6
- package/src/styles/components/form-header.scss +21 -19
- package/src/styles/components/uploader.scss +15 -37
- package/src/styles/counter.scss +25 -33
- package/src/styles/dashboard.scss +9 -0
- package/src/styles/details.scss +6 -15
- package/src/styles/error-boundary.scss +75 -74
- package/src/styles/filter-popup.scss +29 -27
- package/src/styles/form.scss +16 -15
- package/src/styles/index.scss +8 -4
- package/src/styles/layout.scss +9 -8
- package/src/styles/list.scss +29 -27
- package/src/styles/loading-screen.scss +4 -4
- package/src/styles/login.scss +3 -3
- package/src/styles/pagination.scss +13 -13
- package/src/styles/sidebar.scss +24 -22
- package/src/styles/utils/scrollbar.scss +4 -3
- package/src/types/Login.ts +9 -0
- package/src/utils/logout.ts +2 -0
- package/dist/components/components/Checkbox.d.ts +0 -7
- package/dist/components/components/Counter.d.ts +0 -9
- package/dist/components/components/ErrorBoundary.d.ts +0 -16
- package/dist/components/components/ErrorComponent.d.ts +0 -4
- package/dist/components/components/FormField.d.ts +0 -17
- package/dist/components/components/ImageUploader.d.ts +0 -15
- package/dist/components/components/InnerForm.d.ts +0 -17
- package/dist/components/components/Label.d.ts +0 -9
- package/dist/components/components/LoadingScreen.d.ts +0 -2
- package/dist/components/components/Uploader.d.ts +0 -8
- package/dist/components/components/index.d.ts +0 -8
- package/dist/components/components/list/Datagrid.d.ts +0 -9
- package/dist/components/components/list/EmptyList.d.ts +0 -2
- package/dist/components/components/list/FilterPopup.d.ts +0 -11
- package/dist/components/components/list/ListPage.d.ts +0 -20
- package/dist/components/components/list/Pagination.d.ts +0 -11
- package/dist/components/components/list/index.d.ts +0 -0
- package/dist/components/pages/ControllerDetails.d.ts +0 -5
- package/dist/components/pages/FormPage.d.ts +0 -18
- package/dist/components/pages/ListPage.d.ts +0 -18
- package/dist/components/pages/Login.d.ts +0 -13
- package/dist/decorators/Crud.d.ts +0 -6
- package/dist/decorators/form/FormOptions.d.ts +0 -7
- package/dist/decorators/form/getFormFields.d.ts +0 -3
- package/dist/decorators/list/GetCellFields.d.ts +0 -2
- package/dist/decorators/list/ImageCell.d.ts +0 -6
- package/dist/decorators/list/ListData.d.ts +0 -6
- package/dist/decorators/list/getListFields.d.ts +0 -2
- package/dist/initPanel.d.ts +0 -2
- package/dist/types/Screen.d.ts +0 -4
- package/dist/types/ScreenCreatorData.d.ts +0 -13
- package/dist/types/getDetailsData.d.ts +0 -1
- package/dist/types/initPanelOptions.d.ts +0 -2
- package/dist/utils/createScreens.d.ts +0 -1
- package/dist/utils/getFields.d.ts +0 -3
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
# Proje React Panel - Implementation Guide
|
|
2
|
+
|
|
3
|
+
A comprehensive guide for implementing the Proje React Panel library in your React applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Installation & Setup](#installation--setup)
|
|
8
|
+
2. [Basic Configuration](#basic-configuration)
|
|
9
|
+
3. [Authentication Setup](#authentication-setup)
|
|
10
|
+
4. [API Configuration](#api-configuration)
|
|
11
|
+
5. [Creating Models with Decorators](#creating-models-with-decorators)
|
|
12
|
+
6. [Form Implementation](#form-implementation)
|
|
13
|
+
7. [List Implementation](#list-implementation)
|
|
14
|
+
8. [Details Page Implementation](#details-page-implementation)
|
|
15
|
+
9. [Routing & Layout](#routing--layout)
|
|
16
|
+
10. [Advanced Features](#advanced-features)
|
|
17
|
+
11. [Complete Example](#complete-example)
|
|
18
|
+
|
|
19
|
+
## Installation & Setup
|
|
20
|
+
|
|
21
|
+
### Prerequisites
|
|
22
|
+
|
|
23
|
+
- React 19.0.0 or higher
|
|
24
|
+
- TypeScript
|
|
25
|
+
- React Router 7.3.0
|
|
26
|
+
- React Hook Form 7.54.2 or higher
|
|
27
|
+
- Zustand 5.0.3 or higher
|
|
28
|
+
|
|
29
|
+
### Install Dependencies
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install proje-react-panel react react-dom react-router react-hook-form zustand class-validator class-transformer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Required Peer Dependencies
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"react": ">=19.0.0",
|
|
40
|
+
"react-hook-form": ">=7.54.2",
|
|
41
|
+
"react-router": "7.3.0",
|
|
42
|
+
"react-select": "^5.10.1",
|
|
43
|
+
"use-sync-external-store": ">=1.4.0",
|
|
44
|
+
"zustand": ">=5.0.3"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Basic Configuration
|
|
49
|
+
|
|
50
|
+
### 1. Initialize Your App
|
|
51
|
+
|
|
52
|
+
Create your main App component and wrap it with the `Panel` component:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// App.tsx
|
|
56
|
+
import React from 'react';
|
|
57
|
+
import { Panel, Login, ListPage, FormPage, DetailsPage } from 'proje-react-panel';
|
|
58
|
+
import { BrowserRouter as Router, Route, Routes } from 'react-router';
|
|
59
|
+
import { initApi, initAuthToken, setAuthToken } from './api/apiConfig';
|
|
60
|
+
|
|
61
|
+
// Initialize API
|
|
62
|
+
initApi({
|
|
63
|
+
baseUrl: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080',
|
|
64
|
+
});
|
|
65
|
+
initAuthToken();
|
|
66
|
+
|
|
67
|
+
export function App() {
|
|
68
|
+
return (
|
|
69
|
+
<Panel
|
|
70
|
+
onInit={appData => {
|
|
71
|
+
if (appData.token) {
|
|
72
|
+
setAuthToken(appData.token);
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<Router>
|
|
77
|
+
<Routes>{/* Your routes here */}</Routes>
|
|
78
|
+
</Router>
|
|
79
|
+
</Panel>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 2. Entry Point
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
// index.tsx
|
|
88
|
+
import React from 'react';
|
|
89
|
+
import ReactDOM from 'react-dom/client';
|
|
90
|
+
import { App } from './App';
|
|
91
|
+
import './index.scss';
|
|
92
|
+
|
|
93
|
+
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
|
94
|
+
root.render(<App />);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Authentication Setup
|
|
98
|
+
|
|
99
|
+
### 1. API Configuration
|
|
100
|
+
|
|
101
|
+
Create an API configuration file to handle authentication and HTTP requests:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// api/apiConfig.ts
|
|
105
|
+
import axios, { AxiosInstance } from 'axios';
|
|
106
|
+
|
|
107
|
+
let axiosInstance: AxiosInstance;
|
|
108
|
+
|
|
109
|
+
export function initApi(config: { baseUrl: string }) {
|
|
110
|
+
axiosInstance = axios.create({
|
|
111
|
+
baseURL: config.baseUrl,
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Handle 401 errors globally
|
|
118
|
+
axiosInstance.interceptors.response.use(
|
|
119
|
+
response => response,
|
|
120
|
+
error => {
|
|
121
|
+
if (error.response && error.response.status === 401) {
|
|
122
|
+
setAuthLogout();
|
|
123
|
+
window.location.href = '/login';
|
|
124
|
+
}
|
|
125
|
+
return Promise.reject(error);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function initAuthToken(): void {
|
|
131
|
+
if (!axiosInstance) {
|
|
132
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
133
|
+
}
|
|
134
|
+
const token = localStorage.getItem('token');
|
|
135
|
+
if (token) {
|
|
136
|
+
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function setAuthToken(token: string): void {
|
|
141
|
+
if (!axiosInstance) {
|
|
142
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
143
|
+
}
|
|
144
|
+
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
145
|
+
localStorage.setItem('token', token);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function setAuthLogout(): void {
|
|
149
|
+
if (!axiosInstance) {
|
|
150
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
151
|
+
}
|
|
152
|
+
axiosInstance.defaults.headers.common['Authorization'] = null;
|
|
153
|
+
localStorage.removeItem('token');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getAxiosInstance(): AxiosInstance {
|
|
157
|
+
if (!axiosInstance) {
|
|
158
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
159
|
+
}
|
|
160
|
+
return axiosInstance;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. Login Form Implementation
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// types/Login.ts
|
|
168
|
+
import { MinLength } from 'class-validator';
|
|
169
|
+
import { Form, Input, login } from 'proje-react-panel';
|
|
170
|
+
import { dataFetchers } from '../api/dataFetchers';
|
|
171
|
+
import { setAuthToken } from '../api/apiConfig';
|
|
172
|
+
|
|
173
|
+
export interface LoginResponse {
|
|
174
|
+
access_token: string;
|
|
175
|
+
admin: AdminDetails; // Your user type
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@Form<LoginForm, LoginResponse>({
|
|
179
|
+
onSubmit: dataFetchers.auth.login,
|
|
180
|
+
onSubmitSuccess: (data: LoginResponse) => {
|
|
181
|
+
setAuthToken(data.access_token);
|
|
182
|
+
login(data.admin, data.access_token, () => {
|
|
183
|
+
window.location.href = '/';
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
type: 'formData',
|
|
187
|
+
})
|
|
188
|
+
export class LoginForm {
|
|
189
|
+
@MinLength(3)
|
|
190
|
+
@Input({
|
|
191
|
+
label: 'Username',
|
|
192
|
+
})
|
|
193
|
+
username: string;
|
|
194
|
+
|
|
195
|
+
@Input({
|
|
196
|
+
label: 'Password',
|
|
197
|
+
inputType: 'password',
|
|
198
|
+
})
|
|
199
|
+
password: string;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## API Configuration
|
|
204
|
+
|
|
205
|
+
### 1. CRUD Operations
|
|
206
|
+
|
|
207
|
+
Create a CRUD utility for API operations:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// api/crud.ts
|
|
211
|
+
import { getAxiosInstance } from './apiConfig';
|
|
212
|
+
|
|
213
|
+
export function create<T>(endpoint: string) {
|
|
214
|
+
return async (data: T) => {
|
|
215
|
+
const response = await getAxiosInstance().post(`/${endpoint}`, data);
|
|
216
|
+
return response.data;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function getAll<T>(endpoint: string) {
|
|
221
|
+
return async (params: any = {}) => {
|
|
222
|
+
const response = await getAxiosInstance().get(`/${endpoint}`, { params });
|
|
223
|
+
return response.data;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function getOne<T>(endpoint: string) {
|
|
228
|
+
return async (id: string) => {
|
|
229
|
+
const response = await getAxiosInstance().get(`/${endpoint}/${id}`);
|
|
230
|
+
return response.data;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function update<T>(endpoint: string) {
|
|
235
|
+
return async (data: T) => {
|
|
236
|
+
const response = await getAxiosInstance().put(`/${endpoint}`, data);
|
|
237
|
+
return response.data;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function remove(endpoint: string, idField: string) {
|
|
242
|
+
return async (item: any) => {
|
|
243
|
+
const response = await getAxiosInstance().delete(`/${endpoint}/${item[idField]}`);
|
|
244
|
+
return response.data;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 2. Data Fetchers
|
|
250
|
+
|
|
251
|
+
Create a centralized data fetchers object:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// api/dataFetchers.ts
|
|
255
|
+
import { create, getAll, getOne, update, remove } from './crud';
|
|
256
|
+
import { AdminList, CreateAdminForm, EditAdminForm, AdminDetails } from '../types/Admin';
|
|
257
|
+
|
|
258
|
+
export const dataFetchers = Object.freeze({
|
|
259
|
+
admins: {
|
|
260
|
+
getAll: getAll<AdminList>('admins'),
|
|
261
|
+
details: getOne<AdminDetails>('admins'),
|
|
262
|
+
create: create<CreateAdminForm>('admins'),
|
|
263
|
+
update: update<EditAdminForm>('admins'),
|
|
264
|
+
updateDetails: getOne<EditAdminForm>('admins'),
|
|
265
|
+
remove: remove('admins', 'id'),
|
|
266
|
+
},
|
|
267
|
+
auth: {
|
|
268
|
+
login: async (data: any) => {
|
|
269
|
+
const response = await getAxiosInstance().post('/auth/login', data);
|
|
270
|
+
return response.data;
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Creating Models with Decorators
|
|
277
|
+
|
|
278
|
+
The library uses decorators to define models for forms, lists, and details pages.
|
|
279
|
+
|
|
280
|
+
### 1. List Model
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// types/Admin.ts
|
|
284
|
+
import {
|
|
285
|
+
Cell,
|
|
286
|
+
List,
|
|
287
|
+
Input,
|
|
288
|
+
DetailsItem,
|
|
289
|
+
Details,
|
|
290
|
+
Form,
|
|
291
|
+
SelectInput,
|
|
292
|
+
LinkCell,
|
|
293
|
+
} from 'proje-react-panel';
|
|
294
|
+
import { dataFetchers } from '../api/dataFetchers';
|
|
295
|
+
|
|
296
|
+
@List({
|
|
297
|
+
headers: {
|
|
298
|
+
create: { path: 'create', label: 'Create' },
|
|
299
|
+
},
|
|
300
|
+
actions: (item: AdminList) => ({
|
|
301
|
+
customActions: [
|
|
302
|
+
{
|
|
303
|
+
label: 'Custom Action',
|
|
304
|
+
onClick: () => {
|
|
305
|
+
alert('Custom action clicked');
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
details: { path: '' + item.id, label: 'Details' },
|
|
310
|
+
edit: { path: 'edit/' + item.id, label: 'Edit' },
|
|
311
|
+
delete: { label: 'Delete', onRemoveItem: dataFetchers.admins.remove },
|
|
312
|
+
}),
|
|
313
|
+
getData: dataFetchers.admins.getAll,
|
|
314
|
+
primaryId: 'id',
|
|
315
|
+
})
|
|
316
|
+
export class AdminList {
|
|
317
|
+
@Cell({
|
|
318
|
+
title: 'ID',
|
|
319
|
+
type: 'uuid',
|
|
320
|
+
})
|
|
321
|
+
id: string;
|
|
322
|
+
|
|
323
|
+
@Cell({
|
|
324
|
+
title: 'Username',
|
|
325
|
+
})
|
|
326
|
+
username: string;
|
|
327
|
+
|
|
328
|
+
@Cell({
|
|
329
|
+
title: 'Email',
|
|
330
|
+
})
|
|
331
|
+
email: string;
|
|
332
|
+
|
|
333
|
+
@Cell({
|
|
334
|
+
title: 'Created At',
|
|
335
|
+
type: 'date',
|
|
336
|
+
})
|
|
337
|
+
createdAt: string;
|
|
338
|
+
|
|
339
|
+
@LinkCell({
|
|
340
|
+
path: '/',
|
|
341
|
+
placeHolder: 'Custom Link',
|
|
342
|
+
})
|
|
343
|
+
details: string;
|
|
344
|
+
|
|
345
|
+
@Cell({
|
|
346
|
+
title: 'Updated At',
|
|
347
|
+
type: 'date',
|
|
348
|
+
})
|
|
349
|
+
updatedAt: string;
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 2. Form Model
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// Form base class
|
|
357
|
+
class AdminForm {
|
|
358
|
+
@MinLength(3)
|
|
359
|
+
@Input({
|
|
360
|
+
label: 'Username',
|
|
361
|
+
})
|
|
362
|
+
username: string;
|
|
363
|
+
|
|
364
|
+
@IsEmail()
|
|
365
|
+
@Input({
|
|
366
|
+
label: 'Email',
|
|
367
|
+
inputType: 'email',
|
|
368
|
+
})
|
|
369
|
+
email: string;
|
|
370
|
+
|
|
371
|
+
@ValidateIf(o => !o.__formEdit)
|
|
372
|
+
@IsString()
|
|
373
|
+
@MinLength(6)
|
|
374
|
+
@Input({
|
|
375
|
+
label: 'Password',
|
|
376
|
+
inputType: 'password',
|
|
377
|
+
})
|
|
378
|
+
password: string;
|
|
379
|
+
|
|
380
|
+
@IsEnum(['super-admin', 'admin'])
|
|
381
|
+
@SelectInput({
|
|
382
|
+
label: 'Role',
|
|
383
|
+
defaultOptions: [
|
|
384
|
+
{ value: 'super-admin', label: 'Super Admin' },
|
|
385
|
+
{ value: 'admin', label: 'Admin' },
|
|
386
|
+
],
|
|
387
|
+
})
|
|
388
|
+
role: string;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Create form
|
|
392
|
+
@Form({
|
|
393
|
+
onSubmit: dataFetchers.admins.create,
|
|
394
|
+
type: 'formData',
|
|
395
|
+
redirectSuccessUrl: '/admins',
|
|
396
|
+
})
|
|
397
|
+
export class CreateAdminForm extends AdminForm {}
|
|
398
|
+
|
|
399
|
+
// Edit form
|
|
400
|
+
@Form({
|
|
401
|
+
onSubmit: dataFetchers.admins.update,
|
|
402
|
+
getDetailsData: dataFetchers.admins.updateDetails,
|
|
403
|
+
redirectSuccessUrl: '/admins',
|
|
404
|
+
})
|
|
405
|
+
export class EditAdminForm extends AdminForm {}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### 3. Details Model
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
@Details({
|
|
412
|
+
getDetailsData: dataFetchers.admins.details,
|
|
413
|
+
primaryId: 'username',
|
|
414
|
+
})
|
|
415
|
+
export class AdminDetails {
|
|
416
|
+
@DetailsItem()
|
|
417
|
+
id: string;
|
|
418
|
+
|
|
419
|
+
@DetailsItem()
|
|
420
|
+
username: string;
|
|
421
|
+
|
|
422
|
+
@DetailsItem()
|
|
423
|
+
email: string;
|
|
424
|
+
|
|
425
|
+
@DetailsItem()
|
|
426
|
+
role: string;
|
|
427
|
+
|
|
428
|
+
@DetailsItem()
|
|
429
|
+
createdAt: string;
|
|
430
|
+
|
|
431
|
+
@DetailsItem()
|
|
432
|
+
updatedAt: string;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Form Implementation
|
|
437
|
+
|
|
438
|
+
### 1. Basic Form
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
// In your routing
|
|
442
|
+
<Route path="create" element={<FormPage key="admin-create" model={CreateAdminForm} />} />
|
|
443
|
+
<Route path="edit/:id" element={<FormPage key="admin-edit" model={EditAdminForm} />} />
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 2. Form with Custom Validation
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { IsEmail, IsString, MinLength, ValidateIf } from 'class-validator';
|
|
450
|
+
|
|
451
|
+
class UserForm {
|
|
452
|
+
@IsString()
|
|
453
|
+
@MinLength(3)
|
|
454
|
+
@Input({ label: 'Username' })
|
|
455
|
+
username: string;
|
|
456
|
+
|
|
457
|
+
@IsEmail()
|
|
458
|
+
@Input({ label: 'Email', inputType: 'email' })
|
|
459
|
+
email: string;
|
|
460
|
+
|
|
461
|
+
@ValidateIf(o => !o.__formEdit)
|
|
462
|
+
@IsString()
|
|
463
|
+
@MinLength(6)
|
|
464
|
+
@Input({ label: 'Password', inputType: 'password' })
|
|
465
|
+
password: string;
|
|
466
|
+
|
|
467
|
+
@IsBoolean()
|
|
468
|
+
@Input({ label: 'Is Active', type: 'checkbox' })
|
|
469
|
+
isActive: boolean;
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 3. Form with Select Input
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
@SelectInput({
|
|
477
|
+
label: "Role",
|
|
478
|
+
defaultOptions: [
|
|
479
|
+
{ value: "admin", label: "Admin" },
|
|
480
|
+
{ value: "user", label: "User" },
|
|
481
|
+
],
|
|
482
|
+
})
|
|
483
|
+
role: string;
|
|
484
|
+
|
|
485
|
+
// Dynamic options
|
|
486
|
+
@SelectInput({
|
|
487
|
+
label: "Asset",
|
|
488
|
+
defaultOptions: [],
|
|
489
|
+
onSelectPreloader: async () => {
|
|
490
|
+
const response = await dataFetchers.assets.getAll({});
|
|
491
|
+
return response.data.map((asset) => ({
|
|
492
|
+
value: asset.id,
|
|
493
|
+
label: asset.filename,
|
|
494
|
+
}));
|
|
495
|
+
},
|
|
496
|
+
})
|
|
497
|
+
assetId: number;
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## List Implementation
|
|
501
|
+
|
|
502
|
+
### 1. Basic List
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
// In your routing
|
|
506
|
+
<Route path="" element={<ListPage key="admin-list" model={AdminList} />} />
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 2. List with Custom Header
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
<Route
|
|
513
|
+
path=""
|
|
514
|
+
element={<ListPage key="admin-list" customHeader={<AdminListHeader />} model={AdminList} />}
|
|
515
|
+
/>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 3. List with Custom Actions
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
@List({
|
|
522
|
+
actions: (item: AdminList) => ({
|
|
523
|
+
customActions: [
|
|
524
|
+
{
|
|
525
|
+
label: "Custom Action",
|
|
526
|
+
onClick: () => {
|
|
527
|
+
// Custom action logic
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
details: { path: "" + item.id, label: "Details" },
|
|
532
|
+
edit: { path: "edit/" + item.id, label: "Edit" },
|
|
533
|
+
delete: { label: "Delete", onRemoveItem: dataFetchers.admins.remove },
|
|
534
|
+
}),
|
|
535
|
+
// ... other options
|
|
536
|
+
})
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 4. Cell Types
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// Different cell types
|
|
543
|
+
@Cell({ title: "ID", type: "uuid" })
|
|
544
|
+
id: string;
|
|
545
|
+
|
|
546
|
+
@Cell({ title: "Created At", type: "date" })
|
|
547
|
+
createdAt: string;
|
|
548
|
+
|
|
549
|
+
@Cell({ title: "Is Active", type: "boolean" })
|
|
550
|
+
isActive: boolean;
|
|
551
|
+
|
|
552
|
+
// Link cells
|
|
553
|
+
@LinkCell({
|
|
554
|
+
path: "/details",
|
|
555
|
+
placeHolder: "View Details",
|
|
556
|
+
})
|
|
557
|
+
details: string;
|
|
558
|
+
|
|
559
|
+
@LinkCell({
|
|
560
|
+
path: "/",
|
|
561
|
+
placeHolder: "Click Me",
|
|
562
|
+
onClick: (data: AdminList) => {
|
|
563
|
+
alert(data.username);
|
|
564
|
+
},
|
|
565
|
+
})
|
|
566
|
+
customLink: string;
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Details Page Implementation
|
|
570
|
+
|
|
571
|
+
### 1. Basic Details Page
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
<Route path=":id" element={<DetailsPage key="admin-details" model={AdminDetails} />} />
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### 2. Details Page with Custom Header
|
|
578
|
+
|
|
579
|
+
```tsx
|
|
580
|
+
<Route
|
|
581
|
+
path=":id"
|
|
582
|
+
element={
|
|
583
|
+
<DetailsPage CustomHeader={AdminDetailsHeader} key="admin-details" model={AdminDetails} />
|
|
584
|
+
}
|
|
585
|
+
/>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Routing & Layout
|
|
589
|
+
|
|
590
|
+
### 1. Layout Component
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
// AuthLayout.tsx
|
|
594
|
+
import { Outlet } from 'react-router';
|
|
595
|
+
import React from 'react';
|
|
596
|
+
import { Layout, logout } from 'proje-react-panel';
|
|
597
|
+
import { setAuthLogout } from './api/apiConfig';
|
|
598
|
+
|
|
599
|
+
export function AuthLayout() {
|
|
600
|
+
return (
|
|
601
|
+
<Layout
|
|
602
|
+
logout={() => {
|
|
603
|
+
setAuthLogout();
|
|
604
|
+
logout(() => {
|
|
605
|
+
window.location.href = '/login';
|
|
606
|
+
});
|
|
607
|
+
}}
|
|
608
|
+
getIcons={getIcons}
|
|
609
|
+
menu={getMenu}
|
|
610
|
+
>
|
|
611
|
+
<Outlet />
|
|
612
|
+
</Layout>
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function getMenu() {
|
|
617
|
+
return [
|
|
618
|
+
{ name: 'Dashboard', path: '/', iconType: 'dashboard' },
|
|
619
|
+
{ name: 'Admins', path: '/admins', iconType: 'admin' },
|
|
620
|
+
{ name: 'Users', path: 'users', iconType: 'user' },
|
|
621
|
+
// ... more menu items
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### 2. Complete Routing Structure
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
// App.tsx
|
|
630
|
+
export function App() {
|
|
631
|
+
return (
|
|
632
|
+
<Panel
|
|
633
|
+
onInit={appData => {
|
|
634
|
+
if (appData.token) {
|
|
635
|
+
setAuthToken(appData.token);
|
|
636
|
+
}
|
|
637
|
+
}}
|
|
638
|
+
>
|
|
639
|
+
<Router>
|
|
640
|
+
<Routes>
|
|
641
|
+
<Route path="/" element={<AuthLayout />}>
|
|
642
|
+
<Route path={'/'} index element={<Dashboard />} />
|
|
643
|
+
|
|
644
|
+
<Route path={'admins'}>
|
|
645
|
+
<Route path={''} element={<ListPage key="admin-list" model={AdminList} />} />
|
|
646
|
+
<Route
|
|
647
|
+
path={'create'}
|
|
648
|
+
element={<FormPage key="admin-create" model={CreateAdminForm} />}
|
|
649
|
+
/>
|
|
650
|
+
<Route
|
|
651
|
+
path={'edit/:id'}
|
|
652
|
+
element={<FormPage key="admin-edit" model={EditAdminForm} />}
|
|
653
|
+
/>
|
|
654
|
+
<Route
|
|
655
|
+
path={':id'}
|
|
656
|
+
element={<DetailsPage key="admin-details" model={AdminDetails} />}
|
|
657
|
+
/>
|
|
658
|
+
</Route>
|
|
659
|
+
|
|
660
|
+
<Route path={'users'}>
|
|
661
|
+
<Route path={''} element={<ListPage key="user-list" model={UserList} />} />
|
|
662
|
+
<Route
|
|
663
|
+
path={'create'}
|
|
664
|
+
element={<FormPage key="user-create" model={CreateUserForm} />}
|
|
665
|
+
/>
|
|
666
|
+
<Route
|
|
667
|
+
path={'edit/:id'}
|
|
668
|
+
element={<FormPage key="user-edit" model={EditUserForm} />}
|
|
669
|
+
/>
|
|
670
|
+
<Route
|
|
671
|
+
path={':id'}
|
|
672
|
+
element={<DetailsPage key="user-details" model={UserDetails} />}
|
|
673
|
+
/>
|
|
674
|
+
</Route>
|
|
675
|
+
</Route>
|
|
676
|
+
|
|
677
|
+
<Route path="/login" element={<Login key="login" model={LoginForm} />} />
|
|
678
|
+
</Routes>
|
|
679
|
+
</Router>
|
|
680
|
+
</Panel>
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Advanced Features
|
|
686
|
+
|
|
687
|
+
### 1. Custom Components
|
|
688
|
+
|
|
689
|
+
```tsx
|
|
690
|
+
// Custom header component
|
|
691
|
+
export function AdminListHeader() {
|
|
692
|
+
return (
|
|
693
|
+
<div>
|
|
694
|
+
<h2>Admin Management</h2>
|
|
695
|
+
<button
|
|
696
|
+
onClick={() => {
|
|
697
|
+
/* custom action */
|
|
698
|
+
}}
|
|
699
|
+
>
|
|
700
|
+
Custom Action
|
|
701
|
+
</button>
|
|
702
|
+
</div>
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Use in routing
|
|
707
|
+
<Route
|
|
708
|
+
path=""
|
|
709
|
+
element={<ListPage key="admin-list" customHeader={<AdminListHeader />} model={AdminList} />}
|
|
710
|
+
/>;
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### 2. Dashboard Implementation
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
// Dashboard.tsx
|
|
717
|
+
import React from 'react';
|
|
718
|
+
import { Counter } from 'proje-react-panel';
|
|
719
|
+
|
|
720
|
+
export function Dashboard() {
|
|
721
|
+
return (
|
|
722
|
+
<div className="dashboard">
|
|
723
|
+
<Counter targetNumber={100} duration={2000} image={''} text={'Products'} />
|
|
724
|
+
<Counter targetNumber={50} duration={2000} image={''} text={'Users'} />
|
|
725
|
+
<Counter targetNumber={25} duration={2000} image={''} text={'Admins'} />
|
|
726
|
+
</div>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### 3. File Upload Forms
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
// For file uploads, use type: "formData"
|
|
735
|
+
@Form({
|
|
736
|
+
onSubmit: dataFetchers.assets.create,
|
|
737
|
+
type: 'formData', // Important for file uploads
|
|
738
|
+
redirectSuccessUrl: '/assets',
|
|
739
|
+
})
|
|
740
|
+
export class CreateAssetForm {
|
|
741
|
+
@Input({ label: 'File', type: 'file' })
|
|
742
|
+
file: File;
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### 4. Conditional Fields
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
class UserForm {
|
|
750
|
+
@ValidateIf(o => !o.__formEdit)
|
|
751
|
+
@IsString()
|
|
752
|
+
@MinLength(6)
|
|
753
|
+
@Input({ label: 'Password', inputType: 'password' })
|
|
754
|
+
password: string;
|
|
755
|
+
|
|
756
|
+
@ValidateIf(o => o.role === 'admin')
|
|
757
|
+
@Input({ label: 'Admin Code' })
|
|
758
|
+
adminCode: string;
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
## Complete Example
|
|
763
|
+
|
|
764
|
+
Here's a complete example of a simple CRUD implementation:
|
|
765
|
+
|
|
766
|
+
### 1. Model Definition
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
// types/User.ts
|
|
770
|
+
import { IsEmail, IsString, MinLength, IsBoolean } from 'class-validator';
|
|
771
|
+
import { Cell, List, Input, Form, Details, DetailsItem } from 'proje-react-panel';
|
|
772
|
+
import { dataFetchers } from '../api/dataFetchers';
|
|
773
|
+
|
|
774
|
+
@List({
|
|
775
|
+
headers: {
|
|
776
|
+
create: { path: 'create', label: 'Create' },
|
|
777
|
+
},
|
|
778
|
+
actions: (item: UserList) => ({
|
|
779
|
+
details: { path: '' + item.id, label: 'Details' },
|
|
780
|
+
edit: { path: 'edit/' + item.id, label: 'Edit' },
|
|
781
|
+
delete: { label: 'Delete', onRemoveItem: dataFetchers.users.remove },
|
|
782
|
+
}),
|
|
783
|
+
getData: dataFetchers.users.getAll,
|
|
784
|
+
primaryId: 'id',
|
|
785
|
+
})
|
|
786
|
+
export class UserList {
|
|
787
|
+
@Cell({ title: 'ID', type: 'uuid' })
|
|
788
|
+
id: string;
|
|
789
|
+
|
|
790
|
+
@Cell({ title: 'Username' })
|
|
791
|
+
username: string;
|
|
792
|
+
|
|
793
|
+
@Cell({ title: 'Email' })
|
|
794
|
+
email: string;
|
|
795
|
+
|
|
796
|
+
@Cell({ title: 'Is Active', type: 'boolean' })
|
|
797
|
+
isActive: boolean;
|
|
798
|
+
|
|
799
|
+
@Cell({ title: 'Created At', type: 'date' })
|
|
800
|
+
createdAt: Date;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
class UserForm {
|
|
804
|
+
@IsString()
|
|
805
|
+
@MinLength(3)
|
|
806
|
+
@Input({ label: 'Username' })
|
|
807
|
+
username: string;
|
|
808
|
+
|
|
809
|
+
@IsEmail()
|
|
810
|
+
@Input({ label: 'Email', inputType: 'email' })
|
|
811
|
+
email: string;
|
|
812
|
+
|
|
813
|
+
@IsString()
|
|
814
|
+
@MinLength(6)
|
|
815
|
+
@Input({ label: 'Password', inputType: 'password' })
|
|
816
|
+
password: string;
|
|
817
|
+
|
|
818
|
+
@IsBoolean()
|
|
819
|
+
@Input({ label: 'Is Active', type: 'checkbox' })
|
|
820
|
+
isActive: boolean;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
@Form({
|
|
824
|
+
onSubmit: dataFetchers.users.create,
|
|
825
|
+
redirectSuccessUrl: '/users',
|
|
826
|
+
})
|
|
827
|
+
export class CreateUserForm extends UserForm {}
|
|
828
|
+
|
|
829
|
+
@Form({
|
|
830
|
+
onSubmit: dataFetchers.users.update,
|
|
831
|
+
getDetailsData: dataFetchers.users.updateDetails,
|
|
832
|
+
redirectSuccessUrl: '/users',
|
|
833
|
+
})
|
|
834
|
+
export class EditUserForm extends UserForm {
|
|
835
|
+
@Input({ label: 'ID', type: 'hidden' })
|
|
836
|
+
id: string;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
@Details({
|
|
840
|
+
getDetailsData: dataFetchers.users.details,
|
|
841
|
+
primaryId: 'id',
|
|
842
|
+
})
|
|
843
|
+
export class DetailsUserForm {
|
|
844
|
+
@DetailsItem()
|
|
845
|
+
id: string;
|
|
846
|
+
|
|
847
|
+
@DetailsItem()
|
|
848
|
+
username: string;
|
|
849
|
+
|
|
850
|
+
@DetailsItem()
|
|
851
|
+
email: string;
|
|
852
|
+
|
|
853
|
+
@DetailsItem()
|
|
854
|
+
isActive: boolean;
|
|
855
|
+
|
|
856
|
+
@DetailsItem()
|
|
857
|
+
createdAt: Date;
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### 2. Routing Implementation
|
|
862
|
+
|
|
863
|
+
```tsx
|
|
864
|
+
// App.tsx
|
|
865
|
+
<Route path={'users'}>
|
|
866
|
+
<Route path={''} element={<ListPage key="user-list" model={UserList} />} />
|
|
867
|
+
<Route path={'create'} element={<FormPage key="user-create" model={CreateUserForm} />} />
|
|
868
|
+
<Route path={'edit/:id'} element={<FormPage key="user-edit" model={EditUserForm} />} />
|
|
869
|
+
<Route path={':id'} element={<DetailsPage key="user-details" model={DetailsUserForm} />} />
|
|
870
|
+
</Route>
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
## Best Practices
|
|
874
|
+
|
|
875
|
+
1. **Model Organization**: Keep your models organized in separate files by entity
|
|
876
|
+
2. **API Configuration**: Centralize your API configuration and data fetchers
|
|
877
|
+
3. **Error Handling**: Implement proper error handling in your API layer
|
|
878
|
+
4. **Validation**: Use class-validator decorators for form validation
|
|
879
|
+
5. **Type Safety**: Leverage TypeScript for better type safety
|
|
880
|
+
6. **Customization**: Use custom headers and components when needed
|
|
881
|
+
7. **Performance**: Use proper keys for React components to avoid unnecessary re-renders
|
|
882
|
+
|
|
883
|
+
## Troubleshooting
|
|
884
|
+
|
|
885
|
+
### Common Issues
|
|
886
|
+
|
|
887
|
+
1. **Authentication Issues**: Ensure your API configuration is properly initialized
|
|
888
|
+
2. **Form Validation**: Check that class-validator decorators are properly applied
|
|
889
|
+
3. **Routing**: Make sure your route paths match your model configurations
|
|
890
|
+
4. **API Calls**: Verify your data fetchers are correctly configured
|
|
891
|
+
|
|
892
|
+
### Debug Tips
|
|
893
|
+
|
|
894
|
+
1. Check browser console for errors
|
|
895
|
+
2. Verify API endpoints are working
|
|
896
|
+
3. Ensure all required dependencies are installed
|
|
897
|
+
4. Check that decorators are properly imported
|
|
898
|
+
|
|
899
|
+
This guide provides a comprehensive overview of implementing the Proje React Panel library. For more specific use cases, refer to the examples folder in the repository.
|