Skip to content

Commit 2dce6ad

Browse files
committed
feat(zimbra): add delete modal for signle and multiples redirections
ref: #PRDCOL-112 Signed-off-by: Atef ZAAFOURI <[email protected]>
1 parent f449ad3 commit 2dce6ad

File tree

6 files changed

+313
-175
lines changed

6 files changed

+313
-175
lines changed

packages/manager/apps/zimbra/public/translations/common/Messages_fr_FR.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"delete_domain": "Supprimer le domaine",
3232
"redirections": "Redirections",
3333
"delete_redirection": "Supprimer la redirection",
34+
"delete_redirections": "Supprimer les redirections",
3435
"add_redirection": "Créer une redirection",
3536
"edit_redirection": "Modifier la redirection",
3637
"auto_reply": "Répondeur",

packages/manager/apps/zimbra/public/translations/redirections/Messages_fr_FR.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"zimbra_redirections_account_title": "Gestion des redirections",
55
"zimbra_redirections_account_quota": "Quota de redirection",
66
"zimbra_redirections_delete_modal_content": "Souhaitez-vous supprimer la redirection ?",
7+
"zimbra_redirections_delete_selected_modal_content": "Souhaitez-vous supprimer les redirections ?",
78
"zimbra_redirections_add_header": "Merci de compléter les informations pour la redirection.",
89
"zimbra_redirections_add_form_input_from": "De l'adresse",
910
"zimbra_redirections_add_form_input_to": "Vers l'adresse",

packages/manager/apps/zimbra/src/pages/dashboard/redirections/Redirections.page.tsx

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22

3-
import { Outlet, useNavigate, useParams } from 'react-router-dom';
3+
import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
44

55
import { useTranslation } from 'react-i18next';
66

@@ -22,6 +22,7 @@ import {
2222

2323
import { BadgeStatus } from '@/components';
2424
import { MAX_REDIRECTIONS_QUOTA } from '@/constants';
25+
import { ResourceStatus } from '@/data/api';
2526
import { useOrganizations, usePlatform, useRedirections } from '@/data/hooks';
2627
import { useDebouncedValue, useGenerateUrl } from '@/hooks';
2728
import { DATAGRID_REFRESH_INTERVAL, DATAGRID_REFRESH_ON_MOUNT } from '@/utils';
@@ -55,20 +56,31 @@ const columns: DatagridColumn<RedirectionItem>[] = [
5556
},
5657
{
5758
id: 'tooltip',
58-
cell: (item) => <ActionButton data-testid="add-redirection-btn" item={item} />,
59+
cell: (item) => (
60+
<ActionButton data-testid="add-redirection-btn" item={item} />
61+
),
5962
label: '',
6063
},
6164
];
6265

6366
export const Redirections = () => {
6467
const { t } = useTranslation(['redirections', 'common', NAMESPACES.STATUS]);
6568
const navigate = useNavigate();
69+
const location = useLocation();
6670
const { platformUrn } = usePlatform();
6771
const { accountId } = useParams();
68-
const [searchInput, setSearchInput, debouncedSearchInput, setDebouncedSearchInput] =
69-
useDebouncedValue('');
72+
const [
73+
searchInput,
74+
setSearchInput,
75+
debouncedSearchInput,
76+
setDebouncedSearchInput,
77+
] = useDebouncedValue('');
78+
const [rowSelection, setRowSelection] = useState({});
79+
const [selectedRows, setSelectedRows] = useState<RedirectionItem[]>([]);
7080
const hrefAddRedirection = useGenerateUrl('./add', 'path');
81+
const hrefDeleteSelectedRedirection = useGenerateUrl('./delete_all', 'path');
7182
const { data: organizations } = useOrganizations();
83+
const { clearSelectedRedirections } = location.state || {};
7284
const {
7385
data: redirections,
7486
isLoading,
@@ -81,29 +93,92 @@ export const Redirections = () => {
8193
refetchOnMount: DATAGRID_REFRESH_ON_MOUNT,
8294
});
8395

96+
useEffect(() => {
97+
if (clearSelectedRedirections) {
98+
setRowSelection({});
99+
setSelectedRows([]);
100+
}
101+
}, [clearSelectedRedirections]);
102+
84103
const handleAddEmailRedirectionClick = () => {
85104
navigate(hrefAddRedirection);
86105
};
87106

107+
const handleDeleteSelectedRedirectionClick = () => {
108+
navigate(hrefDeleteSelectedRedirection, {
109+
state: {
110+
selectedRedirections: selectedRows.map((row) => ({
111+
id: row?.id,
112+
from: row?.from,
113+
to: row?.to,
114+
})),
115+
},
116+
});
117+
};
118+
119+
const isRowSelectable = useCallback(
120+
(item: RedirectionItem) => item.status === ResourceStatus.READY,
121+
[],
122+
);
123+
88124
const items = useMemo(() => {
89125
return (
90126
redirections?.map((redirection) => ({
91127
id: redirection.id,
92128
from: redirection.currentState.source,
93129
to: redirection.currentState.destination,
94130
status: redirection.resourceStatus,
95-
organization: organizations?.find((o) => o.id === redirection?.currentState?.organizationId)
96-
?.currentState?.name,
131+
organization: organizations?.find(
132+
(o) => o.id === redirection?.currentState?.organizationId,
133+
)?.currentState?.name,
97134
})) ?? []
98135
);
99136
}, [redirections, organizations]);
100137

138+
const topbar = useMemo(
139+
() => (
140+
<div className="flex gap-6">
141+
<ManagerButton
142+
color={ODS_BUTTON_COLOR.primary}
143+
inline-block
144+
size={ODS_BUTTON_SIZE.sm}
145+
onClick={handleAddEmailRedirectionClick}
146+
urn={platformUrn}
147+
iamActions={[IAM_ACTIONS.redirection.create]}
148+
data-testid="add-redirection-btn"
149+
id="add-redirection-btn"
150+
icon={ODS_ICON_NAME.plus}
151+
label={t('common:add_redirection')}
152+
/>
153+
{!!selectedRows?.length && (
154+
<ManagerButton
155+
color={ODS_BUTTON_COLOR.critical}
156+
inline-block
157+
size={ODS_BUTTON_SIZE.sm}
158+
onClick={handleDeleteSelectedRedirectionClick}
159+
urn={platformUrn}
160+
iamActions={[IAM_ACTIONS.redirection.delete]}
161+
data-testid="delete-all-redirection-btn"
162+
id="delete-all-redirection-btn"
163+
icon={ODS_ICON_NAME.trash}
164+
label={`${t('common:delete_redirections')} (${
165+
selectedRows.length
166+
})`}
167+
/>
168+
)}
169+
</div>
170+
),
171+
[platformUrn, selectedRows],
172+
);
173+
101174
return (
102175
<div>
103176
<Outlet />
104177
{accountId && (
105178
<div className="mb-6">
106-
<OdsText preset="heading-3">{t('zimbra_redirections_account_title')}</OdsText>
179+
<OdsText preset="heading-3">
180+
{t('zimbra_redirections_account_title')}
181+
</OdsText>
107182
</div>
108183
)}
109184
<ManagerText
@@ -127,20 +202,13 @@ export const Redirections = () => {
127202
setSearchInput,
128203
onSearch: (search) => setDebouncedSearchInput(search),
129204
}}
130-
topbar={
131-
<ManagerButton
132-
color={ODS_BUTTON_COLOR.primary}
133-
inline-block
134-
size={ODS_BUTTON_SIZE.sm}
135-
onClick={handleAddEmailRedirectionClick}
136-
urn={platformUrn}
137-
iamActions={[IAM_ACTIONS.redirection.create]}
138-
data-testid="add-redirection-btn"
139-
id="add-redirection-btn"
140-
icon={ODS_ICON_NAME.plus}
141-
label={t('common:add_redirection')}
142-
/>
143-
}
205+
rowSelection={{
206+
rowSelection,
207+
setRowSelection,
208+
enableRowSelection: ({ original: item }) => isRowSelectable(item),
209+
onRowSelectionChange: setSelectedRows,
210+
}}
211+
topbar={topbar}
144212
columns={columns.map((column) => ({
145213
...column,
146214
label: t(column.label),

packages/manager/apps/zimbra/src/pages/dashboard/redirections/delete/Delete.modal.tsx

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22

3-
import { useNavigate, useParams } from 'react-router-dom';
3+
import { useLocation, useNavigate, useParams } from 'react-router-dom';
44

55
import { useMutation } from '@tanstack/react-query';
66
import { useTranslation } from 'react-i18next';
@@ -18,7 +18,13 @@ import {
1818
useOvhTracking,
1919
} from '@ovh-ux/manager-react-shell-client';
2020

21+
import {
22+
deleteZimbraPlatformRedirection,
23+
getZimbraPlatformRedirectionsQueryKey,
24+
} from '@/data/api';
25+
import { useRedirection } from '@/data/hooks';
2126
import { useGenerateUrl } from '@/hooks';
27+
import queryClient from '@/queryClient';
2228
import {
2329
CANCEL,
2430
CONFIRM,
@@ -30,17 +36,46 @@ export const DeleteOrganizationModal = () => {
3036
const { trackClick, trackPage } = useOvhTracking();
3137
const { t } = useTranslation(['redirections', 'common', NAMESPACES.ACTIONS]);
3238
const navigate = useNavigate();
39+
const location = useLocation();
3340
const { addSuccess, addError } = useNotifications();
34-
const { accountId, redirectionId } = useParams();
35-
36-
const trackingName = accountId ? EMAIL_ACCOUNT_DELETE_REDIRECTION : DELETE_REDIRECTION;
37-
38-
const goBackUrl = useGenerateUrl('../..', 'path');
39-
const onClose = () => navigate(goBackUrl);
41+
const { platformId, accountId, redirectionId } = useParams();
42+
const { data: redirection } = useRedirection({
43+
enabled: !!redirectionId,
44+
});
4045

41-
const { mutate: deleteRedirection, isPending: isSending } = useMutation({
42-
mutationFn: (id: string) => {
43-
return Promise.resolve(id);
46+
const redirections = useMemo(() => {
47+
let {
48+
selectedRedirections,
49+
}: {
50+
selectedRedirections: Array<{ id: string; from: string; to: string }>;
51+
} = location.state || {};
52+
if (redirection && !selectedRedirections) {
53+
selectedRedirections = [
54+
{
55+
id: redirection.id,
56+
from: redirection?.currentState?.source,
57+
to: redirection?.currentState?.destination,
58+
},
59+
];
60+
}
61+
return selectedRedirections ?? [];
62+
}, [location.state, redirection]);
63+
64+
const trackingName = accountId
65+
? EMAIL_ACCOUNT_DELETE_REDIRECTION
66+
: DELETE_REDIRECTION;
67+
68+
const goBackUrl = useGenerateUrl(redirectionId ? '../..' : '..', 'path');
69+
const onClose = (clear: boolean) =>
70+
navigate(goBackUrl, { state: { clearSelectedRedirections: clear } });
71+
72+
const { mutate: deleteRedirections, isPending: isSending } = useMutation({
73+
mutationFn: () => {
74+
return Promise.all(
75+
redirections.map((r) =>
76+
deleteZimbraPlatformRedirection(platformId, r.id),
77+
),
78+
);
4479
},
4580
onSuccess: () => {
4681
trackPage({
@@ -69,11 +104,11 @@ export const DeleteOrganizationModal = () => {
69104
);
70105
},
71106
onSettled: () => {
72-
/* queryClient.invalidateQueries({
107+
queryClient.invalidateQueries({
73108
queryKey: getZimbraPlatformRedirectionsQueryKey(platformId),
74-
}); */
109+
});
75110

76-
onClose();
111+
onClose(true);
77112
},
78113
});
79114

@@ -84,7 +119,7 @@ export const DeleteOrganizationModal = () => {
84119
actionType: 'action',
85120
actions: [trackingName, CONFIRM],
86121
});
87-
deleteRedirection(redirectionId);
122+
deleteRedirections();
88123
};
89124

90125
const handleCancelClick = () => {
@@ -94,15 +129,19 @@ export const DeleteOrganizationModal = () => {
94129
actionType: 'action',
95130
actions: [trackingName, CANCEL],
96131
});
97-
onClose();
132+
onClose(false);
98133
};
99134

100135
return (
101136
<Modal
102137
type={ODS_MODAL_COLOR.critical}
103-
heading={t('common:delete_redirection')}
138+
heading={
139+
redirectionId
140+
? t('common:delete_redirection')
141+
: t('common:delete_redirections')
142+
}
104143
isOpen
105-
onDismiss={onClose}
144+
onDismiss={() => onClose(false)}
106145
primaryLabel={t(`${NAMESPACES.ACTIONS}:delete`)}
107146
onPrimaryButtonClick={handleConfirmClick}
108147
isPrimaryButtonLoading={isSending}
@@ -117,18 +156,27 @@ export const DeleteOrganizationModal = () => {
117156
className="mt-5 mb-5"
118157
data-testid="modal-content"
119158
>
120-
{t('zimbra_redirections_delete_modal_content')}
121-
</OdsText>
122-
123-
<OdsText preset={ODS_TEXT_PRESET.paragraph} className="font-bold">
124-
{t('zimbra_redirections_from')}
125-
{' :'}
126-
</OdsText>
127-
128-
<OdsText preset={ODS_TEXT_PRESET.paragraph} className="font-bold">
129-
{t('zimbra_redirections_to')}
130-
{' :'}
159+
{redirectionId
160+
? t('zimbra_redirections_delete_modal_content')
161+
: t('zimbra_redirections_delete_selected_modal_content')}
131162
</OdsText>
163+
<div className="flex flex-col">
164+
{redirections.map((item) => (
165+
<div className="flex gap-6" key={item?.id}>
166+
<OdsText preset={ODS_TEXT_PRESET.paragraph} className="font-bold">
167+
{t('zimbra_redirections_from')}
168+
{': '}
169+
{item?.from}
170+
</OdsText>
171+
172+
<OdsText preset={ODS_TEXT_PRESET.paragraph} className="font-bold">
173+
{t('zimbra_redirections_to')}
174+
{': '}
175+
{item?.to}
176+
</OdsText>
177+
</div>
178+
))}
179+
</div>
132180
</>
133181
</Modal>
134182
);

packages/manager/apps/zimbra/src/pages/dashboard/redirections/delete/Delete.spec.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import React from 'react';
22

3+
import { useNavigate } from 'react-router-dom';
4+
35
import { describe, expect, it } from 'vitest';
46

5-
import { render, screen } from '@/utils/test.provider';
7+
import { fireEvent, render, screen } from '@/utils/test.provider';
68

79
import DeleteRedirectionModal from './Delete.modal';
810

11+
const mockNavigate = vi.fn();
12+
vi.mocked(useNavigate).mockReturnValue(mockNavigate);
13+
914
describe('DeleteRedirection modal', () => {
1015
it('should render correctly', () => {
1116
render(<DeleteRedirectionModal />);
@@ -18,4 +23,14 @@ describe('DeleteRedirection modal', () => {
1823
expect(cancelButton).not.toBeDisabled();
1924
expect(deleteButton).not.toBeDisabled();
2025
});
26+
27+
it('navigates back when cancel button is clicked', () => {
28+
render(<DeleteRedirectionModal />);
29+
30+
fireEvent.click(screen.getByTestId('cancel-btn'));
31+
32+
expect(mockNavigate).toHaveBeenCalledWith('..', {
33+
state: { clearSelectedRedirections: false },
34+
});
35+
});
2136
});

0 commit comments

Comments
 (0)