diff --git a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx index fac74da93c..5e460b5d3e 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/ResultFilters.tsx @@ -62,7 +62,7 @@ interface Props { studyId: string; path: string; colHeaders: string[][]; - onColHeadersChange: (colHeaders: string[][]) => void; + onColHeadersChange: (colHeaders: string[][], indices: number[]) => void; } function ResultFilters({ @@ -104,48 +104,61 @@ function ResultFilters({ ); }, [colHeaders]); - useEffect(() => { - const filteredHeaders = parsedHeaders.filter((header) => { - // Apply search filters - if (filters.search) { - const matchesVariable = matchesSearchTerm( - header.variable, - filters.search, - ); + // Process headers while keeping track of their original positions + // This ensures we can properly filter the data matrix later + // Example: if we filter out column 1, we need to know that column 2 + // becomes column 1 in the filtered view but maps to index 2 in the data + const filteredHeaders = useMemo(() => { + return parsedHeaders + .map((header, index) => ({ ...header, index })) + .filter((header) => { + // Apply search filter + if (filters.search) { + const matchesVariable = matchesSearchTerm( + header.variable, + filters.search, + ); - const matchesUnit = matchesSearchTerm(header.unit, filters.search); + const matchesUnit = matchesSearchTerm(header.unit, filters.search); - if (!matchesVariable && !matchesUnit) { - return false; + if (!matchesVariable && !matchesUnit) { + return false; + } } - } - // Apply stat filters - if (header.stat) { - const stat = header.stat.toLowerCase(); + // Apply statistical filters + if (header.stat) { + const stat = header.stat.toLowerCase(); - if (!filters.exp && stat.includes("exp")) { - return false; - } - if (!filters.min && stat.includes("min")) { - return false; - } - if (!filters.max && stat.includes("max")) { - return false; - } - if (!filters.std && stat.includes("std")) { - return false; + if (!filters.exp && stat.includes("exp")) { + return false; + } + if (!filters.min && stat.includes("min")) { + return false; + } + if (!filters.max && stat.includes("max")) { + return false; + } + if (!filters.std && stat.includes("std")) { + return false; + } + if (!filters.values && stat.includes("values")) { + return false; + } } - if (!filters.values && stat.includes("values")) { - return false; - } - } - return true; - }); + return true; + }); + }, [filters, parsedHeaders]); - onColHeadersChange(filteredHeaders.map((h) => h.original)); - }, [filters, parsedHeaders, onColHeadersChange]); + // Notify parent of both filtered headers and their original indices + // This allows the parent to correctly map the filtered view back to the original data + useEffect(() => { + onColHeadersChange( + filteredHeaders.map((h) => h.original), + filteredHeaders.map((h) => h.index), + ); + }, [filteredHeaders, onColHeadersChange]); //////////////////////////////////////////////////////////////// // Event handlers diff --git a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx index 3aaaf4509b..1814bd876d 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx @@ -68,6 +68,8 @@ import { getStudyMatrixIndex } from "../../../../../../services/api/matrix.ts"; import { MatrixGridSynthesis } from "@/components/common/Matrix/components/MatrixGridSynthesis"; import { ResultMatrixDTO } from "@/components/common/Matrix/shared/types.ts"; +type SetResultColHeaders = (headers: string[][], indices: number[]) => void; + function ResultDetails() { const { study } = useOutletContext<{ study: StudyMetadata }>(); const { outputId } = useParams(); @@ -84,7 +86,11 @@ function ResultDetails() { const [itemType, setItemType] = useState(OutputItemType.Areas); const [selectedItemId, setSelectedItemId] = useState(""); const [searchValue, setSearchValue] = useState(""); + // Store filtered headers and their original indices separately + // This allows us to correctly map the data rows to their corresponding headers + // when some columns are filtered out const [resultColHeaders, setResultColHeaders] = useState([]); + const [headerIndices, setHeaderIndices] = useState([]); const isSynthesis = itemType === OutputItemType.Synthesis; const { t } = useTranslation(); const navigate = useNavigate(); @@ -143,16 +149,24 @@ function ResultDetails() { } const res = await getStudyData(study.id, path); - // TODO add backend parse + if (typeof res === "string") { const fixed = res .replace(/NaN/g, '"NaN"') .replace(/Infinity/g, '"Infinity"'); - return JSON.parse(fixed); + const parsed = JSON.parse(fixed); + + return { + ...parsed, + indices: Array.from({ length: parsed.columns.length }, (_, i) => i), + }; } - return res; + return { + ...res, + indices: Array.from({ length: res.columns.length }, (_, i) => i), + }; }, { resetDataOnReload: true, @@ -161,6 +175,19 @@ function ResultDetails() { }, ); + // Transform the matrix data by keeping only the columns that match our filters + // headerIndices contains the original positions of our kept columns, ensuring + // the data stays aligned with its corresponding headers + const filteredData = useMemo(() => { + if (!matrixRes.data) { + return []; + } + + return matrixRes.data.data.map((row) => { + return headerIndices.map((index) => row[index]); + }); + }, [matrixRes.data, headerIndices]); + const synthesisRes = usePromise( () => { if (outputId && selectedItem && isSynthesis) { @@ -213,6 +240,11 @@ function ResultDetails() { } }; + const handleColHeadersChange: SetResultColHeaders = (headers, indices) => { + setResultColHeaders(headers); + setHeaderIndices(indices); + }; + //////////////////////////////////////////////////////////////// // JSX //////////////////////////////////////////////////////////////// @@ -295,7 +327,7 @@ function ResultDetails() { studyId={study.id} path={path} colHeaders={matrixRes.data?.columns || []} - onColHeadersChange={setResultColHeaders} + onColHeadersChange={handleColHeadersChange} />