proje-react-panel 1.1.2 → 1.1.4

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 (41) hide show
  1. package/dist/components/ErrorComponent.d.ts +3 -2
  2. package/dist/components/LoadingScreen.d.ts +3 -1
  3. package/dist/components/list/CellField.d.ts +3 -2
  4. package/dist/components/list/cells/BooleanCell.d.ts +6 -0
  5. package/dist/components/list/cells/DateCell.d.ts +6 -0
  6. package/dist/components/list/cells/DefaultCell.d.ts +8 -0
  7. package/dist/components/list/cells/DownloadCell.d.ts +8 -0
  8. package/dist/components/list/cells/ImageCell.d.ts +8 -0
  9. package/dist/components/list/cells/UUIDCell.d.ts +6 -0
  10. package/dist/decorators/form/Form.d.ts +5 -1
  11. package/dist/decorators/list/Cell.d.ts +4 -6
  12. package/dist/decorators/list/ExtendedCell.d.ts +10 -0
  13. package/dist/decorators/list/cells/DownloadCell.d.ts +9 -0
  14. package/dist/decorators/list/cells/ImageCell.d.ts +2 -2
  15. package/dist/index.cjs.js +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.esm.js +1 -1
  18. package/dist/utils/decerators.d.ts +7 -0
  19. package/package.json +2 -1
  20. package/src/components/DetailsPage.tsx +4 -3
  21. package/src/components/ErrorComponent.tsx +36 -33
  22. package/src/components/LoadingScreen.tsx +4 -3
  23. package/src/components/Panel.tsx +7 -3
  24. package/src/components/form/InnerForm.tsx +13 -1
  25. package/src/components/list/CellField.tsx +26 -42
  26. package/src/components/list/Datagrid.tsx +16 -9
  27. package/src/components/list/ListPage.tsx +4 -3
  28. package/src/components/list/cells/BooleanCell.tsx +15 -0
  29. package/src/components/list/cells/DateCell.tsx +21 -0
  30. package/src/components/list/cells/DefaultCell.tsx +11 -0
  31. package/src/components/list/cells/DownloadCell.tsx +28 -0
  32. package/src/components/list/cells/ImageCell.tsx +22 -0
  33. package/src/components/list/cells/UUIDCell.tsx +11 -0
  34. package/src/decorators/form/Form.ts +5 -1
  35. package/src/decorators/list/Cell.ts +15 -33
  36. package/src/decorators/list/ExtendedCell.ts +32 -0
  37. package/src/decorators/list/cells/DownloadCell.ts +18 -0
  38. package/src/decorators/list/cells/ImageCell.ts +6 -5
  39. package/src/index.ts +1 -0
  40. package/src/styles/list.scss +1 -1
  41. package/src/utils/decerators.ts +24 -0
@@ -1,7 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
- import { initPanel } from '../initPanel';
3
- import { InitPanelOptions } from '../types/initPanelOptions';
4
2
  import { ErrorBoundary } from './ErrorBoundary';
3
+ import { ToastContainer } from 'react-toastify';
5
4
 
6
5
  type AppProps = {
7
6
  children: React.ReactNode;
@@ -13,5 +12,10 @@ export function Panel({ children }: AppProps) {
13
12
  initPanel(options);
14
13
  }, [init]);*/
15
14
 
16
- return <ErrorBoundary>{children}</ErrorBoundary>;
15
+ return (
16
+ <ErrorBoundary>
17
+ {children}
18
+ <ToastContainer />
19
+ </ErrorBoundary>
20
+ );
17
21
  }
@@ -5,6 +5,7 @@ import { FormField } from './FormField';
5
5
  import { useNavigate } from 'react-router';
6
6
  import { FormConfiguration } from '../../decorators/form/Form';
7
7
  import { AnyClass } from '../../types/AnyClass';
8
+ import { toast } from 'react-toastify';
8
9
 
9
10
  interface InnerFormProps<T extends AnyClass> {
10
11
  inputs: InputConfiguration[];
@@ -17,6 +18,7 @@ export function InnerForm<T extends AnyClass>({ inputs, formClass }: InnerFormPr
17
18
  const formRef = useRef<HTMLFormElement>(null);
18
19
  const navigate = useNavigate();
19
20
  const form = useFormContext<T>();
21
+ const loadingRef = useRef(false);
20
22
 
21
23
  return (
22
24
  <div className="form-wrapper">
@@ -24,6 +26,8 @@ export function InnerForm<T extends AnyClass>({ inputs, formClass }: InnerFormPr
24
26
  ref={formRef}
25
27
  onSubmit={form.handleSubmit(
26
28
  async (dataForm: T) => {
29
+ if (loadingRef.current) return;
30
+ loadingRef.current = true;
27
31
  try {
28
32
  const data =
29
33
  formClass.type === 'json'
@@ -37,17 +41,25 @@ export function InnerForm<T extends AnyClass>({ inputs, formClass }: InnerFormPr
37
41
  }
38
42
  return formData;
39
43
  })();
40
- await formClass.onSubmit(data);
44
+ const resut = await formClass.onSubmit(data);
45
+ form.reset(resut);
41
46
  setErrorMessage(null);
47
+ toast.success('Form submitted successfully');
42
48
  if (formClass.redirectBackOnSuccess) {
43
49
  navigate(-1);
44
50
  }
51
+ if (formClass.redirectSuccessUrl) {
52
+ navigate(formClass.redirectSuccessUrl);
53
+ }
45
54
  } catch (error: any) {
46
55
  const message =
47
56
  error?.response?.data?.message ||
48
57
  (error instanceof Error ? error.message : 'An error occurred');
58
+ toast.error('Something went wrong');
49
59
  setErrorMessage(message);
50
60
  console.error(error);
61
+ } finally {
62
+ loadingRef.current = false;
51
63
  }
52
64
  },
53
65
  (errors, event) => {
@@ -1,63 +1,47 @@
1
1
  import React from 'react';
2
2
  import { ImageCellOptions } from '../../decorators/list/cells/ImageCell';
3
3
  import { AnyClass } from '../../types/AnyClass';
4
- import { ExtendedCellTypes } from '../../decorators/list/Cell';
5
- import CheckIcon from '../../assets/icons/svg/check.svg';
6
- import CrossIcon from '../../assets/icons/svg/cross.svg';
4
+ import { CellConfiguration, ExtendedCellTypes } from '../../decorators/list/Cell';
5
+ import { BooleanCell } from './cells/BooleanCell';
6
+ import { DateCell } from './cells/DateCell';
7
+ import { ImageCell } from './cells/ImageCell';
8
+ import { UUIDCell } from './cells/UUIDCell';
9
+ import { DefaultCell } from './cells/DefaultCell';
10
+ import { DownloadCell } from './cells/DownloadCell';
7
11
 
8
12
  interface CellFieldProps<T extends AnyClass> {
9
- cellOptions: any; // TODO: Create proper type for cellOptions
13
+ configuration: CellConfiguration;
10
14
  item: T;
11
15
  value: any;
12
16
  }
13
17
 
14
- export function CellField<T extends AnyClass>({ cellOptions, item, value }: CellFieldProps<T>) {
15
- let render = value ?? '-'; // Default value if the field is undefined or null
18
+ export function CellField<T extends AnyClass>({
19
+ configuration,
20
+ item,
21
+ value,
22
+ }: CellFieldProps<T>): React.ReactElement {
23
+ let render;
16
24
 
17
- switch (cellOptions.type as ExtendedCellTypes) {
18
- case 'boolean': {
19
- render = value ? <CheckIcon className="icon icon-true" /> : <CrossIcon className="icon icon-false" />;
25
+ switch (configuration.type) {
26
+ case 'boolean':
27
+ render = <BooleanCell value={value} />;
20
28
  break;
21
- }
22
29
  case 'date':
23
- if (value) {
24
- const date = new Date(value);
25
- render = `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1)
26
- .toString()
27
- .padStart(2, '0')}/${date.getFullYear()} ${date
28
- .getHours()
29
- .toString()
30
- .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
31
- }
30
+ render = <DateCell value={value} />;
32
31
  break;
33
-
34
- case 'image': {
35
- const imageCellOptions = cellOptions as ImageCellOptions;
36
- render = (
37
- <img
38
- width={100}
39
- height={100}
40
- src={imageCellOptions.baseUrl + value}
41
- style={{ objectFit: 'contain' }}
42
- />
43
- );
32
+ case 'image':
33
+ render = <ImageCell value={value} configuration={configuration} />;
44
34
  break;
45
- }
46
35
  case 'uuid':
47
- if (value && typeof value === 'string' && value.length >= 6) {
48
- render = `${value.slice(0, 3)}...${value.slice(-3)}`;
49
- }
36
+ render = <UUIDCell value={value} />;
37
+ break;
38
+ case 'download':
39
+ render = <DownloadCell value={value} configuration={configuration} />;
50
40
  break;
51
41
  default:
52
- render = value ? value.toString() : (cellOptions?.placeHolder ?? '-');
42
+ render = <DefaultCell value={value} configuration={configuration} />;
53
43
  break;
54
44
  }
55
45
 
56
- /*
57
- if (cellOptions.linkTo) {
58
- render = <Link to={cellOptions.linkTo(item)}>{formattedValue}</Link>;
59
- }
60
- */
61
-
62
- return <td key={cellOptions.name}>{render}</td>;
46
+ return <td key={configuration.name}>{render}</td>;
63
47
  }
@@ -5,9 +5,9 @@ import SearchIcon from '../../assets/icons/svg/search.svg';
5
5
  import PencilIcon from '../../assets/icons/svg/pencil.svg';
6
6
  import TrashIcon from '../../assets/icons/svg/trash.svg';
7
7
  import { ListPageMeta } from '../../decorators/list/getListPageMeta';
8
- import { ImageCellOptions } from '../../decorators/list/cells/ImageCell';
9
8
  import { AnyClass } from '../../types/AnyClass';
10
9
  import { CellField } from './CellField';
10
+ import { CellConfiguration } from '../../decorators/list/Cell';
11
11
 
12
12
  interface DatagridProps<T extends AnyClass> {
13
13
  data: T[];
@@ -52,13 +52,12 @@ export function Datagrid<T extends AnyClass>({
52
52
  : null;
53
53
  return (
54
54
  <tr key={index}>
55
- {cells.map(cellOptions => {
56
- // @ts-ignore
57
- const value = item[cellOptions.name];
55
+ {cells.map((configuration: CellConfiguration) => {
56
+ const value = item[configuration.name];
58
57
  return (
59
58
  <CellField
60
- key={cellOptions.name}
61
- cellOptions={cellOptions}
59
+ key={configuration.name}
60
+ configuration={configuration}
62
61
  item={item}
63
62
  value={value}
64
63
  />
@@ -84,9 +83,17 @@ export function Datagrid<T extends AnyClass>({
84
83
  <td>
85
84
  <a
86
85
  onClick={() => {
87
- listCells.delete?.onRemoveItem?.(item).then(() => {
88
- onRemoveItem?.(item);
89
- });
86
+ listCells.delete
87
+ ?.onRemoveItem?.(item)
88
+ .then(() => {
89
+ onRemoveItem?.(item);
90
+ })
91
+ .catch((e: unknown) => {
92
+ console.error(e);
93
+ const message =
94
+ e instanceof Error ? e.message : 'Error deleting item';
95
+ alert(message);
96
+ });
90
97
  }}
91
98
  className="util-cell-link util-cell-link-remove"
92
99
  >
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useCallback, useEffect, useState } from 'react';
1
+ import React, { useMemo, useCallback, useEffect, useState, useId } from 'react';
2
2
  import { useParams, useNavigate } from 'react-router';
3
3
  import { Datagrid } from './Datagrid';
4
4
  import { ErrorComponent } from '../ErrorComponent';
@@ -16,6 +16,7 @@ export function ListPage<T extends AnyClass>({
16
16
  model: AnyClassConstructor<T>;
17
17
  customHeader?: React.ReactNode;
18
18
  }) {
19
+ const id = useId();
19
20
  const listPageMeta = useMemo(() => getListPageMeta(model), [model]);
20
21
 
21
22
  const [loading, setLoading] = useState(true);
@@ -83,8 +84,8 @@ export function ListPage<T extends AnyClass>({
83
84
  fetchData(1, filters); // Reset to first page when filters change
84
85
  };
85
86
 
86
- if (loading) return <LoadingScreen />;
87
- if (error) return <ErrorComponent error={error} />;
87
+ if (loading) return <LoadingScreen id={id} />;
88
+ if (error) return <ErrorComponent id={id} error={error} />;
88
89
 
89
90
  return (
90
91
  <div className="list">
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import CheckIcon from '../../../assets/icons/svg/check.svg';
3
+ import CrossIcon from '../../../assets/icons/svg/cross.svg';
4
+
5
+ interface BooleanCellProps {
6
+ value: boolean;
7
+ }
8
+
9
+ export function BooleanCell({ value }: BooleanCellProps) {
10
+ return value ? (
11
+ <CheckIcon className="icon icon-true" />
12
+ ) : (
13
+ <CrossIcon className="icon icon-false" />
14
+ );
15
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ interface DateCellProps {
4
+ value: string | number | Date;
5
+ }
6
+
7
+ export function DateCell({ value }: DateCellProps) {
8
+ if (!value) return <>-</>;
9
+
10
+ const date = new Date(value);
11
+ return (
12
+ <>
13
+ {`${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1)
14
+ .toString()
15
+ .padStart(2, '0')}/${date.getFullYear()} ${date
16
+ .getHours()
17
+ .toString()
18
+ .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`}
19
+ </>
20
+ );
21
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { CellConfiguration } from '../../../decorators/list/Cell';
3
+
4
+ interface DefaultCellProps {
5
+ value: any;
6
+ configuration: CellConfiguration;
7
+ }
8
+
9
+ export function DefaultCell({ value, configuration }: DefaultCellProps): React.ReactElement {
10
+ return <>{value ? value.toString() : configuration.placeHolder}</>;
11
+ }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { CellConfiguration } from '../../../decorators/list/Cell';
3
+ import { DownloadCellConfiguration } from '../../../decorators/list/cells/DownloadCell';
4
+
5
+ interface DownloadCellProps {
6
+ value: string;
7
+ configuration: CellConfiguration;
8
+ }
9
+
10
+ export function DownloadCell({ value, configuration }: DownloadCellProps): React.ReactElement {
11
+ if (!value) return <>-</>;
12
+
13
+ const downloadConfiguration = configuration as DownloadCellConfiguration;
14
+ const baseUrl = downloadConfiguration.baseUrl || '';
15
+ const downloadUrl = baseUrl + value;
16
+
17
+ return (
18
+ <a
19
+ href={downloadUrl}
20
+ download
21
+ className="download-link"
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ >
25
+ Download
26
+ </a>
27
+ );
28
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { CellConfiguration } from '../../../decorators/list/Cell';
3
+ import { ImageCellConfiguration } from '../../../decorators/list/cells/ImageCell';
4
+ interface ImageCellProps {
5
+ value: string;
6
+ configuration: CellConfiguration;
7
+ }
8
+
9
+ export function ImageCell({ value, configuration }: ImageCellProps) {
10
+ const imageConfiguration = configuration as ImageCellConfiguration;
11
+ if (!value) return <>-</>;
12
+
13
+ return (
14
+ <img
15
+ width={100}
16
+ height={100}
17
+ src={imageConfiguration.baseUrl + value}
18
+ style={{ objectFit: 'contain' }}
19
+ alt=""
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ interface UUIDCellProps {
4
+ value: string;
5
+ }
6
+
7
+ export function UUIDCell({ value }: UUIDCellProps) {
8
+ if (!value || typeof value !== 'string' || value.length < 6) return <>-</>;
9
+
10
+ return <>{`${value.slice(0, 3)}...${value.slice(-3)}`}</>;
11
+ }
@@ -4,18 +4,22 @@ import { GetDetailsDataFN } from '../details/Details';
4
4
  import { InputConfiguration } from './Input';
5
5
 
6
6
  const DETAILS_METADATA_KEY = 'DetailsMetaData';
7
- export type OnSubmitFN<T> = (data: T | FormData) => Promise<T | FormData>;
7
+ export type OnSubmitFN<T> = (data: T | FormData) => Promise<T>;
8
8
 
9
9
  interface FormOptions<T extends AnyClass> {
10
10
  onSubmit: OnSubmitFN<T>;
11
11
  getDetailsData?: GetDetailsDataFN<T>;
12
+ /** @deprecated */
12
13
  redirectBackOnSuccess?: boolean;
13
14
  type?: 'json' | 'formData';
15
+ redirectSuccessUrl?: string;
14
16
  }
15
17
 
16
18
  export interface FormConfiguration<T extends AnyClass> extends FormOptions<T> {
19
+ /** @deprecated */
17
20
  redirectBackOnSuccess: boolean;
18
21
  type: 'json' | 'formData';
22
+ redirectSuccessUrl?: string;
19
23
  }
20
24
 
21
25
  export function Form<T extends AnyClass>(options?: FormOptions<T>): ClassDecorator {
@@ -1,7 +1,8 @@
1
1
  import 'reflect-metadata';
2
2
  import { AnyClass } from '../../types/AnyClass';
3
+ import { createDecorator, DecoratorMap } from '../../utils/decerators';
3
4
 
4
- export const CELL_KEY = Symbol('cell');
5
+ export const CELL_KEY: Symbol = Symbol('cell');
5
6
 
6
7
  interface Filter {
7
8
  type: 'string' | 'number' | 'date' | 'static-select';
@@ -13,7 +14,7 @@ export interface StaticSelectFilter extends Filter {
13
14
  }
14
15
 
15
16
  export type CellTypes = 'string' | 'date' | 'number' | 'boolean' | 'uuid';
16
- export type ExtendedCellTypes = CellTypes | 'image';
17
+ export type ExtendedCellTypes = CellTypes | 'image' | 'download';
17
18
 
18
19
  export interface CellOptions {
19
20
  name?: string;
@@ -22,38 +23,23 @@ export interface CellOptions {
22
23
  placeHolder?: string;
23
24
  filter?: Filter | StaticSelectFilter;
24
25
  }
25
- export interface ExtendedCellOptions extends Omit<CellOptions, 'type'> {
26
- type?: ExtendedCellTypes;
27
- }
28
26
 
29
27
  export interface CellConfiguration extends Omit<CellOptions, 'type'> {
30
28
  name: string;
31
29
  type: ExtendedCellTypes;
32
30
  }
33
31
 
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 {
48
- return (target, propertyKey) => {
49
- const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
50
- Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
32
+ export const cellMap: DecoratorMap<CellOptions, CellConfiguration> = (
33
+ { propertyKey },
34
+ options
35
+ ) => ({
36
+ ...options,
37
+ name: options.name || propertyKey.toString(),
38
+ type: options.type || 'string',
39
+ });
51
40
 
52
- if (options) {
53
- const keyString = `${CELL_KEY.toString()}:${propertyKey.toString()}:options`;
54
- Reflect.defineMetadata(keyString, options, target);
55
- }
56
- };
41
+ export function Cell(options?: CellOptions): PropertyDecorator {
42
+ return createDecorator(CELL_KEY, options, cellMap);
57
43
  }
58
44
 
59
45
  export function getCellFields<T extends AnyClass>(entityClass: T): CellConfiguration[] {
@@ -61,12 +47,8 @@ export function getCellFields<T extends AnyClass>(entityClass: T): CellConfigura
61
47
  const inputFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
62
48
 
63
49
  return inputFields.map(field => {
64
- const fields: CellOptions =
50
+ const fields: CellConfiguration =
65
51
  Reflect.getMetadata(`${CELL_KEY.toString()}:${field}:options`, prototype) || {};
66
- return {
67
- ...fields,
68
- name: fields.name || field,
69
- type: fields.type as ExtendedCellTypes,
70
- };
52
+ return fields;
71
53
  });
72
54
  }
@@ -0,0 +1,32 @@
1
+ import {
2
+ CELL_KEY,
3
+ CellConfiguration,
4
+ CellOptions,
5
+ CellTypes,
6
+ Cell,
7
+ ExtendedCellTypes,
8
+ cellMap,
9
+ } from './Cell';
10
+ import { createDecorator, DecoratorMap } from '../../utils/decerators';
11
+
12
+ export interface ExtendedCellOptions extends Omit<CellOptions, 'type'> {
13
+ type?: ExtendedCellTypes;
14
+ }
15
+
16
+ interface ExtendedCellConfiguration extends Omit<CellConfiguration, 'name'> {
17
+ name?: string;
18
+ }
19
+
20
+ export function ExtendedCell<T extends ExtendedCellOptions, K extends ExtendedCellConfiguration>(
21
+ options?: T,
22
+ map?: DecoratorMap<T, K>
23
+ ): PropertyDecorator {
24
+ return createDecorator<T, K>(CELL_KEY, options, ({ target, propertyKey }, optionsInner) => {
25
+ const config = cellMap({ target, propertyKey }, optionsInner as CellOptions);
26
+ const newConfig: K | undefined = map ? map({ target, propertyKey }, optionsInner) : undefined;
27
+ return {
28
+ ...config,
29
+ ...newConfig,
30
+ } as K;
31
+ });
32
+ }
@@ -0,0 +1,18 @@
1
+ import { CellConfiguration, CellOptions } from '../Cell';
2
+ import { ExtendedCell } from '../ExtendedCell';
3
+
4
+ export interface DownloadCellOptions extends Omit<CellOptions, 'type'> {
5
+ baseUrl: string;
6
+ }
7
+
8
+ export interface DownloadCellConfiguration extends CellConfiguration {
9
+ type: 'download';
10
+ baseUrl: string;
11
+ }
12
+
13
+ export function DownloadCell(options?: DownloadCellOptions): PropertyDecorator {
14
+ return ExtendedCell(options, (_, options) => ({
15
+ ...options,
16
+ type: 'download',
17
+ }));
18
+ }
@@ -1,17 +1,18 @@
1
- import 'reflect-metadata';
2
- import { Cell, CellConfiguration, CellOptions, ExtendedCell } from '../Cell';
1
+ import { CellConfiguration, CellOptions } from '../Cell';
2
+ import { ExtendedCell, ExtendedCellOptions } from '../ExtendedCell';
3
3
 
4
- export interface ImageCellOptions extends CellOptions {
4
+ export interface ImageCellOptions extends Omit<CellOptions, 'type'> {
5
5
  baseUrl: string;
6
6
  }
7
7
 
8
8
  export interface ImageCellConfiguration extends CellConfiguration {
9
9
  type: 'image';
10
+ baseUrl: string;
10
11
  }
11
12
 
12
13
  export function ImageCell(options?: ImageCellOptions): PropertyDecorator {
13
- return ExtendedCell({
14
+ return ExtendedCell(options, (_, options) => ({
14
15
  ...options,
15
16
  type: 'image',
16
- });
17
+ }));
17
18
  }
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export { FormPage } from './components/form/FormPage';
19
19
  export { Form, type OnSubmitFN } from './decorators/form/Form';
20
20
  export { Input } from './decorators/form/Input';
21
21
  export { SelectInput } from './decorators/form/inputs/SelectInput';
22
+ export { DownloadCell } from './decorators/list/cells/DownloadCell';
22
23
  //for nested form fields
23
24
  export { getInputFields } from './decorators/form/Input';
24
25
 
@@ -1,7 +1,7 @@
1
1
  @import 'utils/scrollbar';
2
2
  $header-height: 60px;
3
3
  $footer-height: 60px;
4
- $datagrid-height: calc(100vh - ($header-height + $footer-height));
4
+ $datagrid-height: calc(100vh - #{$header-height} - #{$footer-height});
5
5
  .list {
6
6
  width: 100%;
7
7
  background-color: #2b2b2b;
@@ -0,0 +1,24 @@
1
+ export type DecoratorMap<T, K> = (
2
+ { target, propertyKey }: { target: Object; propertyKey: string | symbol },
3
+ options: T
4
+ ) => K;
5
+ export function createDecorator<T extends { name?: string }, K>(
6
+ key: Symbol,
7
+ options?: T,
8
+ map?: DecoratorMap<T, K>
9
+ ): PropertyDecorator {
10
+ return (target, propertyKey) => {
11
+ const propKeyStr = propertyKey.toString();
12
+ const existingCells: string[] = Reflect.getMetadata(key, target) || [];
13
+
14
+ if (options) {
15
+ const keyString = `${key.toString()}:${propKeyStr}:options`;
16
+ const config = map ? map({ target, propertyKey }, options) : options;
17
+
18
+ Reflect.defineMetadata(key, [...existingCells, propKeyStr], target);
19
+ Reflect.defineMetadata(keyString, config, target);
20
+ } else {
21
+ Reflect.defineMetadata(key, [...existingCells, propKeyStr], target);
22
+ }
23
+ };
24
+ }