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 }; +}