Skip to content

Commit f85d05a

Browse files
authored
feat: expand/collapse all functionality for proposals and reviewers a… (#1575)
2 parents 4c8f952 + 0f21cc6 commit f85d05a

4 files changed

Lines changed: 150 additions & 38 deletions

File tree

apps/frontend/src/components/fap/Proposals/ProposalsView/FapLegacyProposalsTable.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import ProposalReviewContent, {
1515
import ProposalReviewModal from 'components/review/ProposalReviewModal';
1616
import { Review, SettingsId, Fap } from 'generated/sdk';
1717
import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime';
18+
import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
1819
import {
1920
FapProposalType,
2021
FapProposalAssignmentType,
@@ -159,6 +160,10 @@ const FapLegacyProposalsTable = ({
159160
settingsFormatToUse: SettingsId.DATE_FORMAT,
160161
});
161162
const { t } = useTranslation();
163+
const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
164+
'[data-cy="fap-assignments-table"]',
165+
[loadingFapProposals]
166+
);
162167

163168
const translatedColumns = FapProposalColumns.map((column) =>
164169
column.title === 'Instrument'
@@ -360,6 +365,7 @@ const FapLegacyProposalsTable = ({
360365
</ProposalReviewModal>
361366
<div data-cy="fap-assignments-table">
362367
<MaterialTable
368+
tableRef={tableRef}
363369
icons={tableIcons}
364370
columns={translatedColumns}
365371
title={
@@ -424,6 +430,7 @@ const FapLegacyProposalsTable = ({
424430
}}
425431
/>
426432
</div>
433+
{expandCollapseAllButton}
427434
</>
428435
);
429436
};

apps/frontend/src/components/fap/Proposals/ProposalsView/FapProposalsAndAssignmentsTable.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import ProposalReviewModal from 'components/review/ProposalReviewModal';
2121
import { UserRole, Review, SettingsId, Fap } from 'generated/sdk';
2222
import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime';
2323
import { useCheckAccess } from 'hooks/common/useCheckAccess';
24+
import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
2425
import {
2526
FapProposalType,
2627
FapProposalAssignmentType,
@@ -182,6 +183,10 @@ const FapProposalsAndAssignmentsTable = ({
182183
UserRole.USER_OFFICER,
183184
]);
184185
const { t } = useTranslation();
186+
const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
187+
'[data-cy="fap-assignments-table"]',
188+
[loadingFapProposals]
189+
);
185190

186191
const translatedColumns = FapProposalColumns.map((column) =>
187192
column.title === 'Instrument'
@@ -431,6 +436,7 @@ const FapProposalsAndAssignmentsTable = ({
431436
/>
432437
<div data-cy="fap-assignments-table">
433438
<MaterialTable
439+
tableRef={tableRef}
434440
icons={tableIcons}
435441
columns={translatedColumns}
436442
title={
@@ -495,6 +501,7 @@ const FapProposalsAndAssignmentsTable = ({
495501
}}
496502
/>
497503
</div>
504+
{expandCollapseAllButton}
498505
</>
499506
);
500507
};

apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import { useSearchParams } from 'react-router-dom';
55

66
import { Fap } from 'generated/sdk';
7+
import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
78
import { FapMember, useFapMembersData } from 'hooks/fap/useFapMembersData';
89
import {
910
FapProposalAssignmentType,
@@ -122,6 +123,11 @@ const FapReviewersAndAssignmentsTable = ({
122123
const page = searchParams.get('page');
123124
const pageSize = searchParams.get('pageSize');
124125

126+
const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
127+
'[data-cy="fap-reviewers-assignments-table"]',
128+
[loadingMembers, loadingFapProposals]
129+
);
130+
125131
const reviewersAndProposals: ReviewerAndProposals[] = FapMembersData.map(
126132
(member) => {
127133
const assignedProposals = fapProposals.FapProposalsData.filter(
@@ -253,46 +259,50 @@ const FapReviewersAndAssignmentsTable = ({
253259
fapProposalsData={FapProposalsData}
254260
assignProposalsToReviewer={handleMemberAssignmentToFapProposals}
255261
/>
256-
<MaterialTable
257-
title="Reviewers and Assignments"
258-
columns={FapReviewersAndAssignmentsTableColumns}
259-
actions={tableActions}
260-
detailPanel={(rowData) => (
261-
<FapAssignedProposalsTable
262-
assignedProposals={rowData.rowData.assignedProposals}
263-
fapSecs={fap.fapSecretaries.map((s) => s.id)}
264-
removeAssignedReviewer={removeAssignedReviewer}
265-
updateView={updateFapProposalAssignmentsView}
266-
/>
267-
)}
268-
data={reviewersAndProposals}
269-
isLoading={loadingMembers || loadingFapProposals}
270-
options={{
271-
pageSize: pageSize ? +pageSize : Math.min(10, maxPageLength),
272-
selection: true,
273-
initialPage: page ? +page : 0,
274-
pageSizeOptions: pageSizeOptions,
275-
}}
276-
localization={{
277-
toolbar: {
278-
nRowsSelected: (rowCount) => `${rowCount} reviewer(s) selected`,
279-
},
280-
}}
281-
onSelectionChange={(selectedItems) => {
282-
const selectedReviewers = selectedItems.map(
283-
(item) => item.user.userId
284-
);
285-
286-
setSearchParams((searchParams) => {
287-
searchParams.delete('selection');
288-
selectedReviewers.forEach((pk) =>
289-
searchParams.append('selection', pk.toString())
262+
<div data-cy="fap-reviewers-assignments-table">
263+
<MaterialTable
264+
tableRef={tableRef}
265+
title="Reviewers and Assignments"
266+
columns={FapReviewersAndAssignmentsTableColumns}
267+
actions={tableActions}
268+
detailPanel={(rowData) => (
269+
<FapAssignedProposalsTable
270+
assignedProposals={rowData.rowData.assignedProposals}
271+
fapSecs={fap.fapSecretaries.map((s) => s.id)}
272+
removeAssignedReviewer={removeAssignedReviewer}
273+
updateView={updateFapProposalAssignmentsView}
274+
/>
275+
)}
276+
data={reviewersAndProposals}
277+
isLoading={loadingMembers || loadingFapProposals}
278+
options={{
279+
pageSize: pageSize ? +pageSize : Math.min(10, maxPageLength),
280+
selection: true,
281+
initialPage: page ? +page : 0,
282+
pageSizeOptions: pageSizeOptions,
283+
}}
284+
localization={{
285+
toolbar: {
286+
nRowsSelected: (rowCount) => `${rowCount} reviewer(s) selected`,
287+
},
288+
}}
289+
onSelectionChange={(selectedItems) => {
290+
const selectedReviewers = selectedItems.map(
291+
(item) => item.user.userId
290292
);
291293

292-
return searchParams;
293-
});
294-
}}
295-
/>
294+
setSearchParams((searchParams) => {
295+
searchParams.delete('selection');
296+
selectedReviewers.forEach((pk) =>
297+
searchParams.append('selection', pk.toString())
298+
);
299+
300+
return searchParams;
301+
});
302+
}}
303+
/>
304+
</div>
305+
{expandCollapseAllButton}
296306
</>
297307
);
298308
};
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import KeyboardDoubleArrowDown from '@mui/icons-material/KeyboardDoubleArrowDown';
2+
import KeyboardDoubleArrowRight from '@mui/icons-material/KeyboardDoubleArrowRight';
3+
import { IconButton, Tooltip } from '@mui/material';
4+
import {
5+
DependencyList,
6+
useCallback,
7+
useEffect,
8+
useRef,
9+
useState,
10+
} from 'react';
11+
import React from 'react';
12+
import { createPortal } from 'react-dom';
13+
14+
export function useExpandCollapseAll(
15+
tableSelector: string,
16+
dependencies: DependencyList
17+
) {
18+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
19+
const tableRef = useRef<any>(null);
20+
const [isAllExpanded, setIsAllExpanded] = useState(false);
21+
const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);
22+
23+
//find position to render button
24+
useEffect(() => {
25+
const frame = requestAnimationFrame(() => {
26+
const container = document.querySelector(`${tableSelector} thead tr`);
27+
if (!container) return;
28+
29+
const cells = container.querySelectorAll('th, td');
30+
const cellArray = Array.from(cells);
31+
const targetCell = cellArray.find(
32+
(cell) =>
33+
cell.hasAttribute('data-expand-all-target') ||
34+
(cell.childElementCount === 0 && cell.textContent?.trim() === '')
35+
);
36+
37+
if (targetCell) {
38+
if (!targetCell.hasAttribute('data-expand-all-target')) {
39+
targetCell.setAttribute('data-expand-all-target', 'true');
40+
}
41+
setPortalTarget(targetCell as HTMLElement);
42+
}
43+
});
44+
45+
return () => cancelAnimationFrame(frame);
46+
}, dependencies);
47+
48+
//index each row and expand/collapse them
49+
const toggleExpandAll = useCallback(() => {
50+
const nextExpandedState = !isAllExpanded;
51+
52+
const sortedData = tableRef.current?.dataManager?.sortedData || [];
53+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
54+
sortedData.forEach((row: any, index: number) => {
55+
const isCurrentlyExpanded = !!row.tableData.showDetailPanel;
56+
if (isCurrentlyExpanded !== nextExpandedState) {
57+
const detailPanelProp = tableRef.current?.props.detailPanel;
58+
const detailPanelRenderFunction = Array.isArray(detailPanelProp)
59+
? detailPanelProp[0].render
60+
: detailPanelProp;
61+
62+
tableRef.current?.onToggleDetailPanel(
63+
[index],
64+
detailPanelRenderFunction
65+
);
66+
}
67+
});
68+
69+
setIsAllExpanded(nextExpandedState);
70+
}, [isAllExpanded]);
71+
72+
const expandCollapseAllButton = portalTarget
73+
? createPortal(
74+
<Tooltip title={isAllExpanded ? 'Collapse all' : 'Expand all'}>
75+
<IconButton onClick={toggleExpandAll} data-cy="expand-collapse-all">
76+
{isAllExpanded ? (
77+
<KeyboardDoubleArrowDown />
78+
) : (
79+
<KeyboardDoubleArrowRight />
80+
)}
81+
</IconButton>
82+
</Tooltip>,
83+
portalTarget
84+
)
85+
: null;
86+
87+
return { tableRef, expandCollapseAllButton };
88+
}

0 commit comments

Comments
 (0)