Skip to content

Commit 080678c

Browse files
committed
change: Use TypeScript for EntitiesFooter
Split the component and functions into several distinct modules too.
1 parent 8b881e7 commit 080678c

File tree

3 files changed

+142
-69
lines changed

3 files changed

+142
-69
lines changed

src/web/entities/Footer.jsx renamed to src/web/entities/EntitiesFooter.tsx

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
showInfoNotification,
99
showSuccessNotification,
1010
} from '@greenbone/opensight-ui-components-mantinev7';
11+
import {isDefined} from 'gmp/utils/identity';
1112
import ConfirmationDialog from 'web/components/dialog/ConfirmationDialog';
1213
import {DELETE_ACTION} from 'web/components/dialog/DialogTwoButtonFooter';
1314
import Select from 'web/components/form/Select';
@@ -20,16 +21,45 @@ import IconDivider from 'web/components/layout/IconDivider';
2021
import Layout from 'web/components/layout/Layout';
2122
import TableFooter from 'web/components/table/TableFooter';
2223
import TableRow from 'web/components/table/TableRow';
24+
import {WithEntitiesFooterComponentProps} from 'web/entities/withEntitiesFooter';
2325
import useTranslation from 'web/hooks/useTranslation';
24-
import PropTypes from 'web/utils/PropTypes';
25-
import SelectionType from 'web/utils/SelectionType';
26+
import SelectionType, {SelectionTypeType} from 'web/utils/SelectionType';
2627

2728
const DIALOG_TYPES = {
2829
TRASH: 'trash',
2930
DELETE: 'delete',
30-
};
31+
} as const;
32+
33+
type DialogType = (typeof DIALOG_TYPES)[keyof typeof DIALOG_TYPES];
34+
35+
interface DialogConfig {
36+
useCustomDialog: boolean;
37+
customDialogElement?: React.ReactElement | null;
38+
dialogProcessing?: boolean;
39+
}
40+
41+
interface ConfigDialog {
42+
dialogText: string;
43+
dialogTitle: string;
44+
dialogButtonTitle: string;
45+
confirmFunction: () => Promise<void>;
46+
}
3147

32-
export const EntitiesFooter = ({
48+
export interface EntitiesFooterProps extends WithEntitiesFooterComponentProps {
49+
actions?: boolean;
50+
children?: React.ReactNode;
51+
delete?: boolean;
52+
dialogConfig?: DialogConfig;
53+
download?: string;
54+
selection?: boolean;
55+
selectionType?: SelectionTypeType;
56+
span?: number;
57+
tags?: boolean;
58+
trash?: boolean;
59+
onSelectionTypeChange?: (selectionType: SelectionTypeType) => void;
60+
}
61+
62+
const EntitiesFooter = ({
3363
actions = true,
3464
children,
3565
download,
@@ -45,16 +75,22 @@ export const EntitiesFooter = ({
4575
onTrashClick,
4676
dialogConfig = {useCustomDialog: false},
4777
delete: deleteEntities,
48-
...props
49-
}) => {
78+
}: EntitiesFooterProps) => {
5079
const [_] = useTranslation();
51-
const [configDialog, setConfigDialog] = useState(undefined);
80+
const [configDialog, setConfigDialog] = useState<ConfigDialog | undefined>(
81+
undefined,
82+
);
5283
const [isDialogVisible, setIsDialogVisible] = useState(false);
5384
const [isInProgress, setIsInProgress] = useState(false);
5485

55-
const onIconClick = (type, propOnAction) => {
86+
const onIconClick = (
87+
type: DialogType,
88+
propOnAction?: () => void | Promise<void>,
89+
) => {
5690
if (dialogConfig.useCustomDialog) {
57-
propOnAction();
91+
if (isDefined(propOnAction)) {
92+
void propOnAction();
93+
}
5894
return;
5995
}
6096

@@ -69,7 +105,9 @@ export const EntitiesFooter = ({
69105
try {
70106
setIsInProgress(true);
71107
showInfoNotification('', _('Deletion started'));
72-
await propOnAction();
108+
if (isDefined(propOnAction)) {
109+
await propOnAction();
110+
}
73111
showSuccessNotification('', _('Deletion completed'));
74112
} finally {
75113
setIsInProgress(false);
@@ -86,14 +124,16 @@ export const EntitiesFooter = ({
86124
try {
87125
setIsInProgress(true);
88126
showInfoNotification('', _('Moving to trashcan'));
89-
await propOnAction();
127+
if (isDefined(propOnAction)) {
128+
await propOnAction();
129+
}
90130
showSuccessNotification('', _('Move to trashcan completed'));
91131
} finally {
92132
setIsInProgress(false);
93133
}
94134
},
95135
},
96-
};
136+
} as const;
97137
setConfigDialog(configMap[type]);
98138
setIsDialogVisible(true);
99139
};
@@ -129,7 +169,12 @@ export const EntitiesFooter = ({
129169
<Select
130170
items={selectItems}
131171
value={selectionType}
132-
onChange={onSelectionTypeChange}
172+
onChange={
173+
onSelectionTypeChange as (
174+
value: string,
175+
name?: string,
176+
) => void
177+
}
133178
/>
134179
)}
135180
<IconDivider>
@@ -161,7 +206,12 @@ export const EntitiesFooter = ({
161206
<ExportIcon
162207
selectionType={selectionType}
163208
value={download}
164-
onClick={onDownloadClick}
209+
onClick={
210+
onDownloadClick as (
211+
value?: string,
212+
name?: string,
213+
) => void
214+
}
165215
/>
166216
)}
167217
{children}
@@ -185,7 +235,7 @@ export const EntitiesFooter = ({
185235
width="500px"
186236
onClose={closeDialog}
187237
onResumeClick={() => {
188-
configDialog.confirmFunction();
238+
void configDialog.confirmFunction();
189239
closeDialog();
190240
}}
191241
/>
@@ -194,58 +244,4 @@ export const EntitiesFooter = ({
194244
);
195245
};
196246

197-
EntitiesFooter.propTypes = {
198-
actions: PropTypes.bool,
199-
delete: PropTypes.bool,
200-
download: PropTypes.stringOrFalse,
201-
selection: PropTypes.bool,
202-
selectionType: PropTypes.string,
203-
span: PropTypes.number.isRequired,
204-
tags: PropTypes.bool,
205-
trash: PropTypes.bool,
206-
onDeleteClick: PropTypes.func,
207-
onDownloadClick: PropTypes.func,
208-
onSelectionTypeChange: PropTypes.func,
209-
onTagsClick: PropTypes.func,
210-
onTrashClick: PropTypes.func,
211-
children: PropTypes.node,
212-
dialogConfig: PropTypes.shape({
213-
useCustomDialog: PropTypes.bool,
214-
dialog: PropTypes.element,
215-
}),
216-
};
217-
218-
export const withEntitiesFooter =
219-
(options = {}) =>
220-
Component => {
221-
const EntitiesFooterWrapper = ({
222-
onDownloadBulk,
223-
onDeleteBulk,
224-
onTagsBulk,
225-
...props
226-
}) => {
227-
return (
228-
<Component
229-
{...options}
230-
{...props}
231-
onDeleteClick={onDeleteBulk}
232-
onDownloadClick={onDownloadBulk}
233-
onTagsClick={onTagsBulk}
234-
onTrashClick={onDeleteBulk}
235-
/>
236-
);
237-
};
238-
239-
EntitiesFooterWrapper.propTypes = {
240-
onDeleteBulk: PropTypes.func,
241-
onDownloadBulk: PropTypes.func,
242-
onTagsBulk: PropTypes.func,
243-
};
244-
245-
return EntitiesFooterWrapper;
246-
};
247-
248-
export const createEntitiesFooter = options =>
249-
withEntitiesFooter(options)(EntitiesFooter);
250-
251247
export default EntitiesFooter;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* SPDX-FileCopyrightText: 2024 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import EntitiesFooter, {EntitiesFooterProps} from 'web/entities/EntitiesFooter';
7+
import withEntitiesFooter from 'web/entities/withEntitiesFooter';
8+
9+
function createEntitiesFooter<
10+
TOptions extends EntitiesFooterProps = EntitiesFooterProps,
11+
>(options: TOptions) {
12+
return withEntitiesFooter<EntitiesFooterProps, EntitiesFooterProps>(options)(
13+
EntitiesFooter,
14+
);
15+
}
16+
17+
export default createEntitiesFooter;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* SPDX-FileCopyrightText: 2025 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
interface EntitiesFooterWrapperProps {
7+
onDeleteBulk?: () => void;
8+
onDownloadBulk?: () => void;
9+
onTagsBulk?: () => void;
10+
onTrashBulk?: () => void;
11+
}
12+
13+
/**
14+
* Props for the component passed to withEntitiesFooter gets provided.
15+
*/
16+
export interface WithEntitiesFooterComponentProps {
17+
onDeleteClick?: () => void;
18+
onDownloadClick?: (filename: string) => void;
19+
onTagsClick?: () => void;
20+
onTrashClick?: () => void;
21+
}
22+
23+
/**
24+
* Props for the wrapper component created by withEntitiesFooter.
25+
*/
26+
type WithEntitiesFooterProps<TProps> = EntitiesFooterWrapperProps &
27+
Omit<
28+
TProps,
29+
keyof EntitiesFooterWrapperProps | keyof WithEntitiesFooterComponentProps
30+
>;
31+
32+
export function withEntitiesFooter<
33+
TProps extends
34+
WithEntitiesFooterComponentProps = WithEntitiesFooterComponentProps,
35+
TOptions = {},
36+
>(options: TOptions = {} as TOptions) {
37+
return (Component: React.ComponentType<TProps>) => {
38+
const EntitiesFooterWrapper = ({
39+
onDownloadBulk,
40+
onDeleteBulk,
41+
onTagsBulk,
42+
...props
43+
}: WithEntitiesFooterProps<TProps>) => {
44+
return (
45+
<Component
46+
{...(options as TOptions)}
47+
{...(props as TProps)}
48+
onDeleteClick={onDeleteBulk}
49+
onDownloadClick={onDownloadBulk}
50+
onTagsClick={onTagsBulk}
51+
onTrashClick={onDeleteBulk}
52+
/>
53+
);
54+
};
55+
56+
return EntitiesFooterWrapper;
57+
};
58+
}
59+
60+
export default withEntitiesFooter;

0 commit comments

Comments
 (0)