diff --git a/frontend/src/components/RecordsList.tsx b/frontend/src/components/RecordsList.tsx index 32993078..238846f8 100644 --- a/frontend/src/components/RecordsList.tsx +++ b/frontend/src/components/RecordsList.tsx @@ -3,7 +3,7 @@ import { Button, Container, Modal } from "react-bootstrap"; import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { DownloadModal } from "./DownloadModal"; -import { buildTsvString } from "../utils/buildTsvString"; +import { buildTsvString } from "../utils/stringBuilders"; import { AgGridReact } from "ag-grid-react"; import { useState } from "react"; import styles from "./records.module.scss"; @@ -26,6 +26,8 @@ import { import { PatientIdsTriplet } from "../pages/patients/PatientsPage"; import { ErrorMessage, LoadingSpinner, Toolbar } from "../shared/tableElements"; import { AgGridReact as AgGridReactType } from "ag-grid-react/lib/agGridReact"; +import { BreadCrumb } from "../shared/components/BreadCrumb"; +import { PageHeader } from "../shared/components/PageHeader"; interface IRecordsListProps { colDefs: ColDef[]; @@ -50,7 +52,7 @@ interface IRecordsListProps { showDownloadModal: boolean; setShowDownloadModal: Dispatch>; handleDownload: () => void; - samplesQueryParam: string | undefined; + samplesQueryParam: string | undefined | any; // TODO prepareSamplesDataForAgGrid?: (samples: Sample[]) => any[]; samplesColDefs: ColDef[]; samplesParentWhereVariables: SampleWhere; @@ -189,6 +191,9 @@ export default function RecordsList({ return ( + + + {showDownloadModal && ( { @@ -261,9 +266,7 @@ export default function RecordsList({ {({ height, width }) => ( - - {`Viewing ${samplesQueryParam}`} - +
(null); + const params = useParams(); useEffect(() => { gridRef.current?.api?.showLoadingOverlay(); @@ -123,12 +128,23 @@ export default function SamplesList({ setRowCount(data?.samplesConnection.edges.length || 0); }, [data]); + const samples = data?.samplesConnection?.edges.map((e) => e.node) as Sample[]; + + const popupPageHeader = useMemo(() => { + if (parentWhereVariables && samples && params) { + return getSamplePopupParamId( + parentWhereVariables, + samples, + Object.values(params)?.[0]! + ); + } + return undefined; + }, [parentWhereVariables, params, samples]); + if (loading) return ; if (error) return ; - const samples = data!.samplesConnection.edges.map((e) => e.node) as Sample[]; - async function onCellValueChanged(params: CellValueChangedEvent) { if (!editMode) return; @@ -243,6 +259,13 @@ export default function SamplesList({ return ( <> + + {!parentWhereVariables && } + + + {showDownloadModal && ( { @@ -253,7 +276,9 @@ export default function SamplesList({ onComplete={() => { setShowDownloadModal(false); }} - exportFileName={exportFileName || "samples.tsv"} + exportFileName={[popupPageHeader, "samples.tsv"] + .filter(Boolean) + .join("_")} /> )} @@ -323,7 +348,11 @@ export default function SamplesList({ {({ width }) => (
diff --git a/frontend/src/components/records.module.scss b/frontend/src/components/records.module.scss index 787a829a..4ad9350e 100644 --- a/frontend/src/components/records.module.scss +++ b/frontend/src/components/records.module.scss @@ -17,3 +17,7 @@ .popupHeight { height: calc(100vh - 180px); } + +.popupTableHeight { + height: calc(100vh - 270px); +} diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts index b0b41aa2..bf60b6fd 100644 --- a/frontend/src/generated/graphql.ts +++ b/frontend/src/generated/graphql.ts @@ -12431,7 +12431,17 @@ export type FindSamplesByInputValueQuery = { __typename?: "SamplePatientsHasSampleConnection"; edges: Array<{ __typename?: "SamplePatientsHasSampleRelationship"; - node: { __typename?: "Patient"; smilePatientId: string }; + node: { + __typename?: "Patient"; + smilePatientId: string; + cmoPatientId?: string | null; + dmpPatientId?: string | null; + patientAliasesIsAlias: Array<{ + __typename?: "PatientAlias"; + namespace: string; + value: string; + }>; + }; }>; }; cohortsHasCohortSampleConnection: { @@ -12988,6 +12998,12 @@ export const FindSamplesByInputValueDocument = gql` edges { node { smilePatientId + cmoPatientId + dmpPatientId + patientAliasesIsAlias { + namespace + value + } } } } diff --git a/frontend/src/pages/cohorts/CohortsPage.tsx b/frontend/src/pages/cohorts/CohortsPage.tsx index 98dbd8dd..050a7adf 100644 --- a/frontend/src/pages/cohorts/CohortsPage.tsx +++ b/frontend/src/pages/cohorts/CohortsPage.tsx @@ -16,7 +16,6 @@ import { } from "../../shared/helpers"; import RecordsList from "../../components/RecordsList"; import { useParams } from "react-router-dom"; -import { PageHeader } from "../../shared/components/PageHeader"; function cohortFilterWhereVariables(parsedSearchVals: string[]): CohortWhere[] { if (parsedSearchVals.length > 1) { @@ -147,61 +146,57 @@ export default function CohortsPage({ const defaultSort = [{ initialCohortDeliveryDate: SortDirection.Desc }]; return ( - <> - - - handleSearch(userSearchVal, setParsedSearchVals)} - showDownloadModal={showDownloadModal} - setShowDownloadModal={setShowDownloadModal} - handleDownload={() => setShowDownloadModal(true)} - samplesColDefs={CohortSampleDetailsColumns} - samplesQueryParam={ - sampleQueryParamValue && - `${sampleQueryParamHeaderName} ${sampleQueryParamValue}` - } - prepareSamplesDataForAgGrid={prepareSampleCohortDataForAgGrid} - samplesParentWhereVariables={ - { - cohortsHasCohortSampleConnection_SOME: { - node: { - [sampleQueryParamFieldName]: sampleQueryParamValue, - }, + handleSearch(userSearchVal, setParsedSearchVals)} + showDownloadModal={showDownloadModal} + setShowDownloadModal={setShowDownloadModal} + handleDownload={() => setShowDownloadModal(true)} + samplesColDefs={CohortSampleDetailsColumns} + samplesQueryParam={ + sampleQueryParamValue && + `${sampleQueryParamHeaderName} ${sampleQueryParamValue}` + } + prepareSamplesDataForAgGrid={prepareSampleCohortDataForAgGrid} + samplesParentWhereVariables={ + { + cohortsHasCohortSampleConnection_SOME: { + node: { + [sampleQueryParamFieldName]: sampleQueryParamValue, }, - } as SampleWhere - } - samplesRefetchWhereVariables={(samplesParsedSearchVals) => { - return { - cohortsHasCohortSampleConnection_SOME: { - node: { - [sampleQueryParamFieldName]: sampleQueryParamValue, - }, + }, + } as SampleWhere + } + samplesRefetchWhereVariables={(samplesParsedSearchVals) => { + return { + cohortsHasCohortSampleConnection_SOME: { + node: { + [sampleQueryParamFieldName]: sampleQueryParamValue, }, - OR: cohortSampleFilterWhereVariables(samplesParsedSearchVals), - } as SampleWhere; - }} - sampleKeyForUpdate={sampleKeyForUpdate} - userEmail={userEmail} - setUserEmail={setUserEmail} - /> - + }, + OR: cohortSampleFilterWhereVariables(samplesParsedSearchVals), + } as SampleWhere; + }} + sampleKeyForUpdate={sampleKeyForUpdate} + userEmail={userEmail} + setUserEmail={setUserEmail} + /> ); } diff --git a/frontend/src/pages/patients/PatientsPage.tsx b/frontend/src/pages/patients/PatientsPage.tsx index 273ac093..95b4bde4 100644 --- a/frontend/src/pages/patients/PatientsPage.tsx +++ b/frontend/src/pages/patients/PatientsPage.tsx @@ -8,7 +8,6 @@ import { import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; import RecordsList from "../../components/RecordsList"; import { useParams } from "react-router-dom"; -import { PageHeader } from "../../shared/components/PageHeader"; import { Col, Form } from "react-bootstrap"; import { AlertModal } from "../../components/AlertModal"; import { Tooltip } from "@material-ui/core"; @@ -241,12 +240,11 @@ export default function PatientsPage({ const dataName = "patients"; const sampleQueryParamFieldName = "smilePatientId"; + const sampleQueryParamHeaderName = "Patient"; const sampleQueryParamValue = params[sampleQueryParamFieldName]; return ( <> - - - - - handleSearch(userSearchVal, setParsedSearchVals)} - showDownloadModal={showDownloadModal} - setShowDownloadModal={setShowDownloadModal} - handleDownload={() => setShowDownloadModal(true)} - samplesColDefs={SampleMetadataDetailsColumns} - samplesQueryParam={ - sampleQueryParamValue && - `${sampleQueryParamHeaderName} ${sampleQueryParamValue}` - } - prepareSamplesDataForAgGrid={prepareSampleMetadataForAgGrid} - samplesParentWhereVariables={ - { - hasMetadataSampleMetadata_SOME: { - [sampleQueryParamFieldName]: sampleQueryParamValue, - }, - } as SampleWhere - } - samplesRefetchWhereVariables={(sampleParsedSearchVals) => { - return { - hasMetadataSampleMetadata_SOME: { - OR: sampleFilterWhereVariables(sampleParsedSearchVals), - ...(sampleQueryParamValue - ? { - [sampleQueryParamFieldName]: sampleQueryParamValue, - } - : {}), - }, - } as SampleWhere; - }} - /> - + handleSearch(userSearchVal, setParsedSearchVals)} + showDownloadModal={showDownloadModal} + setShowDownloadModal={setShowDownloadModal} + handleDownload={() => setShowDownloadModal(true)} + samplesColDefs={SampleMetadataDetailsColumns} + samplesQueryParam={ + sampleQueryParamValue && + `${sampleQueryParamHeaderName} ${sampleQueryParamValue}` + } + prepareSamplesDataForAgGrid={prepareSampleMetadataForAgGrid} + samplesParentWhereVariables={ + { + hasMetadataSampleMetadata_SOME: { + [sampleQueryParamFieldName]: sampleQueryParamValue, + }, + } as SampleWhere + } + samplesRefetchWhereVariables={(sampleParsedSearchVals) => { + return { + hasMetadataSampleMetadata_SOME: { + OR: sampleFilterWhereVariables(sampleParsedSearchVals), + ...(sampleQueryParamValue + ? { + [sampleQueryParamFieldName]: sampleQueryParamValue, + } + : {}), + }, + } as SampleWhere; + }} + /> ); } diff --git a/frontend/src/pages/samples/SamplesPage.tsx b/frontend/src/pages/samples/SamplesPage.tsx index 8022c39c..ed9d0058 100644 --- a/frontend/src/pages/samples/SamplesPage.tsx +++ b/frontend/src/pages/samples/SamplesPage.tsx @@ -1,4 +1,3 @@ -import { PageHeader } from "../../shared/components/PageHeader"; import SamplesList from "../../components/SamplesList"; import { ReadOnlyCohortSampleDetailsColumns, @@ -17,51 +16,47 @@ export default function SamplesPage() { const [columnDefs, setColumnDefs] = useState(combinedSampleDetailsColumns); return ( - <> - - - { - return { - OR: cohortSampleFilterWhereVariables(parsedSearchVals).concat({ - hasMetadataSampleMetadata_SOME: { - OR: sampleFilterWhereVariables(parsedSearchVals), - }, - }), - } as SampleWhere; - }} - customToolbarUI={ - <> - - These tabs change the fields displayed in the table below. "View - All" shows all fields, including both SampleMetadata and Tempo - fields. - {" "} - {" "} - - - } - /> - + { + return { + OR: cohortSampleFilterWhereVariables(parsedSearchVals).concat({ + hasMetadataSampleMetadata_SOME: { + OR: sampleFilterWhereVariables(parsedSearchVals), + }, + }), + } as SampleWhere; + }} + customToolbarUI={ + <> + + These tabs change the fields displayed in the table below. "View + All" shows all fields, including both SampleMetadata and Tempo + fields. + {" "} + {" "} + + + } + /> ); } diff --git a/frontend/src/shared/components/BreadCrumb.tsx b/frontend/src/shared/components/BreadCrumb.tsx new file mode 100644 index 00000000..5910a484 --- /dev/null +++ b/frontend/src/shared/components/BreadCrumb.tsx @@ -0,0 +1,24 @@ +import { Col, Row } from "react-bootstrap"; +import { NavLink } from "react-router-dom"; +import { buildSentenceCaseString } from "../../utils/stringBuilders"; + +export function BreadCrumb({ currPageTitle }: { currPageTitle: string }) { + return ( + + + + + + ); +} diff --git a/frontend/src/shared/components/PageHeader.tsx b/frontend/src/shared/components/PageHeader.tsx index 341a4e14..d635b45f 100644 --- a/frontend/src/shared/components/PageHeader.tsx +++ b/frontend/src/shared/components/PageHeader.tsx @@ -1,27 +1,12 @@ -import { Col, Container, Row } from "react-bootstrap"; -import { NavLink } from "react-router-dom"; - -export function PageHeader({ dataName }: { dataName: string }) { - const pageTitle = - dataName.charAt(0).toUpperCase() + dataName.slice(1).toLowerCase(); +import { Col, Row } from "react-bootstrap"; +import { buildSentenceCaseString } from "../../utils/stringBuilders"; +export function PageHeader({ title }: { title: string }) { return ( - - - - -

{pageTitle}

- -
-
+ + +

{buildSentenceCaseString(title)}

+ +
); } diff --git a/frontend/src/shared/helpers.tsx b/frontend/src/shared/helpers.tsx index 3768d304..b1dbc8ad 100644 --- a/frontend/src/shared/helpers.tsx +++ b/frontend/src/shared/helpers.tsx @@ -1084,3 +1084,26 @@ export function isValidCostCenter(costCenter: string): boolean { const validCostCenter = new RegExp("^\\d{5}/\\d{5}$"); return validCostCenter.test(costCenter); } + +export function getSamplePopupParamId( + parentWhereVariables: SampleWhere, + samples: Sample[], + paramId: string +) { + if (parentWhereVariables.OR?.[0].patientsHasSampleConnection_SOME) { + console.log(samples); + const patient = samples[0].patientsHasSampleConnection?.edges?.[0]?.node; + + const cmoPatientId = patient.patientAliasesIsAlias.find( + (patientAlias) => patientAlias.namespace === "cmoId" + )?.value; + if (cmoPatientId) return cmoPatientId; + + const dmpPatientId = patient.patientAliasesIsAlias.find( + (patientAlias) => patientAlias.namespace === "dmpId" + )?.value; + if (dmpPatientId) return dmpPatientId; + } + + return paramId; +} diff --git a/frontend/src/utils/buildTsvString.ts b/frontend/src/utils/stringBuilders.ts similarity index 87% rename from frontend/src/utils/buildTsvString.ts rename to frontend/src/utils/stringBuilders.ts index 81e11d27..dde4d2c9 100644 --- a/frontend/src/utils/buildTsvString.ts +++ b/frontend/src/utils/stringBuilders.ts @@ -29,3 +29,7 @@ export function buildTsvString(rows: any[], colDefs: ColDef[]) { return [colHeadersAsTsvRow, ...rowsAsTsvRows].join("\n"); } + +export function buildSentenceCaseString(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/graphql-server/src/generated/graphql.ts b/graphql-server/src/generated/graphql.ts index 2157d64d..e67698ea 100644 --- a/graphql-server/src/generated/graphql.ts +++ b/graphql-server/src/generated/graphql.ts @@ -12430,7 +12430,17 @@ export type FindSamplesByInputValueQuery = { __typename?: "SamplePatientsHasSampleConnection"; edges: Array<{ __typename?: "SamplePatientsHasSampleRelationship"; - node: { __typename?: "Patient"; smilePatientId: string }; + node: { + __typename?: "Patient"; + smilePatientId: string; + cmoPatientId?: string | null; + dmpPatientId?: string | null; + patientAliasesIsAlias: Array<{ + __typename?: "PatientAlias"; + namespace: string; + value: string; + }>; + }; }>; }; cohortsHasCohortSampleConnection: { @@ -12891,6 +12901,12 @@ export const FindSamplesByInputValueDocument = gql` edges { node { smilePatientId + cmoPatientId + dmpPatientId + patientAliasesIsAlias { + namespace + value + } } } } diff --git a/graphql/operations.graphql b/graphql/operations.graphql index 3e31a68b..26be399f 100644 --- a/graphql/operations.graphql +++ b/graphql/operations.graphql @@ -75,6 +75,12 @@ query FindSamplesByInputValue( edges { node { smilePatientId + cmoPatientId + dmpPatientId + patientAliasesIsAlias { + namespace + value + } } } }