diff --git a/apps/frontend/src/components/fap/Proposals/ProposalsView/FapLegacyProposalsTable.tsx b/apps/frontend/src/components/fap/Proposals/ProposalsView/FapLegacyProposalsTable.tsx
index ebeb708d67..876c339253 100644
--- a/apps/frontend/src/components/fap/Proposals/ProposalsView/FapLegacyProposalsTable.tsx
+++ b/apps/frontend/src/components/fap/Proposals/ProposalsView/FapLegacyProposalsTable.tsx
@@ -15,6 +15,7 @@ import ProposalReviewContent, {
import ProposalReviewModal from 'components/review/ProposalReviewModal';
import { Review, SettingsId, Fap } from 'generated/sdk';
import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime';
+import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
import {
FapProposalType,
FapProposalAssignmentType,
@@ -159,6 +160,10 @@ const FapLegacyProposalsTable = ({
settingsFormatToUse: SettingsId.DATE_FORMAT,
});
const { t } = useTranslation();
+ const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
+ '[data-cy="fap-assignments-table"]',
+ [loadingFapProposals]
+ );
const translatedColumns = FapProposalColumns.map((column) =>
column.title === 'Instrument'
@@ -360,6 +365,7 @@ const FapLegacyProposalsTable = ({
+ {expandCollapseAllButton}
>
);
};
diff --git a/apps/frontend/src/components/fap/Proposals/ProposalsView/FapProposalsAndAssignmentsTable.tsx b/apps/frontend/src/components/fap/Proposals/ProposalsView/FapProposalsAndAssignmentsTable.tsx
index e818435f96..8d9c55caa4 100644
--- a/apps/frontend/src/components/fap/Proposals/ProposalsView/FapProposalsAndAssignmentsTable.tsx
+++ b/apps/frontend/src/components/fap/Proposals/ProposalsView/FapProposalsAndAssignmentsTable.tsx
@@ -21,6 +21,7 @@ import ProposalReviewModal from 'components/review/ProposalReviewModal';
import { UserRole, Review, SettingsId, Fap } from 'generated/sdk';
import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime';
import { useCheckAccess } from 'hooks/common/useCheckAccess';
+import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
import {
FapProposalType,
FapProposalAssignmentType,
@@ -182,6 +183,10 @@ const FapProposalsAndAssignmentsTable = ({
UserRole.USER_OFFICER,
]);
const { t } = useTranslation();
+ const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
+ '[data-cy="fap-assignments-table"]',
+ [loadingFapProposals]
+ );
const translatedColumns = FapProposalColumns.map((column) =>
column.title === 'Instrument'
@@ -431,6 +436,7 @@ const FapProposalsAndAssignmentsTable = ({
/>
+ {expandCollapseAllButton}
>
);
};
diff --git a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx
index ba4fd1fafb..f04f3caea3 100644
--- a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx
+++ b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx
@@ -4,6 +4,7 @@ import React from 'react';
import { useSearchParams } from 'react-router-dom';
import { Fap } from 'generated/sdk';
+import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll';
import { FapMember, useFapMembersData } from 'hooks/fap/useFapMembersData';
import {
FapProposalAssignmentType,
@@ -122,6 +123,11 @@ const FapReviewersAndAssignmentsTable = ({
const page = searchParams.get('page');
const pageSize = searchParams.get('pageSize');
+ const { tableRef, expandCollapseAllButton } = useExpandCollapseAll(
+ '[data-cy="fap-reviewers-assignments-table"]',
+ [loadingMembers, loadingFapProposals]
+ );
+
const reviewersAndProposals: ReviewerAndProposals[] = FapMembersData.map(
(member) => {
const assignedProposals = fapProposals.FapProposalsData.filter(
@@ -253,46 +259,50 @@ const FapReviewersAndAssignmentsTable = ({
fapProposalsData={FapProposalsData}
assignProposalsToReviewer={handleMemberAssignmentToFapProposals}
/>
- (
- s.id)}
- removeAssignedReviewer={removeAssignedReviewer}
- updateView={updateFapProposalAssignmentsView}
- />
- )}
- data={reviewersAndProposals}
- isLoading={loadingMembers || loadingFapProposals}
- options={{
- pageSize: pageSize ? +pageSize : Math.min(10, maxPageLength),
- selection: true,
- initialPage: page ? +page : 0,
- pageSizeOptions: pageSizeOptions,
- }}
- localization={{
- toolbar: {
- nRowsSelected: (rowCount) => `${rowCount} reviewer(s) selected`,
- },
- }}
- onSelectionChange={(selectedItems) => {
- const selectedReviewers = selectedItems.map(
- (item) => item.user.userId
- );
-
- setSearchParams((searchParams) => {
- searchParams.delete('selection');
- selectedReviewers.forEach((pk) =>
- searchParams.append('selection', pk.toString())
+
+ (
+ s.id)}
+ removeAssignedReviewer={removeAssignedReviewer}
+ updateView={updateFapProposalAssignmentsView}
+ />
+ )}
+ data={reviewersAndProposals}
+ isLoading={loadingMembers || loadingFapProposals}
+ options={{
+ pageSize: pageSize ? +pageSize : Math.min(10, maxPageLength),
+ selection: true,
+ initialPage: page ? +page : 0,
+ pageSizeOptions: pageSizeOptions,
+ }}
+ localization={{
+ toolbar: {
+ nRowsSelected: (rowCount) => `${rowCount} reviewer(s) selected`,
+ },
+ }}
+ onSelectionChange={(selectedItems) => {
+ const selectedReviewers = selectedItems.map(
+ (item) => item.user.userId
);
- return searchParams;
- });
- }}
- />
+ setSearchParams((searchParams) => {
+ searchParams.delete('selection');
+ selectedReviewers.forEach((pk) =>
+ searchParams.append('selection', pk.toString())
+ );
+
+ return searchParams;
+ });
+ }}
+ />
+
+ {expandCollapseAllButton}
>
);
};
diff --git a/apps/frontend/src/hooks/fap/useExpandCollapseAll.tsx b/apps/frontend/src/hooks/fap/useExpandCollapseAll.tsx
new file mode 100644
index 0000000000..23cc6dbcdc
--- /dev/null
+++ b/apps/frontend/src/hooks/fap/useExpandCollapseAll.tsx
@@ -0,0 +1,88 @@
+import KeyboardDoubleArrowDown from '@mui/icons-material/KeyboardDoubleArrowDown';
+import KeyboardDoubleArrowRight from '@mui/icons-material/KeyboardDoubleArrowRight';
+import { IconButton, Tooltip } from '@mui/material';
+import {
+ DependencyList,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import React from 'react';
+import { createPortal } from 'react-dom';
+
+export function useExpandCollapseAll(
+ tableSelector: string,
+ dependencies: DependencyList
+) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const tableRef = useRef(null);
+ const [isAllExpanded, setIsAllExpanded] = useState(false);
+ const [portalTarget, setPortalTarget] = useState(null);
+
+ //find position to render button
+ useEffect(() => {
+ const frame = requestAnimationFrame(() => {
+ const container = document.querySelector(`${tableSelector} thead tr`);
+ if (!container) return;
+
+ const cells = container.querySelectorAll('th, td');
+ const cellArray = Array.from(cells);
+ const targetCell = cellArray.find(
+ (cell) =>
+ cell.hasAttribute('data-expand-all-target') ||
+ (cell.childElementCount === 0 && cell.textContent?.trim() === '')
+ );
+
+ if (targetCell) {
+ if (!targetCell.hasAttribute('data-expand-all-target')) {
+ targetCell.setAttribute('data-expand-all-target', 'true');
+ }
+ setPortalTarget(targetCell as HTMLElement);
+ }
+ });
+
+ return () => cancelAnimationFrame(frame);
+ }, dependencies);
+
+ //index each row and expand/collapse them
+ const toggleExpandAll = useCallback(() => {
+ const nextExpandedState = !isAllExpanded;
+
+ const sortedData = tableRef.current?.dataManager?.sortedData || [];
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ sortedData.forEach((row: any, index: number) => {
+ const isCurrentlyExpanded = !!row.tableData.showDetailPanel;
+ if (isCurrentlyExpanded !== nextExpandedState) {
+ const detailPanelProp = tableRef.current?.props.detailPanel;
+ const detailPanelRenderFunction = Array.isArray(detailPanelProp)
+ ? detailPanelProp[0].render
+ : detailPanelProp;
+
+ tableRef.current?.onToggleDetailPanel(
+ [index],
+ detailPanelRenderFunction
+ );
+ }
+ });
+
+ setIsAllExpanded(nextExpandedState);
+ }, [isAllExpanded]);
+
+ const expandCollapseAllButton = portalTarget
+ ? createPortal(
+
+
+ {isAllExpanded ? (
+
+ ) : (
+
+ )}
+
+ ,
+ portalTarget
+ )
+ : null;
+
+ return { tableRef, expandCollapseAllButton };
+}