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.
- package/dist/components/ErrorComponent.d.ts +3 -2
- package/dist/components/LoadingScreen.d.ts +3 -1
- package/dist/components/list/CellField.d.ts +3 -2
- package/dist/components/list/cells/BooleanCell.d.ts +6 -0
- package/dist/components/list/cells/DateCell.d.ts +6 -0
- package/dist/components/list/cells/DefaultCell.d.ts +8 -0
- package/dist/components/list/cells/DownloadCell.d.ts +8 -0
- package/dist/components/list/cells/ImageCell.d.ts +8 -0
- package/dist/components/list/cells/UUIDCell.d.ts +6 -0
- package/dist/decorators/form/Form.d.ts +5 -1
- package/dist/decorators/list/Cell.d.ts +4 -6
- package/dist/decorators/list/ExtendedCell.d.ts +10 -0
- package/dist/decorators/list/cells/DownloadCell.d.ts +9 -0
- package/dist/decorators/list/cells/ImageCell.d.ts +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +1 -1
- package/dist/utils/decerators.d.ts +7 -0
- package/package.json +2 -1
- package/src/components/DetailsPage.tsx +4 -3
- package/src/components/ErrorComponent.tsx +36 -33
- package/src/components/LoadingScreen.tsx +4 -3
- package/src/components/Panel.tsx +7 -3
- package/src/components/form/InnerForm.tsx +13 -1
- package/src/components/list/CellField.tsx +26 -42
- package/src/components/list/Datagrid.tsx +16 -9
- package/src/components/list/ListPage.tsx +4 -3
- package/src/components/list/cells/BooleanCell.tsx +15 -0
- package/src/components/list/cells/DateCell.tsx +21 -0
- package/src/components/list/cells/DefaultCell.tsx +11 -0
- package/src/components/list/cells/DownloadCell.tsx +28 -0
- package/src/components/list/cells/ImageCell.tsx +22 -0
- package/src/components/list/cells/UUIDCell.tsx +11 -0
- package/src/decorators/form/Form.ts +5 -1
- package/src/decorators/list/Cell.ts +15 -33
- package/src/decorators/list/ExtendedCell.ts +32 -0
- package/src/decorators/list/cells/DownloadCell.ts +18 -0
- package/src/decorators/list/cells/ImageCell.ts +6 -5
- package/src/index.ts +1 -0
- package/src/styles/list.scss +1 -1
- package/src/utils/decerators.ts +24 -0
package/src/components/Panel.tsx
CHANGED
@@ -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
|
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
|
6
|
-
import
|
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
|
-
|
13
|
+
configuration: CellConfiguration;
|
10
14
|
item: T;
|
11
15
|
value: any;
|
12
16
|
}
|
13
17
|
|
14
|
-
export function CellField<T extends AnyClass>({
|
15
|
-
|
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 (
|
18
|
-
case 'boolean':
|
19
|
-
render =
|
25
|
+
switch (configuration.type) {
|
26
|
+
case 'boolean':
|
27
|
+
render = <BooleanCell value={value} />;
|
20
28
|
break;
|
21
|
-
}
|
22
29
|
case 'date':
|
23
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
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 =
|
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(
|
56
|
-
|
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={
|
61
|
-
|
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
|
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
|
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
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
53
|
-
|
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:
|
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 '
|
2
|
-
import {
|
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
|
|
package/src/styles/list.scss
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
@import 'utils/scrollbar';
|
2
2
|
$header-height: 60px;
|
3
3
|
$footer-height: 60px;
|
4
|
-
$datagrid-height: calc(100vh -
|
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
|
+
}
|