proje-react-panel 1.1.4 → 1.1.6
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 +21 -95
- package/.vscode/settings.json +6 -1
- package/PTD.md +124 -24
- package/dist/components/DetailsPage.d.ts +2 -2
- package/dist/components/Login.d.ts +2 -2
- package/dist/components/Panel.d.ts +2 -2
- package/dist/components/form/FormField.d.ts +1 -5
- package/dist/components/form/FormHeader.d.ts +10 -0
- package/dist/components/form/FormPage.d.ts +12 -1
- package/dist/components/form/Select.d.ts +1 -1
- package/dist/components/form/SelectStyles.d.ts +3 -3
- package/dist/components/list/CellField.d.ts +2 -3
- package/dist/components/list/EmptyList.d.ts +1 -1
- package/dist/decorators/details/Details.d.ts +1 -2
- package/dist/decorators/form/Form.d.ts +3 -5
- package/dist/decorators/form/Input.d.ts +10 -2
- package/dist/decorators/form/inputs/SelectInput.d.ts +8 -7
- package/dist/decorators/list/Cell.d.ts +3 -3
- package/dist/decorators/list/List.d.ts +3 -4
- package/dist/decorators/list/getListPageMeta.d.ts +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/store/store.d.ts +1 -0
- package/dist/utils/decerators.d.ts +3 -3
- package/eslint.config.mjs +60 -0
- package/package.json +6 -3
- package/src/api/CrudApi.ts +59 -53
- package/src/components/DetailsPage.tsx +8 -6
- package/src/components/Login.tsx +11 -4
- package/src/components/Panel.tsx +3 -3
- package/src/components/form/Checkbox.tsx +1 -1
- package/src/components/form/FormField.tsx +13 -9
- package/src/components/form/FormHeader.tsx +18 -0
- package/src/components/form/FormPage.tsx +146 -5
- package/src/components/form/InnerForm.tsx +13 -8
- package/src/components/form/Select.tsx +14 -15
- package/src/components/form/SelectStyles.ts +4 -4
- package/src/components/layout/Layout.tsx +1 -7
- package/src/components/layout/SideBar.tsx +1 -2
- package/src/components/list/CellField.tsx +2 -5
- package/src/components/list/Datagrid.tsx +0 -1
- package/src/components/list/EmptyList.tsx +2 -2
- package/src/components/list/FilterPopup.tsx +4 -2
- package/src/components/list/ListPage.tsx +7 -5
- package/src/components/list/cells/DefaultCell.tsx +2 -0
- package/src/decorators/details/Details.ts +2 -2
- package/src/decorators/details/DetailsItem.ts +2 -0
- package/src/decorators/form/Form.ts +10 -11
- package/src/decorators/form/Input.ts +19 -10
- package/src/decorators/form/inputs/SelectInput.ts +8 -7
- package/src/decorators/list/Cell.ts +6 -4
- package/src/decorators/list/ExtendedCell.ts +1 -9
- package/src/decorators/list/List.ts +8 -4
- package/src/decorators/list/cells/ImageCell.ts +1 -1
- package/src/decorators/list/getListPageMeta.ts +4 -3
- package/src/store/store.ts +3 -1
- package/src/styles/components/form-header.scss +75 -0
- package/src/styles/index.scss +1 -0
- package/src/types/AnyClass.ts +3 -0
- package/src/utils/decerators.ts +3 -2
- package/.eslintrc.js +0 -23
- package/.eslintrc.json +0 -26
- package/src/initPanel.ts +0 -3
- package/src/types/initPanelOptions.ts +0 -1
@@ -1,8 +1,8 @@
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
2
2
|
import { InputConfiguration } from '../../decorators/form/Input';
|
3
|
-
import { useFormContext, Controller
|
3
|
+
import { useFormContext, Controller } from 'react-hook-form';
|
4
4
|
import { SelectInputConfiguration } from '../../decorators/form/inputs/SelectInput';
|
5
|
-
import ReactSelect
|
5
|
+
import ReactSelect from 'react-select';
|
6
6
|
import { darkSelectStyles } from './SelectStyles';
|
7
7
|
|
8
8
|
interface SelectProps {
|
@@ -10,24 +10,24 @@ interface SelectProps {
|
|
10
10
|
fieldName: string;
|
11
11
|
}
|
12
12
|
|
13
|
-
interface OptionType {
|
13
|
+
interface OptionType<TValue> {
|
14
14
|
label: string;
|
15
|
-
value:
|
15
|
+
value: TValue;
|
16
16
|
}
|
17
17
|
|
18
|
-
export function Select({ input, fieldName }: SelectProps) {
|
19
|
-
const inputSelect = input as SelectInputConfiguration
|
18
|
+
export function Select<TValue>({ input, fieldName }: SelectProps) {
|
19
|
+
const inputSelect = input as SelectInputConfiguration<TValue>;
|
20
20
|
const { control } = useFormContext();
|
21
|
-
const [options, setOptions] = useState<OptionType[]>(inputSelect.defaultOptions || []);
|
22
|
-
|
21
|
+
const [options, setOptions] = useState<OptionType<TValue>[]>(inputSelect.defaultOptions || []);
|
22
|
+
//NOTE: is added to component to fix type error. Need to find a better solution.
|
23
|
+
const styles = useMemo(() => darkSelectStyles<TValue>(), []);
|
23
24
|
useEffect(() => {
|
24
25
|
if (inputSelect.onSelectPreloader) {
|
25
26
|
inputSelect.onSelectPreloader().then(option => {
|
26
27
|
setOptions(option);
|
27
|
-
setKey(key + 1);
|
28
28
|
});
|
29
29
|
}
|
30
|
-
}, [
|
30
|
+
}, [inputSelect, inputSelect.onSelectPreloader]);
|
31
31
|
|
32
32
|
return (
|
33
33
|
<Controller
|
@@ -36,12 +36,11 @@ export function Select({ input, fieldName }: SelectProps) {
|
|
36
36
|
render={({ field }) => {
|
37
37
|
return (
|
38
38
|
<ReactSelect
|
39
|
-
key={key}
|
40
39
|
options={options}
|
41
|
-
styles={
|
40
|
+
styles={styles}
|
42
41
|
value={options.find(option => option.value === field.value) || null}
|
43
|
-
onChange={(selectedOption: OptionType | null) => {
|
44
|
-
field.onChange(selectedOption?.value);
|
42
|
+
onChange={(selectedOption: OptionType<TValue> | null) => {
|
43
|
+
field.onChange(selectedOption?.value as TValue);
|
45
44
|
}}
|
46
45
|
/>
|
47
46
|
);
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { StylesConfig } from 'react-select';
|
2
2
|
|
3
|
-
interface OptionType {
|
3
|
+
interface OptionType<TValue> {
|
4
4
|
label: string;
|
5
|
-
value:
|
5
|
+
value: TValue;
|
6
6
|
}
|
7
7
|
|
8
|
-
export const darkSelectStyles: StylesConfig<OptionType
|
8
|
+
export const darkSelectStyles = <TValue>(): StylesConfig<OptionType<TValue>, false> => ({
|
9
9
|
control: (baseStyles, state) => ({
|
10
10
|
...baseStyles,
|
11
11
|
backgroundColor: '#1f2937',
|
@@ -70,4 +70,4 @@ export const darkSelectStyles: StylesConfig<OptionType, false> = {
|
|
70
70
|
color: '#6366f1',
|
71
71
|
},
|
72
72
|
}),
|
73
|
-
};
|
73
|
+
});
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { SideBar } from './SideBar';
|
3
3
|
import { useAppStore } from '../../store/store';
|
4
|
-
import { useNavigate } from 'react-router';
|
5
4
|
|
6
5
|
export function Layout<IconType>({
|
7
6
|
children,
|
@@ -17,18 +16,13 @@ export function Layout<IconType>({
|
|
17
16
|
const { user } = useAppStore(s => ({
|
18
17
|
user: s.user,
|
19
18
|
}));
|
20
|
-
const navigate = useNavigate();
|
21
19
|
if (!user) {
|
22
20
|
logout?.('redirect');
|
23
21
|
}
|
24
22
|
|
25
23
|
return (
|
26
24
|
<div className="layout">
|
27
|
-
<SideBar
|
28
|
-
onLogout={logout}
|
29
|
-
menu={menu}
|
30
|
-
getIcons={getIcons}
|
31
|
-
/>
|
25
|
+
<SideBar onLogout={logout} menu={menu} getIcons={getIcons} />
|
32
26
|
<main className="content">{children}</main>
|
33
27
|
</div>
|
34
28
|
);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
|
-
import { Link, useLocation
|
2
|
+
import { Link, useLocation } from 'react-router';
|
3
3
|
|
4
4
|
type GetMenuFunction<IconType> = () => { name: string; path: string; iconType: IconType }[];
|
5
5
|
|
@@ -16,7 +16,6 @@ export function SideBar<IconType>({
|
|
16
16
|
}) {
|
17
17
|
const [isOpen, setIsOpen] = useState(true);
|
18
18
|
const location = useLocation();
|
19
|
-
const navigate = useNavigate();
|
20
19
|
|
21
20
|
// Helper function to determine if a link is active
|
22
21
|
const isActiveLink = (path: string) => {
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { ImageCellOptions } from '../../decorators/list/cells/ImageCell';
|
3
2
|
import { AnyClass } from '../../types/AnyClass';
|
4
|
-
import { CellConfiguration
|
3
|
+
import { CellConfiguration } from '../../decorators/list/Cell';
|
5
4
|
import { BooleanCell } from './cells/BooleanCell';
|
6
5
|
import { DateCell } from './cells/DateCell';
|
7
6
|
import { ImageCell } from './cells/ImageCell';
|
@@ -11,13 +10,11 @@ import { DownloadCell } from './cells/DownloadCell';
|
|
11
10
|
|
12
11
|
interface CellFieldProps<T extends AnyClass> {
|
13
12
|
configuration: CellConfiguration;
|
14
|
-
|
15
|
-
value: any;
|
13
|
+
value: T[keyof T];
|
16
14
|
}
|
17
15
|
|
18
16
|
export function CellField<T extends AnyClass>({
|
19
17
|
configuration,
|
20
|
-
item,
|
21
18
|
value,
|
22
19
|
}: CellFieldProps<T>): React.ReactElement {
|
23
20
|
let render;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
|
3
|
-
export
|
3
|
+
export function EmptyList() {
|
4
4
|
return (
|
5
5
|
<div className="empty-list">
|
6
6
|
<div className="empty-list-content">
|
@@ -23,4 +23,4 @@ export const EmptyList: React.FC = () => {
|
|
23
23
|
</div>
|
24
24
|
</div>
|
25
25
|
);
|
26
|
-
}
|
26
|
+
}
|
@@ -134,6 +134,8 @@ export function FilterPopup<T extends AnyClass>({
|
|
134
134
|
listPageMeta,
|
135
135
|
activeFilters,
|
136
136
|
}: FilterPopupProps<T>): React.ReactElement | null {
|
137
|
+
//TODO: any is not a good solution, we need to find a better way to do this
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
137
139
|
const [filters, setFilters] = React.useState<Record<string, any>>(activeFilters ?? {});
|
138
140
|
const popupRef = useRef<HTMLDivElement>(null);
|
139
141
|
const fields = useMemo(
|
@@ -158,8 +160,8 @@ export function FilterPopup<T extends AnyClass>({
|
|
158
160
|
}, [isOpen, onClose]);
|
159
161
|
|
160
162
|
if (!isOpen) return null;
|
161
|
-
|
162
|
-
const handleFilterChange = (fieldName: string, value:
|
163
|
+
|
164
|
+
const handleFilterChange = (fieldName: string, value: string) => {
|
163
165
|
setFilters(prev => ({
|
164
166
|
...prev,
|
165
167
|
[fieldName]: value,
|
@@ -20,7 +20,7 @@ export function ListPage<T extends AnyClass>({
|
|
20
20
|
const listPageMeta = useMemo(() => getListPageMeta(model), [model]);
|
21
21
|
|
22
22
|
const [loading, setLoading] = useState(true);
|
23
|
-
const [data, setData] = useState<
|
23
|
+
const [data, setData] = useState<T[]>([]);
|
24
24
|
const [error, setError] = useState<unknown>(null);
|
25
25
|
|
26
26
|
const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
|
@@ -37,7 +37,9 @@ export function ListPage<T extends AnyClass>({
|
|
37
37
|
page,
|
38
38
|
filters: filters ?? activeFilters ?? {},
|
39
39
|
});
|
40
|
-
|
40
|
+
//TODO: any is not a good solution, we need to find a better way to do this
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
42
|
+
setData(result.data as any);
|
41
43
|
setPagination({
|
42
44
|
total: result.total,
|
43
45
|
page: result.page,
|
@@ -50,7 +52,7 @@ export function ListPage<T extends AnyClass>({
|
|
50
52
|
setLoading(false);
|
51
53
|
}
|
52
54
|
},
|
53
|
-
[activeFilters, listPageMeta.class
|
55
|
+
[activeFilters, listPageMeta.class]
|
54
56
|
);
|
55
57
|
|
56
58
|
useEffect(() => {
|
@@ -60,7 +62,7 @@ export function ListPage<T extends AnyClass>({
|
|
60
62
|
filtersFromUrl[key] = value;
|
61
63
|
});
|
62
64
|
setActiveFilters(filtersFromUrl);
|
63
|
-
}, [
|
65
|
+
}, []);
|
64
66
|
|
65
67
|
useEffect(() => {
|
66
68
|
if (activeFilters) {
|
@@ -68,7 +70,7 @@ export function ListPage<T extends AnyClass>({
|
|
68
70
|
}
|
69
71
|
}, [fetchData, params.page, activeFilters, listPageMeta.class.getData]);
|
70
72
|
|
71
|
-
const handleFilterApply = (filters: Record<string,
|
73
|
+
const handleFilterApply = (filters: Record<string, string>) => {
|
72
74
|
setActiveFilters(filters);
|
73
75
|
|
74
76
|
// Convert filters to URLSearchParams
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
2
2
|
import { CellConfiguration } from '../../../decorators/list/Cell';
|
3
3
|
|
4
4
|
interface DefaultCellProps {
|
5
|
+
//TODO: any is not a good solution, we need to find a better way to do this
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5
7
|
value: any;
|
6
8
|
configuration: CellConfiguration;
|
7
9
|
}
|
@@ -8,10 +8,10 @@ interface DetailsOptions<T extends AnyClass> {
|
|
8
8
|
getDetailsData: GetDetailsDataFN<T>;
|
9
9
|
}
|
10
10
|
|
11
|
-
export
|
11
|
+
export type DetailsConfiguration<T extends AnyClass> = DetailsOptions<T>;
|
12
12
|
|
13
13
|
export function Details<T extends AnyClass>(options?: DetailsOptions<T>): ClassDecorator {
|
14
|
-
return (target:
|
14
|
+
return (target: object) => {
|
15
15
|
if (options) {
|
16
16
|
Reflect.defineMetadata(DETAILS_METADATA_KEY, options, target);
|
17
17
|
}
|
@@ -26,6 +26,8 @@ export function DetailsItem(options?: DetailsItemOptions): PropertyDecorator {
|
|
26
26
|
export function getDetailsItemFields<T extends AnyClass>(
|
27
27
|
entityClass: T
|
28
28
|
): DetailsItemConfiguration[] {
|
29
|
+
//TODO: any is not a good solution, we need to find a better way to do this
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
29
31
|
const prototype = (entityClass as any).prototype;
|
30
32
|
const inputFields: string[] = Reflect.getMetadata(DETAILS_ITEM_KEY, prototype) || [];
|
31
33
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import 'reflect-metadata';
|
2
2
|
import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
|
3
3
|
import { GetDetailsDataFN } from '../details/Details';
|
4
|
-
import { InputConfiguration } from './Input';
|
5
4
|
|
6
5
|
const DETAILS_METADATA_KEY = 'DetailsMetaData';
|
7
6
|
export type OnSubmitFN<T> = (data: T | FormData) => Promise<T>;
|
@@ -9,21 +8,19 @@ export type OnSubmitFN<T> = (data: T | FormData) => Promise<T>;
|
|
9
8
|
interface FormOptions<T extends AnyClass> {
|
10
9
|
onSubmit: OnSubmitFN<T>;
|
11
10
|
getDetailsData?: GetDetailsDataFN<T>;
|
12
|
-
/** @deprecated */
|
13
|
-
redirectBackOnSuccess?: boolean;
|
14
11
|
type?: 'json' | 'formData';
|
15
12
|
redirectSuccessUrl?: string;
|
16
13
|
}
|
17
14
|
|
18
|
-
export interface FormConfiguration<T extends AnyClass>
|
19
|
-
|
20
|
-
|
15
|
+
export interface FormConfiguration<T extends AnyClass> {
|
16
|
+
onSubmit: OnSubmitFN<T>;
|
17
|
+
getDetailsData?: GetDetailsDataFN<T>;
|
21
18
|
type: 'json' | 'formData';
|
22
19
|
redirectSuccessUrl?: string;
|
23
20
|
}
|
24
21
|
|
25
22
|
export function Form<T extends AnyClass>(options?: FormOptions<T>): ClassDecorator {
|
26
|
-
return (target:
|
23
|
+
return (target: object) => {
|
27
24
|
if (options) {
|
28
25
|
Reflect.defineMetadata(DETAILS_METADATA_KEY, options, target);
|
29
26
|
}
|
@@ -35,14 +32,16 @@ export function getFormConfiguration<T extends AnyClass, K extends AnyClassConst
|
|
35
32
|
): FormConfiguration<T> {
|
36
33
|
const formOptions: FormOptions<T> = Reflect.getMetadata(
|
37
34
|
DETAILS_METADATA_KEY,
|
38
|
-
entityClass as
|
35
|
+
entityClass as object
|
39
36
|
);
|
40
37
|
if (!formOptions) {
|
41
38
|
throw new Error('Form decerator should be used on class');
|
42
39
|
}
|
43
|
-
|
44
|
-
|
40
|
+
const formConfiguration: FormConfiguration<T> = {
|
41
|
+
onSubmit: formOptions.onSubmit,
|
42
|
+
getDetailsData: formOptions.getDetailsData,
|
45
43
|
type: formOptions.type ?? 'json',
|
46
|
-
|
44
|
+
redirectSuccessUrl: formOptions.redirectSuccessUrl,
|
47
45
|
};
|
46
|
+
return formConfiguration;
|
48
47
|
}
|
@@ -11,21 +11,29 @@ export type InputTypes = 'input' | 'textarea' | 'file-upload' | 'checkbox' | 'hi
|
|
11
11
|
export type ExtendedInputTypes = InputTypes | 'select';
|
12
12
|
|
13
13
|
export interface InputOptions {
|
14
|
+
name?: string;
|
14
15
|
type?: InputTypes;
|
15
16
|
inputType?: 'text' | 'email' | 'tel' | 'password' | 'number' | 'date';
|
16
|
-
name?: string;
|
17
17
|
label?: string;
|
18
18
|
placeholder?: string;
|
19
19
|
nestedFields?: InputConfiguration[];
|
20
|
+
includeInCSV?: boolean;
|
21
|
+
includeInJSON?: boolean;
|
20
22
|
}
|
21
23
|
|
22
24
|
export interface ExtendedInputOptions extends Omit<InputOptions, 'type'> {
|
23
25
|
type: ExtendedInputTypes;
|
24
26
|
}
|
25
27
|
|
26
|
-
export interface InputConfiguration
|
28
|
+
export interface InputConfiguration {
|
27
29
|
name: string;
|
28
30
|
type: ExtendedInputTypes;
|
31
|
+
inputType: 'text' | 'email' | 'tel' | 'password' | 'number' | 'date';
|
32
|
+
label?: string;
|
33
|
+
placeholder?: string;
|
34
|
+
nestedFields?: InputConfiguration[];
|
35
|
+
includeInCSV: boolean;
|
36
|
+
includeInJSON: boolean;
|
29
37
|
}
|
30
38
|
|
31
39
|
export function Input(options?: InputOptions): PropertyDecorator {
|
@@ -44,9 +52,8 @@ export function ExtendedInput(options?: ExtendedInputOptions): PropertyDecorator
|
|
44
52
|
return (target, propertyKey) => {
|
45
53
|
const existingInputs: string[] = Reflect.getMetadata(INPUT_KEY, target) || [];
|
46
54
|
Reflect.defineMetadata(INPUT_KEY, [...existingInputs, propertyKey.toString()], target);
|
47
|
-
|
48
55
|
if (options) {
|
49
|
-
|
56
|
+
const keyString = `${INPUT_KEY.toString()}:${propertyKey.toString()}:options`;
|
50
57
|
Reflect.defineMetadata(keyString, options, target);
|
51
58
|
}
|
52
59
|
};
|
@@ -55,21 +62,23 @@ export function ExtendedInput(options?: ExtendedInputOptions): PropertyDecorator
|
|
55
62
|
export function getInputFields<T extends AnyClass>(
|
56
63
|
entityClass: AnyClassConstructor<T>
|
57
64
|
): InputConfiguration[] {
|
58
|
-
const prototype =
|
65
|
+
const prototype = entityClass.prototype;
|
59
66
|
const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
|
60
67
|
return inputFields.map(field => {
|
61
|
-
const fields
|
68
|
+
const fields: InputOptions =
|
69
|
+
Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
|
62
70
|
const inputType = fields?.inputType ?? (isFieldSensitive(field) ? 'password' : 'text');
|
63
|
-
|
71
|
+
const inputConfiguration: InputConfiguration = {
|
64
72
|
...fields,
|
65
|
-
editable: fields.editable ?? true,
|
66
|
-
sensitive: fields.sensitive,
|
67
73
|
name: fields?.name ?? field,
|
68
74
|
label: fields?.label ?? field,
|
69
75
|
placeholder: fields?.placeholder ?? field,
|
70
76
|
inputType: inputType,
|
77
|
+
nestedFields: fields?.nestedFields ?? [],
|
71
78
|
type: fields?.type ?? 'input',
|
72
|
-
|
79
|
+
includeInCSV: fields?.includeInCSV ?? false,
|
80
|
+
includeInJSON: fields?.includeInJSON ?? false,
|
73
81
|
};
|
82
|
+
return inputConfiguration;
|
74
83
|
});
|
75
84
|
}
|
@@ -1,17 +1,18 @@
|
|
1
1
|
import { ExtendedInput, ExtendedInputOptions, InputConfiguration, InputOptions } from '../Input';
|
2
2
|
|
3
|
-
export interface SelectInputOptions extends InputOptions {
|
4
|
-
onSelectPreloader?: () => Promise<{ label: string; value:
|
5
|
-
defaultOptions?: { value:
|
3
|
+
export interface SelectInputOptions<T> extends InputOptions {
|
4
|
+
onSelectPreloader?: () => Promise<{ label: string; value: T }[]>;
|
5
|
+
defaultOptions?: { value: T; label: string }[];
|
6
|
+
csvExport?: never;
|
6
7
|
}
|
7
8
|
|
8
|
-
export interface SelectInputConfiguration extends InputConfiguration {
|
9
|
+
export interface SelectInputConfiguration<T> extends InputConfiguration {
|
9
10
|
type: 'select';
|
10
|
-
onSelectPreloader?: () => Promise<{ label: string; value:
|
11
|
-
defaultOptions?: { value:
|
11
|
+
onSelectPreloader?: () => Promise<{ label: string; value: T }[]>;
|
12
|
+
defaultOptions?: { value: T; label: string }[];
|
12
13
|
}
|
13
14
|
|
14
|
-
export function SelectInput(options?: SelectInputOptions): PropertyDecorator {
|
15
|
+
export function SelectInput<K>(options?: SelectInputOptions<K>): PropertyDecorator {
|
15
16
|
return ExtendedInput({
|
16
17
|
...options,
|
17
18
|
type: 'select',
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import 'reflect-metadata';
|
2
|
-
import { AnyClass } from '../../types/AnyClass';
|
2
|
+
import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
|
3
3
|
import { createDecorator, DecoratorMap } from '../../utils/decerators';
|
4
4
|
|
5
|
-
export const CELL_KEY
|
5
|
+
export const CELL_KEY = Symbol('cell');
|
6
6
|
|
7
7
|
interface Filter {
|
8
8
|
type: 'string' | 'number' | 'date' | 'static-select';
|
@@ -42,8 +42,10 @@ export function Cell(options?: CellOptions): PropertyDecorator {
|
|
42
42
|
return createDecorator(CELL_KEY, options, cellMap);
|
43
43
|
}
|
44
44
|
|
45
|
-
export function getCellFields<T extends AnyClass>(
|
46
|
-
|
45
|
+
export function getCellFields<T extends AnyClass>(
|
46
|
+
entityClass: AnyClassConstructor<T>
|
47
|
+
): CellConfiguration[] {
|
48
|
+
const prototype = entityClass.prototype;
|
47
49
|
const inputFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
|
48
50
|
|
49
51
|
return inputFields.map(field => {
|
@@ -1,12 +1,4 @@
|
|
1
|
-
import {
|
2
|
-
CELL_KEY,
|
3
|
-
CellConfiguration,
|
4
|
-
CellOptions,
|
5
|
-
CellTypes,
|
6
|
-
Cell,
|
7
|
-
ExtendedCellTypes,
|
8
|
-
cellMap,
|
9
|
-
} from './Cell';
|
1
|
+
import { CELL_KEY, CellConfiguration, CellOptions, ExtendedCellTypes, cellMap } from './Cell';
|
10
2
|
import { createDecorator, DecoratorMap } from '../../utils/decerators';
|
11
3
|
|
12
4
|
export interface ExtendedCellOptions extends Omit<CellOptions, 'type'> {
|
@@ -1,11 +1,13 @@
|
|
1
1
|
import 'reflect-metadata';
|
2
|
-
import { AnyClass } from '../../types/AnyClass';
|
2
|
+
import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
|
3
3
|
|
4
4
|
const LIST_METADATA_KEY = 'ListMetaData';
|
5
5
|
|
6
6
|
export interface GetDataParams {
|
7
7
|
page?: number;
|
8
8
|
limit?: number;
|
9
|
+
//TODO: fix this
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
9
11
|
filters?: Record<string, any>;
|
10
12
|
}
|
11
13
|
|
@@ -35,17 +37,19 @@ export interface ListOptions<T> {
|
|
35
37
|
cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
|
36
38
|
}
|
37
39
|
|
38
|
-
export
|
40
|
+
export type ListConfiguration<T> = ListOptions<T>;
|
39
41
|
|
40
42
|
export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
|
41
|
-
return (target:
|
43
|
+
return (target: object) => {
|
42
44
|
if (options) {
|
43
45
|
Reflect.defineMetadata(LIST_METADATA_KEY, options, target);
|
44
46
|
}
|
45
47
|
};
|
46
48
|
}
|
47
49
|
|
48
|
-
export function getListConfiguration<T extends AnyClass>(
|
50
|
+
export function getListConfiguration<T extends AnyClass>(
|
51
|
+
entityClass: AnyClassConstructor<T>
|
52
|
+
): ListConfiguration<T> {
|
49
53
|
const listConfiguration = Reflect.getMetadata(LIST_METADATA_KEY, entityClass);
|
50
54
|
if (!listConfiguration) {
|
51
55
|
throw new Error('List decerator should be used on class');
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { CellConfiguration, CellOptions } from '../Cell';
|
2
|
-
import { ExtendedCell
|
2
|
+
import { ExtendedCell } from '../ExtendedCell';
|
3
3
|
|
4
4
|
export interface ImageCellOptions extends Omit<CellOptions, 'type'> {
|
5
5
|
baseUrl: string;
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import { AnyClass } from '../../types/AnyClass';
|
2
|
-
import { getDetailsItemFields } from '../details/DetailsItem';
|
1
|
+
import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
|
3
2
|
import { CellConfiguration, getCellFields } from './Cell';
|
4
3
|
import { getListConfiguration, ListConfiguration } from './List';
|
5
4
|
|
@@ -8,7 +7,9 @@ export interface ListPageMeta<T extends AnyClass> {
|
|
8
7
|
cells: CellConfiguration[];
|
9
8
|
}
|
10
9
|
|
11
|
-
export function getListPageMeta<T extends AnyClass>(
|
10
|
+
export function getListPageMeta<T extends AnyClass>(
|
11
|
+
entityClass: AnyClassConstructor<T>
|
12
|
+
): ListPageMeta<T> {
|
12
13
|
return {
|
13
14
|
class: getListConfiguration(entityClass),
|
14
15
|
cells: getCellFields<T>(entityClass),
|
package/src/store/store.ts
CHANGED
@@ -8,12 +8,14 @@ interface User {
|
|
8
8
|
|
9
9
|
interface AppState {
|
10
10
|
user: User | null;
|
11
|
+
login: (user: User) => void;
|
11
12
|
}
|
12
13
|
|
13
14
|
export const useAppStore = createWithEqualityFn<AppState>()(
|
14
15
|
persist(
|
15
|
-
|
16
|
+
set => ({
|
16
17
|
user: null,
|
18
|
+
login: (user: User) => set({ user }),
|
17
19
|
}),
|
18
20
|
{
|
19
21
|
name: 'app-store-1',
|
@@ -0,0 +1,75 @@
|
|
1
|
+
.form-header {
|
2
|
+
display: flex;
|
3
|
+
justify-content: space-between;
|
4
|
+
align-items: center;
|
5
|
+
padding: 1rem;
|
6
|
+
margin-bottom: 1.5rem;
|
7
|
+
background-color: #333333;
|
8
|
+
border-radius: 8px 0 0 8px;
|
9
|
+
|
10
|
+
.form-title {
|
11
|
+
margin: 0;
|
12
|
+
font-size: 1.5rem;
|
13
|
+
font-weight: 600;
|
14
|
+
color: #cccccc;
|
15
|
+
}
|
16
|
+
|
17
|
+
.form-actions {
|
18
|
+
display: flex;
|
19
|
+
gap: 0.5rem;
|
20
|
+
align-items: center;
|
21
|
+
|
22
|
+
//keep this even its not used in project (it can be used in other projects)
|
23
|
+
.export-button,
|
24
|
+
.import-button {
|
25
|
+
padding: 0.5rem 1rem;
|
26
|
+
border: 1px solid #444444;
|
27
|
+
border-radius: 4px;
|
28
|
+
background-color: #222222;
|
29
|
+
color: #cccccc;
|
30
|
+
font-size: 0.875rem;
|
31
|
+
cursor: pointer;
|
32
|
+
transition: all 0.2s ease;
|
33
|
+
|
34
|
+
&:hover {
|
35
|
+
background-color: #444444;
|
36
|
+
border-color: #555555;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
&.dark-theme {
|
42
|
+
background-color: black;
|
43
|
+
box-shadow: none;
|
44
|
+
|
45
|
+
.form-title {
|
46
|
+
color: white;
|
47
|
+
}
|
48
|
+
|
49
|
+
.form-actions {
|
50
|
+
.export-button {
|
51
|
+
background-color: black;
|
52
|
+
border-color: black;
|
53
|
+
color: white;
|
54
|
+
|
55
|
+
&:hover {
|
56
|
+
background-color: black;
|
57
|
+
border-color: black;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
.import-button {
|
62
|
+
.import-label {
|
63
|
+
background-color: black;
|
64
|
+
border-color: black;
|
65
|
+
color: white;
|
66
|
+
|
67
|
+
&:hover {
|
68
|
+
background-color: black;
|
69
|
+
border-color: black;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
package/src/styles/index.scss
CHANGED
package/src/types/AnyClass.ts
CHANGED
package/src/utils/decerators.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
export type DecoratorMap<T, K> = (
|
2
|
-
|
2
|
+
prop: { target: object; propertyKey: string | symbol },
|
3
3
|
options: T
|
4
4
|
) => K;
|
5
|
+
|
5
6
|
export function createDecorator<T extends { name?: string }, K>(
|
6
|
-
key:
|
7
|
+
key: symbol,
|
7
8
|
options?: T,
|
8
9
|
map?: DecoratorMap<T, K>
|
9
10
|
): PropertyDecorator {
|