proje-react-panel 1.0.17 → 1.1.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.
Files changed (113) hide show
  1. package/dist/components/Counter.d.ts +9 -0
  2. package/dist/components/DetailsPage.d.ts +7 -0
  3. package/dist/components/ErrorBoundary.d.ts +16 -0
  4. package/dist/components/ErrorComponent.d.ts +4 -0
  5. package/dist/components/LoadingScreen.d.ts +2 -0
  6. package/dist/components/Login.d.ts +13 -0
  7. package/dist/components/Panel.d.ts +1 -3
  8. package/dist/components/components/Checkbox.d.ts +3 -2
  9. package/dist/components/components/FormField.d.ts +5 -1
  10. package/dist/components/components/InnerForm.d.ts +8 -3
  11. package/dist/components/components/Label.d.ts +3 -2
  12. package/dist/components/components/Uploader.d.ts +8 -0
  13. package/dist/components/components/index.d.ts +1 -1
  14. package/dist/components/components/list/ListPage.d.ts +1 -1
  15. package/dist/components/form/Checkbox.d.ts +7 -0
  16. package/dist/components/form/FormField.d.ts +17 -0
  17. package/dist/components/form/FormPage.d.ts +6 -0
  18. package/dist/components/form/InnerForm.d.ts +10 -0
  19. package/dist/components/form/Label.d.ts +9 -0
  20. package/dist/components/form/Uploader.d.ts +8 -0
  21. package/dist/components/layout/Layout.d.ts +3 -4
  22. package/dist/components/layout/SideBar.d.ts +2 -3
  23. package/dist/components/list/CellField.d.ts +9 -0
  24. package/dist/components/list/Datagrid.d.ts +6 -8
  25. package/dist/components/list/FilterPopup.d.ts +7 -5
  26. package/dist/components/list/ListHeader.d.ts +11 -0
  27. package/dist/components/list/ListPage.d.ts +6 -0
  28. package/dist/components/pages/FormPage.d.ts +8 -2
  29. package/dist/decorators/details/Details.d.ts +11 -0
  30. package/dist/decorators/details/DetailsItem.d.ts +11 -0
  31. package/dist/decorators/details/getDetailsPageMeta.d.ts +8 -0
  32. package/dist/decorators/form/Form.d.ts +21 -5
  33. package/dist/decorators/form/Input.d.ts +7 -3
  34. package/dist/decorators/form/getFormPageMeta.d.ts +10 -0
  35. package/dist/decorators/list/Cell.d.ts +13 -1
  36. package/dist/decorators/list/List.d.ts +18 -1
  37. package/dist/decorators/list/cells/ImageCell.d.ts +9 -0
  38. package/dist/decorators/list/getListPageMeta.d.ts +8 -0
  39. package/dist/index.cjs.js +1 -1
  40. package/dist/index.d.ts +12 -17
  41. package/dist/index.esm.js +1 -1
  42. package/dist/initPanel.d.ts +1 -1
  43. package/dist/store/store.d.ts +0 -3
  44. package/dist/types/AnyClass.d.ts +2 -1
  45. package/dist/types/getDetailsData.d.ts +1 -0
  46. package/dist/types/initPanelOptions.d.ts +0 -1
  47. package/package.json +1 -1
  48. package/src/assets/icons/svg/check.svg +4 -0
  49. package/src/assets/icons/svg/cross.svg +4 -0
  50. package/src/components/DetailsPage.tsx +55 -0
  51. package/src/components/{components/ErrorComponent.tsx → ErrorComponent.tsx} +1 -1
  52. package/src/components/{pages/Login.tsx → Login.tsx} +2 -2
  53. package/src/components/Panel.tsx +4 -5
  54. package/src/components/form/Checkbox.tsx +21 -0
  55. package/src/components/{components → form}/FormField.tsx +30 -22
  56. package/src/components/form/FormPage.tsx +32 -0
  57. package/src/components/form/InnerForm.tsx +85 -0
  58. package/src/components/form/Label.tsx +21 -0
  59. package/src/components/form/Uploader.tsx +66 -0
  60. package/src/components/layout/Layout.tsx +29 -32
  61. package/src/components/layout/SideBar.tsx +4 -13
  62. package/src/components/list/CellField.tsx +63 -0
  63. package/src/components/list/Datagrid.tsx +106 -0
  64. package/src/components/{components/list → list}/FilterPopup.tsx +13 -9
  65. package/src/components/list/ListHeader.tsx +47 -0
  66. package/src/components/{components/list → list}/ListPage.tsx +20 -82
  67. package/src/decorators/details/Details.ts +31 -0
  68. package/src/decorators/details/DetailsItem.ts +40 -0
  69. package/src/decorators/details/getDetailsPageMeta.ts +15 -0
  70. package/src/decorators/form/Form.ts +37 -12
  71. package/src/decorators/form/Input.ts +11 -4
  72. package/src/decorators/form/getFormPageMeta.ts +21 -0
  73. package/src/decorators/list/Cell.ts +41 -1
  74. package/src/decorators/list/List.ts +30 -6
  75. package/src/decorators/list/cells/ImageCell.ts +17 -0
  76. package/src/decorators/list/getListPageMeta.ts +16 -0
  77. package/src/index.ts +32 -24
  78. package/src/initPanel.ts +1 -4
  79. package/src/store/store.ts +0 -5
  80. package/src/styles/components/checkbox.scss +42 -0
  81. package/src/styles/components/uploader.scss +86 -0
  82. package/src/styles/details.scss +62 -0
  83. package/src/styles/form.scss +9 -11
  84. package/src/styles/index.scss +26 -12
  85. package/src/styles/list.scss +3 -1
  86. package/src/types/AnyClass.ts +2 -1
  87. package/src/types/initPanelOptions.ts +1 -3
  88. package/src/components/components/Checkbox.tsx +0 -9
  89. package/src/components/components/ImageUploader.tsx +0 -301
  90. package/src/components/components/InnerForm.tsx +0 -74
  91. package/src/components/components/Label.tsx +0 -15
  92. package/src/components/components/index.ts +0 -8
  93. package/src/components/components/list/Datagrid.tsx +0 -127
  94. package/src/components/pages/ControllerDetails.tsx +0 -37
  95. package/src/components/pages/FormPage.tsx +0 -34
  96. package/src/decorators/Crud.ts +0 -20
  97. package/src/decorators/form/FormOptions.ts +0 -8
  98. package/src/decorators/form/getFormFields.ts +0 -13
  99. package/src/decorators/list/GetCellFields.ts +0 -13
  100. package/src/decorators/list/ImageCell.ts +0 -13
  101. package/src/decorators/list/ListData.ts +0 -7
  102. package/src/decorators/list/getListFields.ts +0 -10
  103. package/src/styles/image-uploader.scss +0 -94
  104. package/src/types/Screen.ts +0 -4
  105. package/src/types/ScreenCreatorData.ts +0 -14
  106. package/src/utils/createScreens.ts +0 -5
  107. package/src/utils/getFields.ts +0 -22
  108. /package/src/components/{components/Counter.tsx → Counter.tsx} +0 -0
  109. /package/src/components/{components/ErrorBoundary.tsx → ErrorBoundary.tsx} +0 -0
  110. /package/src/components/{components/LoadingScreen.tsx → LoadingScreen.tsx} +0 -0
  111. /package/src/components/{components/list → list}/EmptyList.tsx +0 -0
  112. /package/src/components/{components/list → list}/Pagination.tsx +0 -0
  113. /package/src/components/{components/list → list}/index.ts +0 -0
@@ -0,0 +1,47 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Link } from 'react-router';
3
+ import { AnyClass } from '../../types/AnyClass';
4
+ import CreateIcon from '../../assets/icons/svg/create.svg';
5
+ import FilterIcon from '../../assets/icons/svg/filter.svg';
6
+ import { ListPageMeta } from '../../decorators/list/getListPageMeta';
7
+
8
+ interface ListHeaderProps<T extends AnyClass> {
9
+ listPageMeta: ListPageMeta<T>;
10
+ filtered: boolean;
11
+ onFilterClick: () => void;
12
+ customHeader?: React.ReactNode;
13
+ }
14
+
15
+ export function ListHeader<T extends AnyClass>({
16
+ listPageMeta,
17
+ filtered,
18
+ onFilterClick,
19
+ customHeader,
20
+ }: ListHeaderProps<T>) {
21
+ const fields = useMemo(
22
+ () => listPageMeta.cells.filter(cell => !!cell.filter),
23
+ [listPageMeta.cells]
24
+ );
25
+
26
+ const header = listPageMeta.class.headers;
27
+ return (
28
+ <div className="list-header">
29
+ <div className="header-title">{header?.title || 'List'}</div>
30
+ {customHeader && <div className="header-custom">{customHeader}</div>}
31
+ <div className="header-actions">
32
+ {!!fields.length && (
33
+ <button onClick={onFilterClick} className="filter-button">
34
+ <FilterIcon className={`icon icon-filter ${filtered ? 'active' : ''}`} />
35
+ Filter
36
+ </button>
37
+ )}
38
+ {header?.create && (
39
+ <Link to={header.create.path} className="create-button">
40
+ <CreateIcon className="icon icon-create" />
41
+ {header.create.label}
42
+ </Link>
43
+ )}
44
+ </div>
45
+ </div>
46
+ );
47
+ }
@@ -1,85 +1,30 @@
1
1
  import React, { useMemo, useCallback, useEffect, useState } from 'react';
2
- import { Link, useParams, useNavigate } from 'react-router';
2
+ import { useParams, useNavigate } from 'react-router';
3
3
  import { Datagrid } from './Datagrid';
4
4
  import { ErrorComponent } from '../ErrorComponent';
5
5
  import { LoadingScreen } from '../LoadingScreen';
6
- import { AnyClass } from '../../../types/AnyClass';
7
- import { getListFields } from '../../../decorators/list/getListFields';
6
+ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
8
7
  import { Pagination } from './Pagination';
9
- import { ListData } from '../../../decorators/list/ListData';
10
- import CreateIcon from '../../../assets/icons/svg/create.svg';
11
- import FilterIcon from '../../../assets/icons/svg/filter.svg';
8
+ import { ListHeader } from './ListHeader';
12
9
  import { FilterPopup } from './FilterPopup';
13
-
14
- export interface GetDataParams {
15
- page?: number;
16
- limit?: number;
17
- filters?: Record<string, any>;
18
- }
19
-
20
- export interface PaginatedResponse<T> {
21
- data: T[];
22
- total: number;
23
- page: number;
24
- limit: number;
25
- }
26
-
27
- export type GetDataForList<T> = (params: GetDataParams) => Promise<PaginatedResponse<T>>;
28
-
29
- const ListHeader = <T extends AnyClass>({
30
- listData,
31
- filtered,
32
- onFilterClick,
33
- customHeader,
34
- }: {
35
- listData: ListData<T>;
36
- filtered: boolean;
37
- onFilterClick: () => void;
38
- customHeader?: React.ReactNode;
39
- }) => {
40
- const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
41
-
42
- const header = listData.list?.headers;
43
- return (
44
- <div className="list-header">
45
- <div className="header-title">{header?.title || 'List'}</div>
46
- {customHeader && <div className="header-custom">{customHeader}</div>}
47
- <div className="header-actions">
48
- {!!fields.length && (
49
- <button onClick={onFilterClick} className="filter-button">
50
- <FilterIcon className={`icon icon-filter ${filtered ? 'active' : ''}`} />
51
- Filter
52
- </button>
53
- )}
54
- {header?.create && (
55
- <Link to={header.create.path} className="create-button">
56
- <CreateIcon className="icon icon-create" />
57
- {header.create.label}
58
- </Link>
59
- )}
60
- </div>
61
- </div>
62
- );
63
- };
10
+ import { getListPageMeta } from '../../decorators/list/getListPageMeta';
64
11
 
65
12
  export function ListPage<T extends AnyClass>({
66
13
  model,
67
- getData,
68
- onRemoveItem,
69
14
  customHeader,
70
15
  }: {
71
- model: T;
72
- getData: GetDataForList<T>;
16
+ model: AnyClassConstructor<T>;
73
17
  customHeader?: React.ReactNode;
74
- onRemoveItem?: (item: T) => Promise<void>;
75
18
  }) {
19
+ const listPageMeta = useMemo(() => getListPageMeta(model), [model]);
20
+
76
21
  const [loading, setLoading] = useState(true);
77
- const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
78
22
  const [data, setData] = useState<any>(null);
79
23
  const [error, setError] = useState<unknown>(null);
24
+
25
+ const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
80
26
  const [isFilterOpen, setIsFilterOpen] = useState(false);
81
27
  const [activeFilters, setActiveFilters] = useState<Record<string, string>>();
82
- const listData = useMemo(() => getListFields(model), [model]);
83
28
  const params = useParams();
84
29
  const navigate = useNavigate();
85
30
 
@@ -87,7 +32,10 @@ export function ListPage<T extends AnyClass>({
87
32
  async (page: number, filters?: Record<string, string>) => {
88
33
  setLoading(true);
89
34
  try {
90
- const result = await getData({ page, filters: filters ?? activeFilters ?? {} });
35
+ const result = await listPageMeta.class.getData({
36
+ page,
37
+ filters: filters ?? activeFilters ?? {},
38
+ });
91
39
  setData(result.data);
92
40
  setPagination({
93
41
  total: result.total,
@@ -101,7 +49,7 @@ export function ListPage<T extends AnyClass>({
101
49
  setLoading(false);
102
50
  }
103
51
  },
104
- [getData, activeFilters]
52
+ [activeFilters, listPageMeta.class.getData]
105
53
  );
106
54
 
107
55
  useEffect(() => {
@@ -117,7 +65,7 @@ export function ListPage<T extends AnyClass>({
117
65
  if (activeFilters) {
118
66
  fetchData(parseInt(params.page as string) || 1, activeFilters);
119
67
  }
120
- }, [fetchData, params.page, activeFilters]);
68
+ }, [fetchData, params.page, activeFilters, listPageMeta.class.getData]);
121
69
 
122
70
  const handleFilterApply = (filters: Record<string, any>) => {
123
71
  setActiveFilters(filters);
@@ -141,26 +89,16 @@ export function ListPage<T extends AnyClass>({
141
89
  return (
142
90
  <div className="list">
143
91
  <ListHeader
144
- listData={listData}
92
+ listPageMeta={listPageMeta}
145
93
  filtered={!!(activeFilters && !!Object.keys(activeFilters).length)}
146
94
  onFilterClick={() => setIsFilterOpen(true)}
147
95
  customHeader={customHeader}
148
96
  />
149
97
  <Datagrid
150
- listData={listData}
98
+ listPageMeta={listPageMeta}
151
99
  data={data}
152
- onRemoveItem={async (item: T) => {
153
- if (onRemoveItem) {
154
- alert({
155
- title: 'Are you sure you want to delete this item?',
156
- message: 'This action cannot be undone.',
157
- onConfirm: async () => {
158
- await onRemoveItem(item);
159
- //setData(data.filter((d: T) => d.id !== item.id));
160
- await fetchData(pagination.page);
161
- },
162
- });
163
- }
100
+ onRemoveItem={async () => {
101
+ await fetchData(pagination.page);
164
102
  }}
165
103
  />
166
104
  <div className="list-footer">
@@ -171,7 +109,7 @@ export function ListPage<T extends AnyClass>({
171
109
  activeFilters={activeFilters}
172
110
  onClose={() => setIsFilterOpen(false)}
173
111
  onApplyFilters={handleFilterApply}
174
- listData={listData}
112
+ listPageMeta={listPageMeta}
175
113
  />
176
114
  </div>
177
115
  );
@@ -0,0 +1,31 @@
1
+ import 'reflect-metadata';
2
+ import { AnyClass } from '../../types/AnyClass';
3
+
4
+ const DETAILS_METADATA_KEY = 'DetailsMetaData';
5
+ export type GetDetailsDataFN<T> = (param: Record<string, string>) => Promise<T>;
6
+
7
+ interface DetailsOptions<T extends AnyClass> {
8
+ getDetailsData: GetDetailsDataFN<T>;
9
+ }
10
+
11
+ export interface DetailsConfiguration<T extends AnyClass> extends DetailsOptions<T> {}
12
+
13
+ export function Details<T extends AnyClass>(options?: DetailsOptions<T>): ClassDecorator {
14
+ return (target: Function) => {
15
+ if (options) {
16
+ Reflect.defineMetadata(DETAILS_METADATA_KEY, options, target);
17
+ }
18
+ };
19
+ }
20
+
21
+ export function getDetailsConfiguration<T extends AnyClass>(
22
+ entityClass: T
23
+ ): DetailsConfiguration<T> {
24
+ const detailsConfiguration = Reflect.getMetadata(DETAILS_METADATA_KEY, entityClass);
25
+ if (!detailsConfiguration) {
26
+ throw new Error('Details decerator should be used on class');
27
+ }
28
+ return {
29
+ ...detailsConfiguration,
30
+ };
31
+ }
@@ -0,0 +1,40 @@
1
+ import 'reflect-metadata';
2
+ import { AnyClass } from '../../types/AnyClass';
3
+
4
+ const DETAILS_ITEM_KEY = Symbol('detailsItem');
5
+
6
+ interface DetailsItemOptions {
7
+ //NOTE: all optional to support autoFields merging
8
+ name?: string;
9
+ }
10
+ export interface DetailsItemConfiguration extends DetailsItemOptions {
11
+ name: string;
12
+ }
13
+
14
+ export function DetailsItem(options?: DetailsItemOptions): PropertyDecorator {
15
+ return (target, propertyKey) => {
16
+ const existingInputs: string[] = Reflect.getMetadata(DETAILS_ITEM_KEY, target) || [];
17
+ Reflect.defineMetadata(DETAILS_ITEM_KEY, [...existingInputs, propertyKey.toString()], target);
18
+
19
+ if (options) {
20
+ const keyString = `${DETAILS_ITEM_KEY.toString()}:${propertyKey.toString()}:options`;
21
+ Reflect.defineMetadata(keyString, options, target);
22
+ }
23
+ };
24
+ }
25
+
26
+ export function getDetailsItemFields<T extends AnyClass>(
27
+ entityClass: T
28
+ ): DetailsItemConfiguration[] {
29
+ const prototype = (entityClass as any).prototype;
30
+ const inputFields: string[] = Reflect.getMetadata(DETAILS_ITEM_KEY, prototype) || [];
31
+
32
+ return inputFields.map(field => {
33
+ const fields: DetailsItemOptions =
34
+ Reflect.getMetadata(`${DETAILS_ITEM_KEY.toString()}:${field}:options`, prototype) || {};
35
+ return {
36
+ ...fields,
37
+ name: fields.name || field,
38
+ };
39
+ });
40
+ }
@@ -0,0 +1,15 @@
1
+ import { AnyClass } from '../../types/AnyClass';
2
+ import { DetailsItemConfiguration, getDetailsItemFields } from './DetailsItem';
3
+ import { DetailsConfiguration, getDetailsConfiguration } from './Details';
4
+
5
+ export interface DetailsPageMeta<T extends AnyClass> {
6
+ class: DetailsConfiguration<T>;
7
+ items: DetailsItemConfiguration[];
8
+ }
9
+
10
+ export function getDetailsPageMeta<T extends AnyClass>(entityClass: T): DetailsPageMeta<T> {
11
+ return {
12
+ class: getDetailsConfiguration(entityClass),
13
+ items: getDetailsItemFields<T>(entityClass),
14
+ };
15
+ }
@@ -1,18 +1,43 @@
1
- import "reflect-metadata";
2
- import { AnyClass } from "../../types/AnyClass";
1
+ import 'reflect-metadata';
2
+ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
3
+ import { GetDetailsDataFN } from '../details/Details';
4
+ import { InputConfiguration } from './Input';
3
5
 
4
- const FORM_METADATA_KEY = "FormMetadata"; // More descriptive name indicating it's a metadata key
6
+ const DETAILS_METADATA_KEY = 'DetailsMetaData';
7
+ export type OnSubmitFN<T> = (data: T | FormData) => Promise<T | FormData>;
5
8
 
6
- export interface FormConfiguration {} // Better describes that this is configuration/options for the form
9
+ interface FormOptions<T extends AnyClass> {
10
+ onSubmit: OnSubmitFN<T>;
11
+ getDetailsData?: GetDetailsDataFN<T>;
12
+ onSelectPreloader?: (
13
+ inputOptions: InputConfiguration
14
+ ) => Promise<{ label: string; value: string }[]>;
15
+ redirectBackOnSuccess?: boolean;
16
+ type?: 'json' | 'formData';
17
+ }
18
+
19
+ export interface FormConfiguration<T extends AnyClass> extends FormOptions<T> {
20
+ redirectBackOnSuccess: boolean;
21
+ type: 'json' | 'formData';
22
+ }
7
23
 
8
- export function FormDecorator(options?: FormConfiguration): ClassDecorator {
9
- return (target: Function) => {
10
- if (options) {
11
- Reflect.defineMetadata(FORM_METADATA_KEY, options, target);
12
- }
13
- };
24
+ export function Form<T extends AnyClass>(options?: FormOptions<T>): ClassDecorator {
25
+ return (target: Function) => {
26
+ if (options) {
27
+ Reflect.defineMetadata(DETAILS_METADATA_KEY, options, target);
28
+ }
29
+ };
14
30
  }
15
31
 
16
- export function getFormConfiguration(entityClass: AnyClass): FormConfiguration | undefined {
17
- return Reflect.getMetadata(FORM_METADATA_KEY, entityClass);
32
+ export function getFormConfiguration<T extends AnyClass, K extends AnyClassConstructor<T>>(
33
+ entityClass: K
34
+ ): FormConfiguration<T> {
35
+ const formConfiguration = Reflect.getMetadata(DETAILS_METADATA_KEY, entityClass as Object);
36
+ if (!formConfiguration) {
37
+ throw new Error('Form decerator should be used on class');
38
+ }
39
+ return {
40
+ ...formConfiguration,
41
+ type: formConfiguration.type ?? 'json',
42
+ };
18
43
  }
@@ -1,5 +1,5 @@
1
1
  import 'reflect-metadata';
2
- import { AnyClass } from '../../types/AnyClass';
2
+ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
3
3
 
4
4
  const INPUT_KEY = Symbol('input');
5
5
 
@@ -15,7 +15,12 @@ export interface InputOptions {
15
15
  placeholder?: string;
16
16
  cancelPasswordValidationOnEdit?: boolean;
17
17
  options?: { value: string; label: string }[];
18
- nestedFields?: InputOptions[];
18
+ optionsPreload?: boolean;
19
+ nestedFields?: InputConfiguration[];
20
+ }
21
+
22
+ export interface InputConfiguration extends InputOptions {
23
+ name: string;
19
24
  }
20
25
 
21
26
  export function Input(options?: InputOptions): PropertyDecorator {
@@ -30,8 +35,10 @@ export function Input(options?: InputOptions): PropertyDecorator {
30
35
  };
31
36
  }
32
37
 
33
- export function getInputFields<T extends AnyClass>(entityClass: T): InputOptions[] {
34
- const prototype = entityClass.prototype;
38
+ export function getInputFields<T extends AnyClass>(
39
+ entityClass: AnyClassConstructor<T>
40
+ ): InputConfiguration[] {
41
+ const prototype = (entityClass as any).prototype;
35
42
  const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
36
43
  return inputFields.map(field => {
37
44
  const fields = Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
@@ -0,0 +1,21 @@
1
+ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
2
+ import { getInputFields, InputConfiguration } from './Input';
3
+ import { FormConfiguration, getFormConfiguration } from './Form';
4
+ import { classValidatorResolver } from '@hookform/resolvers/class-validator';
5
+ import { Resolver } from 'react-hook-form';
6
+
7
+ export interface FormPageMeta<T extends AnyClass> {
8
+ resolver: Resolver<T>;
9
+ class: FormConfiguration<T>;
10
+ inputs: InputConfiguration[];
11
+ }
12
+
13
+ export function getFormPageMeta<T extends AnyClass, K extends AnyClassConstructor<T>>(
14
+ entityClass: K
15
+ ): FormPageMeta<T> {
16
+ return {
17
+ resolver: classValidatorResolver(entityClass),
18
+ class: getFormConfiguration(entityClass),
19
+ inputs: getInputFields(entityClass),
20
+ };
21
+ }
@@ -1,4 +1,5 @@
1
1
  import 'reflect-metadata';
2
+ import { AnyClass } from '../../types/AnyClass';
2
3
 
3
4
  export const CELL_KEY = Symbol('cell');
4
5
 
@@ -11,15 +12,39 @@ export interface StaticSelectFilter extends Filter {
11
12
  options: { value: string; label: string }[];
12
13
  }
13
14
 
15
+ export type CellTypes = 'string' | 'date' | 'number' | 'boolean' | 'uuid';
16
+ export type ExtendedCellTypes = CellTypes | 'image';
17
+
14
18
  export interface CellOptions {
15
19
  name?: string;
16
20
  title?: string;
17
- type?: 'string' | 'date' | 'image' | 'number';
21
+ type?: CellTypes;
18
22
  placeHolder?: string;
19
23
  filter?: Filter | StaticSelectFilter;
20
24
  }
25
+ export interface ExtendedCellOptions extends Omit<CellOptions, 'type'> {
26
+ type?: ExtendedCellTypes;
27
+ }
28
+
29
+ export interface CellConfiguration extends Omit<CellOptions, 'type'> {
30
+ name: string;
31
+ type: ExtendedCellTypes;
32
+ }
21
33
 
22
34
  export function Cell(options?: CellOptions): PropertyDecorator {
35
+ //TODO: reduce all similar code
36
+ return (target, propertyKey) => {
37
+ const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
38
+ Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
39
+
40
+ if (options) {
41
+ const keyString = `${CELL_KEY.toString()}:${propertyKey.toString()}:options`;
42
+ Reflect.defineMetadata(keyString, options, target);
43
+ }
44
+ };
45
+ }
46
+
47
+ export function ExtendedCell(options?: ExtendedCellOptions): PropertyDecorator {
23
48
  return (target, propertyKey) => {
24
49
  const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
25
50
  Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
@@ -30,3 +55,18 @@ export function Cell(options?: CellOptions): PropertyDecorator {
30
55
  }
31
56
  };
32
57
  }
58
+
59
+ export function getCellFields<T extends AnyClass>(entityClass: T): CellConfiguration[] {
60
+ const prototype = (entityClass as any).prototype;
61
+ const inputFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
62
+
63
+ return inputFields.map(field => {
64
+ const fields: CellOptions =
65
+ Reflect.getMetadata(`${CELL_KEY.toString()}:${field}:options`, prototype) || {};
66
+ return {
67
+ ...fields,
68
+ name: fields.name || field,
69
+ type: fields.type as ExtendedCellTypes,
70
+ };
71
+ });
72
+ }
@@ -1,6 +1,22 @@
1
1
  import 'reflect-metadata';
2
+ import { AnyClass } from '../../types/AnyClass';
2
3
 
3
- const LIST_KEY = 'List';
4
+ const LIST_METADATA_KEY = 'ListMetaData';
5
+
6
+ export interface GetDataParams {
7
+ page?: number;
8
+ limit?: number;
9
+ filters?: Record<string, any>;
10
+ }
11
+
12
+ export interface PaginatedResponse<T> {
13
+ data: T[];
14
+ total: number;
15
+ page: number;
16
+ limit: number;
17
+ }
18
+
19
+ export type GetDataForList<T> = (params: GetDataParams) => Promise<PaginatedResponse<T>>;
4
20
 
5
21
  export interface ListHeaderOptions {
6
22
  title?: string;
@@ -10,23 +26,31 @@ export interface ListHeaderOptions {
10
26
  export interface ListCellOptions<T> {
11
27
  details?: { path: string; label: string };
12
28
  edit?: { path: string; label: string };
13
- delete?: { label: string };
29
+ delete?: { label: string; onRemoveItem?: (item: T) => Promise<void> };
14
30
  }
15
31
 
16
32
  export interface ListOptions<T> {
33
+ getData: GetDataForList<T>;
17
34
  headers?: ListHeaderOptions;
18
35
  cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
19
36
  }
20
37
 
38
+ export interface ListConfiguration<T> extends ListOptions<T> {}
39
+
21
40
  export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
22
41
  return (target: Function) => {
23
42
  if (options) {
24
- Reflect.defineMetadata(LIST_KEY, options, target);
43
+ Reflect.defineMetadata(LIST_METADATA_KEY, options, target);
25
44
  }
26
45
  };
27
46
  }
28
47
 
29
- export function getClassListData<T>(entityClass: T): ListOptions<T> | undefined {
30
- //TODO: try to remove any
31
- return Reflect.getMetadata(LIST_KEY, entityClass as any);
48
+ export function getListConfiguration<T extends AnyClass>(entityClass: T): ListConfiguration<T> {
49
+ const listConfiguration = Reflect.getMetadata(LIST_METADATA_KEY, entityClass);
50
+ if (!listConfiguration) {
51
+ throw new Error('List decerator should be used on class');
52
+ }
53
+ return {
54
+ ...listConfiguration,
55
+ };
32
56
  }
@@ -0,0 +1,17 @@
1
+ import 'reflect-metadata';
2
+ import { Cell, CellConfiguration, CellOptions, ExtendedCell } from '../Cell';
3
+
4
+ export interface ImageCellOptions extends CellOptions {
5
+ baseUrl: string;
6
+ }
7
+
8
+ export interface ImageCellConfiguration extends CellConfiguration {
9
+ type: 'image';
10
+ }
11
+
12
+ export function ImageCell(options?: ImageCellOptions): PropertyDecorator {
13
+ return ExtendedCell({
14
+ ...options,
15
+ type: 'image',
16
+ });
17
+ }
@@ -0,0 +1,16 @@
1
+ import { AnyClass } from '../../types/AnyClass';
2
+ import { getDetailsItemFields } from '../details/DetailsItem';
3
+ import { CellConfiguration, getCellFields } from './Cell';
4
+ import { getListConfiguration, ListConfiguration } from './List';
5
+
6
+ export interface ListPageMeta<T extends AnyClass> {
7
+ class: ListConfiguration<T>;
8
+ cells: CellConfiguration[];
9
+ }
10
+
11
+ export function getListPageMeta<T extends AnyClass>(entityClass: T): ListPageMeta<T> {
12
+ return {
13
+ class: getListConfiguration(entityClass),
14
+ cells: getCellFields<T>(entityClass),
15
+ };
16
+ }
package/src/index.ts CHANGED
@@ -1,28 +1,36 @@
1
- export type { OnSubmitFN, GetDetailsDataFN } from './components/pages/FormPage';
2
- export type { InitPanelOptions } from './types/initPanelOptions';
3
- export type { ScreenCreatorData } from './types/ScreenCreatorData';
4
- export type { OnLogin } from './components/pages/Login';
5
- export type { AnyClass } from './types/AnyClass';
6
- export type {
7
- GetDataForList,
8
- PaginatedResponse,
9
- GetDataParams,
10
- } from './components/components/list/ListPage';
1
+ //DETAILS
2
+ export { DetailsPage } from './components/DetailsPage';
3
+ export { Details, type GetDetailsDataFN } from './decorators/details/Details';
4
+ export { DetailsItem } from './decorators/details/DetailsItem';
11
5
 
12
- export { ListPage } from './components/components/list/ListPage';
13
- export { FormPage } from './components/pages/FormPage';
14
- export type { FormPageProps } from './components/pages/FormPage';
15
- export { Login } from './components/pages/Login';
16
- export { Layout } from './components/layout/Layout';
17
- export { Panel } from './components/Panel';
18
- export { Counter } from './components/components/Counter';
19
-
20
- //TODO: decerator index.ts ayır
21
- export { List } from './decorators/list/List';
22
- export { ImageCell } from './decorators/list/ImageCell';
23
- export { Crud } from './decorators/Crud';
6
+ //LIST
7
+ export { ListPage } from './components/list/ListPage';
8
+ export {
9
+ List,
10
+ type GetDataForList,
11
+ type PaginatedResponse,
12
+ type GetDataParams,
13
+ } from './decorators/list/List';
14
+ export { ImageCell } from './decorators/list/cells/ImageCell';
24
15
  export { Cell } from './decorators/list/Cell';
16
+
17
+ //FORM
18
+ export { FormPage } from './components/form/FormPage';
19
+ export { Form, type OnSubmitFN } from './decorators/form/Form';
25
20
  export { Input } from './decorators/form/Input';
26
- // Export page components
27
- export { getFormFields } from './decorators/form/getFormFields';
21
+ //for nested form fields
28
22
  export { getInputFields } from './decorators/form/Input';
23
+
24
+ //PANEL
25
+ export { Panel } from './components/Panel';
26
+
27
+ //DASHBOARD
28
+ export { Counter } from './components/Counter';
29
+
30
+ //LAYOUT
31
+ export { Layout } from './components/layout';
32
+
33
+ //AUTH
34
+ export { Login } from './components/Login';
35
+
36
+ //UTILS
package/src/initPanel.ts CHANGED
@@ -1,6 +1,3 @@
1
1
  import { InitPanelOptions } from './types/initPanelOptions';
2
- import { useAppStore } from './store/store';
3
2
 
4
- export function initPanel({ screenPaths }: InitPanelOptions) {
5
- useAppStore.setState({ screenPaths });
6
- }
3
+ export function initPanel({}: InitPanelOptions) {}
@@ -1,7 +1,6 @@
1
1
  import { createJSONStorage, persist } from 'zustand/middleware';
2
2
  import { createWithEqualityFn } from 'zustand/traditional';
3
3
  import { shallow } from 'zustand/vanilla/shallow';
4
- import { ScreenCreatorData } from '../types/ScreenCreatorData';
5
4
 
6
5
  interface User {
7
6
  username: string;
@@ -9,16 +8,12 @@ interface User {
9
8
 
10
9
  interface AppState {
11
10
  user: User | null;
12
- screens: Record<string, ScreenCreatorData> | null;
13
- screenPaths: Record<string, string>;
14
11
  }
15
12
 
16
13
  export const useAppStore = createWithEqualityFn<AppState>()(
17
14
  persist(
18
15
  _ => ({
19
- screens: null,
20
16
  user: null,
21
- screenPaths: {},
22
17
  }),
23
18
  {
24
19
  name: 'app-store-1',