proje-react-panel 1.0.14 → 1.0.15
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 +122 -0
- package/.cursor/settings.json +57 -0
- package/.eslintrc.js +5 -0
- package/.eslintrc.json +26 -0
- package/.prettierrc +10 -0
- package/.vscode/launch.json +17 -0
- package/.vscode/settings.json +8 -0
- package/PTD.md +234 -0
- package/README.md +62 -28
- package/dist/api/CrudApi.d.ts +12 -0
- package/dist/components/Panel.d.ts +2 -2
- package/dist/components/components/Checkbox.d.ts +6 -0
- package/dist/components/components/Counter.d.ts +9 -0
- package/dist/components/components/FormField.d.ts +12 -0
- package/dist/components/components/ImageUploader.d.ts +15 -0
- package/dist/components/components/InnerForm.d.ts +12 -0
- package/dist/components/components/LoadingScreen.d.ts +2 -0
- package/dist/components/components/index.d.ts +8 -0
- package/dist/components/layout/Layout.d.ts +2 -1
- package/dist/components/layout/SideBar.d.ts +4 -3
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/list/Datagrid.d.ts +8 -0
- package/dist/components/list/Pagination.d.ts +11 -0
- package/dist/components/list/index.d.ts +0 -0
- package/dist/{src/screens → components/pages}/ControllerDetails.d.ts +1 -1
- package/dist/components/pages/FormPage.d.ts +11 -0
- package/dist/components/pages/ListPage.d.ts +17 -0
- package/dist/components/pages/Login.d.ts +13 -0
- package/dist/decorators/form/Form.d.ts +6 -0
- package/dist/decorators/form/FormOptions.d.ts +7 -0
- package/dist/decorators/form/Input.d.ts +13 -0
- package/dist/decorators/form/getFormFields.d.ts +3 -0
- package/dist/decorators/{Cell.d.ts → list/Cell.d.ts} +2 -2
- package/dist/decorators/list/GetCellFields.d.ts +2 -0
- package/dist/decorators/list/ImageCell.d.ts +6 -0
- package/dist/decorators/list/List.d.ts +5 -0
- package/dist/decorators/list/ListData.d.ts +6 -0
- package/dist/decorators/list/getListFields.d.ts +2 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +19 -10
- package/dist/index.esm.js +1 -1
- package/dist/initPanel.d.ts +2 -2
- package/dist/store/store.d.ts +1 -5
- package/dist/types/AnyClass.d.ts +1 -0
- package/dist/types/ScreenCreatorData.d.ts +5 -3
- package/dist/types/initPanelOptions.d.ts +0 -6
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/getFields.d.ts +2 -1
- package/package.json +5 -4
- package/src/api/CrudApi.ts +30 -11
- package/src/components/Panel.tsx +11 -11
- package/src/components/components/Checkbox.tsx +9 -0
- package/src/components/components/Counter.tsx +51 -0
- package/src/components/components/FormField.tsx +60 -0
- package/src/components/components/ImageUploader.tsx +301 -0
- package/src/components/components/InnerForm.tsx +75 -0
- package/src/components/components/LoadingScreen.tsx +12 -0
- package/src/components/components/index.ts +8 -0
- package/src/components/layout/Layout.tsx +8 -1
- package/src/components/layout/SideBar.tsx +103 -31
- package/src/components/layout/index.ts +2 -0
- package/src/components/list/Datagrid.tsx +101 -0
- package/src/components/list/Pagination.tsx +110 -0
- package/src/components/list/index.ts +1 -0
- package/src/components/pages/ControllerDetails.tsx +37 -0
- package/src/components/pages/FormPage.tsx +32 -0
- package/src/components/pages/ListPage.tsx +85 -0
- package/src/components/pages/Login.tsx +79 -0
- package/src/decorators/form/Form.ts +18 -0
- package/src/decorators/form/FormOptions.ts +8 -0
- package/src/decorators/form/Input.ts +52 -0
- package/src/decorators/form/getFormFields.ts +13 -0
- package/src/decorators/{Cell.ts → list/Cell.ts} +2 -14
- package/src/decorators/list/GetCellFields.ts +13 -0
- package/src/decorators/list/ImageCell.ts +13 -0
- package/src/decorators/list/List.ts +17 -0
- package/src/decorators/list/ListData.ts +7 -0
- package/src/decorators/list/getListFields.ts +10 -0
- package/src/index.ts +23 -10
- package/src/initPanel.ts +4 -12
- package/src/store/store.ts +23 -28
- package/src/styles/_scrollbar.scss +19 -0
- package/src/styles/counter.scss +42 -0
- package/src/styles/image-uploader.scss +94 -0
- package/src/styles/index.scss +30 -7
- package/src/styles/layout.scss +1 -6
- package/src/styles/list.scss +32 -5
- package/src/styles/loading-screen.scss +42 -0
- package/src/styles/pagination.scss +66 -0
- package/src/styles/sidebar.scss +64 -0
- package/src/types/AnyClass.ts +1 -0
- package/src/types/ScreenCreatorData.ts +5 -3
- package/src/types/initPanelOptions.ts +1 -7
- package/src/utils/format.ts +7 -0
- package/src/utils/getFields.ts +11 -9
- package/dist/api/crudApi.d.ts +0 -17
- package/dist/components/Form.d.ts +0 -6
- package/dist/components/FormField.d.ts +0 -13
- package/dist/components/list/List.d.ts +0 -10
- package/dist/components/screens/ControllerCreate.d.ts +0 -5
- package/dist/components/screens/ControllerDetails.d.ts +0 -5
- package/dist/components/screens/ControllerEdit.d.ts +0 -5
- package/dist/components/screens/ControllerList.d.ts +0 -5
- package/dist/components/screens/Login.d.ts +0 -2
- package/dist/decorators/Input.d.ts +0 -13
- package/dist/hooks/useScreens.d.ts +0 -2
- package/dist/initPanelOptions.d.ts +0 -8
- package/dist/screens/ControllerCreate.d.ts +0 -5
- package/dist/screens/ControllerDetails.d.ts +0 -5
- package/dist/screens/ControllerEdit.d.ts +0 -5
- package/dist/screens/ControllerList.d.ts +0 -5
- package/dist/screens/Form.d.ts +0 -6
- package/dist/src/api/crudApi.d.ts +0 -6
- package/dist/src/components/Panel.d.ts +0 -9
- package/dist/src/components/layout/Layout.d.ts +0 -11
- package/dist/src/components/layout/SideBar.d.ts +0 -10
- package/dist/src/components/list/List.d.ts +0 -10
- package/dist/src/decorators/Cell.d.ts +0 -10
- package/dist/src/decorators/Crud.d.ts +0 -6
- package/dist/src/index.d.ts +0 -8
- package/dist/src/screens/ControllerCreate.d.ts +0 -5
- package/dist/src/screens/ControllerEdit.d.ts +0 -5
- package/dist/src/screens/ControllerList.d.ts +0 -5
- package/dist/src/screens/Form.d.ts +0 -6
- package/dist/src/store/store.d.ts +0 -19
- package/dist/src/types/Screen.d.ts +0 -4
- package/dist/src/types/ScreenCreatorData.d.ts +0 -8
- package/dist/src/utils/createScreens.d.ts +0 -1
- package/dist/src/utils/getFields.d.ts +0 -2
- package/dist/src/utils/getScreens.d.ts +0 -2
- package/dist/utils/crudScreens.d.ts +0 -2
- package/dist/utils/getScreens.d.ts +0 -2
- package/src/api/AuthApi.ts +0 -14
- package/src/components/Form.tsx +0 -70
- package/src/components/FormField.tsx +0 -60
- package/src/components/list/List.tsx +0 -81
- package/src/components/screens/ControllerCreate.tsx +0 -7
- package/src/components/screens/ControllerDetails.tsx +0 -40
- package/src/components/screens/ControllerEdit.tsx +0 -35
- package/src/components/screens/ControllerList.tsx +0 -45
- package/src/components/screens/Login.tsx +0 -68
- package/src/decorators/Input.ts +0 -50
- package/src/hooks/useScreens.tsx +0 -36
- /package/dist/components/{ErrorBoundary.d.ts → components/ErrorBoundary.d.ts} +0 -0
- /package/dist/components/{ErrorComponent.d.ts → components/ErrorComponent.d.ts} +0 -0
- /package/dist/components/{Label.d.ts → components/Label.d.ts} +0 -0
- /package/src/components/{ErrorBoundary.tsx → components/ErrorBoundary.tsx} +0 -0
- /package/src/components/{ErrorComponent.tsx → components/ErrorComponent.tsx} +0 -0
- /package/src/components/{Label.tsx → components/Label.tsx} +0 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
interface PaginationProps {
|
4
|
+
pagination: {
|
5
|
+
total: number;
|
6
|
+
page: number;
|
7
|
+
limit: number;
|
8
|
+
};
|
9
|
+
onPageChange: (page: number) => void;
|
10
|
+
}
|
11
|
+
|
12
|
+
export function Pagination({ pagination, onPageChange }: PaginationProps) {
|
13
|
+
const { total, page, limit } = pagination;
|
14
|
+
const totalPages = Math.floor(total / limit);
|
15
|
+
|
16
|
+
if (totalPages <= 1) return null;
|
17
|
+
|
18
|
+
const renderPageNumbers = () => {
|
19
|
+
const pages = [];
|
20
|
+
const range = 2; // Number of pages to show before and after current page
|
21
|
+
|
22
|
+
// Always show first 2 pages
|
23
|
+
for (let i = 1; i <= Math.min(2, totalPages); i++) {
|
24
|
+
pages.push(
|
25
|
+
<button
|
26
|
+
key={i}
|
27
|
+
onClick={() => onPageChange(i)}
|
28
|
+
className={`pagination-item ${page === i ? 'active' : ''}`}
|
29
|
+
disabled={page === i}
|
30
|
+
>
|
31
|
+
{i}
|
32
|
+
</button>
|
33
|
+
);
|
34
|
+
}
|
35
|
+
|
36
|
+
// Add ellipsis if needed
|
37
|
+
if (page - range > 3) {
|
38
|
+
pages.push(
|
39
|
+
<span key="ellipsis1" className="pagination-ellipsis">
|
40
|
+
...
|
41
|
+
</span>
|
42
|
+
);
|
43
|
+
}
|
44
|
+
|
45
|
+
// Show pages around current page
|
46
|
+
for (let i = Math.max(3, page - range); i <= Math.min(totalPages - 2, page + range); i++) {
|
47
|
+
if (i > 2 && i < totalPages - 1) {
|
48
|
+
pages.push(
|
49
|
+
<button
|
50
|
+
key={i}
|
51
|
+
onClick={() => onPageChange(i)}
|
52
|
+
className={`pagination-item ${page === i ? 'active' : ''}`}
|
53
|
+
disabled={page === i}
|
54
|
+
>
|
55
|
+
{i}
|
56
|
+
</button>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
// Add ellipsis if needed
|
62
|
+
if (page + range < totalPages - 2) {
|
63
|
+
pages.push(
|
64
|
+
<span key="ellipsis2" className="pagination-ellipsis">
|
65
|
+
...
|
66
|
+
</span>
|
67
|
+
);
|
68
|
+
}
|
69
|
+
|
70
|
+
// Always show last 2 pages
|
71
|
+
for (let i = Math.max(totalPages - 1, 3); i <= totalPages; i++) {
|
72
|
+
if (i > 2) {
|
73
|
+
pages.push(
|
74
|
+
<button
|
75
|
+
key={i}
|
76
|
+
onClick={() => onPageChange(i)}
|
77
|
+
className={`pagination-item ${page === i ? 'active' : ''}`}
|
78
|
+
disabled={page === i}
|
79
|
+
>
|
80
|
+
{i}
|
81
|
+
</button>
|
82
|
+
);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
return pages;
|
87
|
+
};
|
88
|
+
|
89
|
+
return (
|
90
|
+
<div className="pagination">
|
91
|
+
<button
|
92
|
+
onClick={() => onPageChange(page - 1)}
|
93
|
+
className={`pagination-item ${page === 1 ? 'disabled' : ''}`}
|
94
|
+
disabled={page === 1}
|
95
|
+
aria-disabled={page === 1}
|
96
|
+
>
|
97
|
+
Previous
|
98
|
+
</button>
|
99
|
+
{renderPageNumbers()}
|
100
|
+
<button
|
101
|
+
onClick={() => onPageChange(page + 1)}
|
102
|
+
className={`pagination-item ${page === totalPages ? 'disabled' : ''}`}
|
103
|
+
disabled={page === totalPages}
|
104
|
+
aria-disabled={page === totalPages}
|
105
|
+
>
|
106
|
+
Next
|
107
|
+
</button>
|
108
|
+
</div>
|
109
|
+
);
|
110
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { useParams } from 'react-router';
|
2
|
+
import React, { useEffect, useState } from 'react';
|
3
|
+
import { Screen } from '../../types/Screen';
|
4
|
+
import { ErrorComponent } from '../components/ErrorComponent';
|
5
|
+
|
6
|
+
export function ControllerDetails({ screen }: { screen: Screen }) {
|
7
|
+
const { id } = useParams();
|
8
|
+
const [data, setData] = useState<any>(null);
|
9
|
+
const [error, setError] = useState(null);
|
10
|
+
|
11
|
+
useEffect(() => {
|
12
|
+
if (screen.controller && id) {
|
13
|
+
/*
|
14
|
+
CrudApi.details({ ...fetchSettings, token }, screen.controller, id)
|
15
|
+
.then(res => {
|
16
|
+
setData(res);
|
17
|
+
})
|
18
|
+
.catch((e: any) => {
|
19
|
+
setError(e);
|
20
|
+
console.error(e);
|
21
|
+
});
|
22
|
+
*/
|
23
|
+
}
|
24
|
+
}, [id, screen]);
|
25
|
+
|
26
|
+
if (error) {
|
27
|
+
return <ErrorComponent error={error} />;
|
28
|
+
}
|
29
|
+
|
30
|
+
return (
|
31
|
+
<p
|
32
|
+
dangerouslySetInnerHTML={{
|
33
|
+
__html: JSON.stringify(data, null, ' ' + '<br/>'),
|
34
|
+
}}
|
35
|
+
/>
|
36
|
+
);
|
37
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import React, { useMemo } from 'react';
|
2
|
+
import { InnerForm } from '../components';
|
3
|
+
import { AnyClass } from '../../types/AnyClass';
|
4
|
+
import { getFormFields } from '../../decorators/form/getFormFields';
|
5
|
+
|
6
|
+
export type GetDetailsDataFN<T> = (param: string) => Promise<T>;
|
7
|
+
export type OnSubmitFN<T> = (data: T) => Promise<T>;
|
8
|
+
|
9
|
+
export interface FormPageProps<T extends AnyClass> {
|
10
|
+
model: T;
|
11
|
+
getDetailsData?: GetDetailsDataFN<T>;
|
12
|
+
redirect?: string;
|
13
|
+
onSubmit: OnSubmitFN<T>;
|
14
|
+
}
|
15
|
+
|
16
|
+
export function FormPage<T extends AnyClass>({
|
17
|
+
model,
|
18
|
+
getDetailsData,
|
19
|
+
onSubmit,
|
20
|
+
redirect,
|
21
|
+
...rest
|
22
|
+
}: FormPageProps<T>) {
|
23
|
+
const formOptions = useMemo(() => getFormFields(model), [model]);
|
24
|
+
return (
|
25
|
+
<InnerForm
|
26
|
+
getDetailsData={getDetailsData}
|
27
|
+
redirect={redirect}
|
28
|
+
onSubmit={onSubmit}
|
29
|
+
formOptions={formOptions}
|
30
|
+
/>
|
31
|
+
);
|
32
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import React, { useMemo, useCallback } from 'react';
|
2
|
+
import { useEffect, useState } from 'react';
|
3
|
+
import { Link, useParams } from 'react-router';
|
4
|
+
import { Datagrid } from '../list/Datagrid';
|
5
|
+
import { ErrorComponent } from '../components/ErrorComponent';
|
6
|
+
import { LoadingScreen } from '../components/LoadingScreen';
|
7
|
+
import { AnyClass } from '../../types/AnyClass';
|
8
|
+
import { getListFields } from '../../decorators/list/getListFields';
|
9
|
+
import { Pagination } from '../list/Pagination';
|
10
|
+
|
11
|
+
export interface PaginationParams {
|
12
|
+
page?: number;
|
13
|
+
limit?: number;
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface PaginatedResponse<T> {
|
17
|
+
data: T[];
|
18
|
+
total: number;
|
19
|
+
page: number;
|
20
|
+
limit: number;
|
21
|
+
}
|
22
|
+
|
23
|
+
export type GetDataForList<T> = ({
|
24
|
+
page,
|
25
|
+
}: PaginationParams) => PaginatedResponse<T> | Promise<PaginatedResponse<T>>;
|
26
|
+
|
27
|
+
export function ListPage<T extends AnyClass>({
|
28
|
+
model,
|
29
|
+
getData,
|
30
|
+
}: {
|
31
|
+
model: T;
|
32
|
+
getData: GetDataForList<T>;
|
33
|
+
}) {
|
34
|
+
const [loading, setLoading] = useState(true);
|
35
|
+
const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
|
36
|
+
const [data, setData] = useState<any>(null);
|
37
|
+
const [error, setError] = useState<unknown | null>(null);
|
38
|
+
const listData = useMemo(() => getListFields(model), [model]);
|
39
|
+
const params = useParams();
|
40
|
+
|
41
|
+
const fetchData = useCallback(
|
42
|
+
async (page: number) => {
|
43
|
+
setLoading(true);
|
44
|
+
try {
|
45
|
+
const result = await getData({ page });
|
46
|
+
setData(result.data);
|
47
|
+
setPagination({
|
48
|
+
total: result.total,
|
49
|
+
page: result.page,
|
50
|
+
limit: result.limit,
|
51
|
+
});
|
52
|
+
} catch (e: unknown) {
|
53
|
+
setError(e);
|
54
|
+
console.error(e);
|
55
|
+
} finally {
|
56
|
+
setLoading(false);
|
57
|
+
}
|
58
|
+
},
|
59
|
+
[getData]
|
60
|
+
);
|
61
|
+
|
62
|
+
useEffect(() => {
|
63
|
+
fetchData(parseInt(params.page as string) || 1);
|
64
|
+
}, [fetchData, params.page]);
|
65
|
+
|
66
|
+
if (loading) return <LoadingScreen />;
|
67
|
+
if (error) return <ErrorComponent error={error} />;
|
68
|
+
|
69
|
+
return (
|
70
|
+
<div className="list">
|
71
|
+
<div className="list-header">
|
72
|
+
<Link to={'create'}>Create</Link>
|
73
|
+
</div>
|
74
|
+
<Datagrid cells={listData.cells} data={data} />
|
75
|
+
<div className="list-footer">
|
76
|
+
<Pagination
|
77
|
+
pagination={pagination}
|
78
|
+
onPageChange={(page: number) => {
|
79
|
+
fetchData(page);
|
80
|
+
}}
|
81
|
+
/>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
);
|
85
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useForm } from 'react-hook-form';
|
3
|
+
import { FormField } from '../components/FormField';
|
4
|
+
import { useNavigate } from 'react-router';
|
5
|
+
import { useAppStore } from '../../store/store';
|
6
|
+
|
7
|
+
interface LoginFormData {
|
8
|
+
username: string;
|
9
|
+
password: string;
|
10
|
+
}
|
11
|
+
|
12
|
+
export type OnLogin = {
|
13
|
+
login: (username: string, password: string) => Promise<LoginResponse>;
|
14
|
+
};
|
15
|
+
|
16
|
+
interface LoginResponse {
|
17
|
+
user: any; // Replace with proper user type if available
|
18
|
+
token: string;
|
19
|
+
}
|
20
|
+
|
21
|
+
interface LoginProps {
|
22
|
+
onLogin: OnLogin;
|
23
|
+
}
|
24
|
+
|
25
|
+
export function Login({ onLogin }: LoginProps) {
|
26
|
+
const {
|
27
|
+
register,
|
28
|
+
handleSubmit,
|
29
|
+
formState: { errors },
|
30
|
+
} = useForm<LoginFormData>();
|
31
|
+
const navigate = useNavigate();
|
32
|
+
const onSubmit = async (data: LoginFormData) => {
|
33
|
+
onLogin.login(data.username, data.password).then((dataInner: LoginResponse) => {
|
34
|
+
const { user, token } = dataInner;
|
35
|
+
localStorage.setItem('token', token);
|
36
|
+
useAppStore.setState({ user });
|
37
|
+
navigate('/');
|
38
|
+
});
|
39
|
+
};
|
40
|
+
|
41
|
+
return (
|
42
|
+
<div className="login-container">
|
43
|
+
<div className="login-panel">
|
44
|
+
<div className="login-header">
|
45
|
+
<h1>Welcome Back</h1>
|
46
|
+
<p>Please sign in to continue</p>
|
47
|
+
</div>
|
48
|
+
<form onSubmit={handleSubmit(onSubmit)} className="login-form">
|
49
|
+
<FormField
|
50
|
+
input={{
|
51
|
+
name: 'username',
|
52
|
+
label: 'Username',
|
53
|
+
placeholder: 'Enter your username',
|
54
|
+
type: 'input',
|
55
|
+
}}
|
56
|
+
register={register}
|
57
|
+
error={errors.username}
|
58
|
+
/>
|
59
|
+
<FormField
|
60
|
+
input={{
|
61
|
+
name: 'password',
|
62
|
+
label: 'Password',
|
63
|
+
inputType: 'password',
|
64
|
+
placeholder: 'Enter your password',
|
65
|
+
type: 'input',
|
66
|
+
}}
|
67
|
+
register={register}
|
68
|
+
error={errors.password}
|
69
|
+
/>
|
70
|
+
<div className="form-actions">
|
71
|
+
<button type="submit" className="submit-button">
|
72
|
+
Sign In
|
73
|
+
</button>
|
74
|
+
</div>
|
75
|
+
</form>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
);
|
79
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import "reflect-metadata";
|
2
|
+
import { AnyClass } from "../../types/AnyClass";
|
3
|
+
|
4
|
+
const FORM_METADATA_KEY = "FormMetadata"; // More descriptive name indicating it's a metadata key
|
5
|
+
|
6
|
+
export interface FormConfiguration {} // Better describes that this is configuration/options for the form
|
7
|
+
|
8
|
+
export function FormDecorator(options?: FormConfiguration): ClassDecorator {
|
9
|
+
return (target: Function) => {
|
10
|
+
if (options) {
|
11
|
+
Reflect.defineMetadata(FORM_METADATA_KEY, options, target);
|
12
|
+
}
|
13
|
+
};
|
14
|
+
}
|
15
|
+
|
16
|
+
export function getFormConfiguration(entityClass: AnyClass): FormConfiguration | undefined {
|
17
|
+
return Reflect.getMetadata(FORM_METADATA_KEY, entityClass);
|
18
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import 'reflect-metadata';
|
2
|
+
import { AnyClass } from '../../types/AnyClass';
|
3
|
+
|
4
|
+
const INPUT_KEY = Symbol('input');
|
5
|
+
|
6
|
+
const isFieldSensitive = (fieldName: string): boolean => {
|
7
|
+
return ['password'].some(term => fieldName.toLowerCase().includes(term));
|
8
|
+
};
|
9
|
+
|
10
|
+
export interface InputOptions {
|
11
|
+
name?: string;
|
12
|
+
label?: string;
|
13
|
+
placeholder?: string;
|
14
|
+
inputType?: 'text' | 'email' | 'tel' | 'password' | 'number' | 'date';
|
15
|
+
type?: 'input' | 'select' | 'textarea' | 'file-upload' | 'checkbox' | 'hidden';
|
16
|
+
selectOptions?: string[]; //TODO: label/value
|
17
|
+
cancelPasswordValidationOnEdit?: boolean;
|
18
|
+
}
|
19
|
+
|
20
|
+
export function Input(options?: InputOptions): PropertyDecorator {
|
21
|
+
return (target, propertyKey) => {
|
22
|
+
const existingInputs: string[] = Reflect.getMetadata(INPUT_KEY, target) || [];
|
23
|
+
Reflect.defineMetadata(INPUT_KEY, [...existingInputs, propertyKey.toString()], target);
|
24
|
+
|
25
|
+
if (options) {
|
26
|
+
const keyString = `${INPUT_KEY.toString()}:${propertyKey.toString()}:options`;
|
27
|
+
Reflect.defineMetadata(keyString, options, target);
|
28
|
+
}
|
29
|
+
};
|
30
|
+
}
|
31
|
+
|
32
|
+
export function getInputFields<T extends AnyClass>(entityClass: T): InputOptions[] {
|
33
|
+
const prototype = entityClass.prototype;
|
34
|
+
const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
|
35
|
+
return inputFields.map(field => {
|
36
|
+
const fields = Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
|
37
|
+
const inputType = fields?.inputType ?? (isFieldSensitive(field) ? 'password' : 'text');
|
38
|
+
return {
|
39
|
+
...fields,
|
40
|
+
editable: fields.editable ?? true,
|
41
|
+
sensitive: fields.sensitive,
|
42
|
+
name: fields?.name ?? field,
|
43
|
+
label: fields?.label ?? field,
|
44
|
+
placeholder: fields?.placeholder ?? field,
|
45
|
+
inputType: inputType,
|
46
|
+
type: fields?.type ?? 'input',
|
47
|
+
selectOptions: fields?.selectOptions ?? [],
|
48
|
+
cancelPasswordValidationOnEdit:
|
49
|
+
fields?.cancelPasswordValidationOnEdit ?? inputType === 'password',
|
50
|
+
};
|
51
|
+
});
|
52
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { AnyClass } from "../../types/AnyClass";
|
2
|
+
import { FormOptions } from "./FormOptions";
|
3
|
+
import { getInputFields } from "./Input";
|
4
|
+
import { getFormConfiguration } from "./Form";
|
5
|
+
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
6
|
+
|
7
|
+
export function getFormFields<T extends AnyClass>(entityClass: T): FormOptions {
|
8
|
+
return {
|
9
|
+
resolver: classValidatorResolver(entityClass as any),
|
10
|
+
form: getFormConfiguration(entityClass),
|
11
|
+
inputs: getInputFields<T>(entityClass),
|
12
|
+
};
|
13
|
+
}
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import "reflect-metadata";
|
2
2
|
|
3
|
-
const CELL_KEY = Symbol("cell");
|
3
|
+
export const CELL_KEY = Symbol("cell");
|
4
4
|
|
5
5
|
export interface CellOptions {
|
6
6
|
name?: string;
|
7
7
|
title?: string;
|
8
|
-
type?: "string" | "
|
8
|
+
type?: "string" | "date" | "image";
|
9
9
|
placeHolder?: string;
|
10
10
|
}
|
11
11
|
|
@@ -20,15 +20,3 @@ export function Cell(options?: CellOptions): PropertyDecorator {
|
|
20
20
|
}
|
21
21
|
};
|
22
22
|
}
|
23
|
-
|
24
|
-
export function getCellFields(entityClass: any): CellOptions[] {
|
25
|
-
const prototype = entityClass.prototype;
|
26
|
-
const cellFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
|
27
|
-
return cellFields.map((field) => {
|
28
|
-
const fields = Reflect.getMetadata(`${CELL_KEY.toString()}:${field}:options`, prototype) || {};
|
29
|
-
return {
|
30
|
-
...fields,
|
31
|
-
name: fields?.name ?? field,
|
32
|
-
};
|
33
|
-
});
|
34
|
-
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { CELL_KEY, CellOptions } from "./Cell";
|
2
|
+
|
3
|
+
export function getCellFields(entityClass: any): CellOptions[] {
|
4
|
+
const prototype = entityClass.prototype;
|
5
|
+
const cellFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
|
6
|
+
return cellFields.map((field) => {
|
7
|
+
const fields = Reflect.getMetadata(`${CELL_KEY.toString()}:${field}:options`, prototype) || {};
|
8
|
+
return {
|
9
|
+
...fields,
|
10
|
+
name: fields?.name ?? field,
|
11
|
+
};
|
12
|
+
});
|
13
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import "reflect-metadata";
|
2
|
+
import { Cell, CellOptions } from "./Cell";
|
3
|
+
|
4
|
+
export interface ImageCellOptions extends CellOptions {
|
5
|
+
baseUrl: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
export function ImageCell(options?: ImageCellOptions): PropertyDecorator {
|
9
|
+
return Cell({
|
10
|
+
...options,
|
11
|
+
type: "image",
|
12
|
+
});
|
13
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import "reflect-metadata";
|
2
|
+
|
3
|
+
const LIST_KEY = "List";
|
4
|
+
|
5
|
+
export interface ListOptions {}
|
6
|
+
|
7
|
+
export function List(options?: ListOptions): ClassDecorator {
|
8
|
+
return (target: Function) => {
|
9
|
+
if (options) {
|
10
|
+
Reflect.defineMetadata(LIST_KEY, options, target);
|
11
|
+
}
|
12
|
+
};
|
13
|
+
}
|
14
|
+
|
15
|
+
export function getClassListData(entityClass: any): ListOptions | undefined {
|
16
|
+
return Reflect.getMetadata(LIST_KEY, entityClass);
|
17
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { getClassListData } from "./List";
|
2
|
+
import { getCellFields } from "./GetCellFields";
|
3
|
+
import { ListData } from "./ListData";
|
4
|
+
|
5
|
+
export function getListFields<T>(entityClass: T): ListData {
|
6
|
+
return {
|
7
|
+
list: getClassListData(entityClass),
|
8
|
+
cells: getCellFields(entityClass),
|
9
|
+
};
|
10
|
+
}
|
package/src/index.ts
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
export {
|
2
|
-
export {
|
3
|
-
export {
|
4
|
-
export {
|
5
|
-
export {
|
6
|
-
export {
|
7
|
-
export {
|
8
|
-
|
9
|
-
export {
|
10
|
-
export {
|
1
|
+
export type { OnSubmitFN, GetDetailsDataFN } from './components/pages/FormPage';
|
2
|
+
export type { GetDataForList } from './components/pages/ListPage';
|
3
|
+
export type { PaginatedResponse, PaginationParams } from './components/pages/ListPage';
|
4
|
+
export type { InitPanelOptions } from './types/initPanelOptions';
|
5
|
+
export type { ScreenCreatorData } from './types/ScreenCreatorData';
|
6
|
+
export type { OnLogin } from './components/pages/Login';
|
7
|
+
export type { AnyClass } from './types/AnyClass';
|
8
|
+
|
9
|
+
export { ListPage } from './components/pages/ListPage';
|
10
|
+
export { FormPage } from './components/pages/FormPage';
|
11
|
+
export type { FormPageProps } from './components/pages/FormPage';
|
12
|
+
export { Login } from './components/pages/Login';
|
13
|
+
export { Layout } from './components/layout/Layout';
|
14
|
+
export { Panel } from './components/Panel';
|
15
|
+
export { Counter } from './components/components/Counter';
|
16
|
+
|
17
|
+
//TODO: decerator index.ts ayır
|
18
|
+
export { List } from './decorators/list/List';
|
19
|
+
export { ImageCell } from './decorators/list/ImageCell';
|
20
|
+
export { Crud } from './decorators/Crud';
|
21
|
+
export { Cell } from './decorators/list/Cell';
|
22
|
+
export { Input } from './decorators/form/Input';
|
23
|
+
// Export page components
|
package/src/initPanel.ts
CHANGED
@@ -1,14 +1,6 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
3
|
-
import { ScreenCreatorData } from "./types/ScreenCreatorData";
|
4
|
-
import { InitPanelOptions } from "./types/initPanelOptions";
|
5
|
-
import { useAppStore } from "./store/store";
|
1
|
+
import { InitPanelOptions } from './types/initPanelOptions';
|
2
|
+
import { useAppStore } from './store/store';
|
6
3
|
|
7
|
-
export function initPanel({
|
8
|
-
|
9
|
-
Object.entries(crud).forEach(([key, value]) => {
|
10
|
-
screensCrudOptions[key] = getFields(key, value);
|
11
|
-
});
|
12
|
-
createScreens(screensCrudOptions);
|
13
|
-
useAppStore.setState({ fetchSettings: { baseUrl: fetch.baseURL }, screenPaths });
|
4
|
+
export function initPanel({ screenPaths }: InitPanelOptions) {
|
5
|
+
useAppStore.setState({ screenPaths });
|
14
6
|
}
|
package/src/store/store.ts
CHANGED
@@ -1,37 +1,32 @@
|
|
1
|
-
import { createJSONStorage, persist } from
|
2
|
-
import { createWithEqualityFn } from
|
3
|
-
import { shallow } from
|
4
|
-
import { ScreenCreatorData } from
|
1
|
+
import { createJSONStorage, persist } from 'zustand/middleware';
|
2
|
+
import { createWithEqualityFn } from 'zustand/traditional';
|
3
|
+
import { shallow } from 'zustand/vanilla/shallow';
|
4
|
+
import { ScreenCreatorData } from '../types/ScreenCreatorData';
|
5
5
|
|
6
6
|
interface User {
|
7
|
-
|
7
|
+
username: string;
|
8
8
|
}
|
9
9
|
|
10
10
|
interface AppState {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
screenPaths: Record<string, string>;
|
15
|
-
token: string | null;
|
11
|
+
user: User | null;
|
12
|
+
screens: Record<string, ScreenCreatorData> | null;
|
13
|
+
screenPaths: Record<string, string>;
|
16
14
|
}
|
17
15
|
|
18
16
|
export const useAppStore = createWithEqualityFn<AppState>()(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
}
|
35
|
-
),
|
36
|
-
shallow
|
17
|
+
persist(
|
18
|
+
_ => ({
|
19
|
+
screens: null,
|
20
|
+
user: null,
|
21
|
+
screenPaths: {},
|
22
|
+
}),
|
23
|
+
{
|
24
|
+
name: 'app-store-1',
|
25
|
+
storage: createJSONStorage(() => localStorage),
|
26
|
+
partialize: state => ({
|
27
|
+
user: state.user,
|
28
|
+
}),
|
29
|
+
}
|
30
|
+
),
|
31
|
+
shallow
|
37
32
|
);
|