diff --git a/web/src/index.css b/web/src/index.css index 7f0a5424f..3611a538e 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -124,9 +124,9 @@ blockquote { padding: 80px; } -@media screen and (min-width: 1200px) { +@media screen and (min-width: 2200px) { .dataStyle { - max-width: 1200px; + max-width: 2200px; margin-left: auto; margin-right: auto; } diff --git a/web/src/pages/family/FamilyView.tsx b/web/src/pages/family/FamilyView.tsx index 4ff8cb7a9..0f6d32b09 100644 --- a/web/src/pages/family/FamilyView.tsx +++ b/web/src/pages/family/FamilyView.tsx @@ -1,24 +1,16 @@ import * as React from 'react' import { useParams } from 'react-router-dom' -import { Accordion, AccordionTitleProps } from 'semantic-ui-react' - -import PersonRoundedIcon from '@mui/icons-material/PersonRounded' -import BloodtypeRoundedIcon from '@mui/icons-material/BloodtypeRounded' - +import { Table as SUITable } from 'semantic-ui-react' import { useQuery } from '@apollo/client' +import Table from '../../shared/components/Table' + import Pedigree from '../../shared/components/pedigree/Pedigree' import LoadingDucks from '../../shared/components/LoadingDucks/LoadingDucks' import { gql } from '../../__generated__/gql' import FamilyViewTitle from './FamilyViewTitle' -import iconStyle from '../../shared/iconStyle' -import SeqPanel from '../../shared/components/SeqPanel' -import SampleInfo from '../../shared/components/SampleInfo' - -const sampleFieldsToDisplay = ['active', 'type'] - const GET_FAMILY_INFO = gql(` query FamilyInfo($family_id: Int!) { family(familyId: $family_id) { @@ -61,12 +53,27 @@ query FamilyInfo($family_id: Int!) { } }`) +const HEADINGS = [ + 'Individual ID', + 'External ID', + 'Maternal ID', + 'Paternal ID', + 'Sex', + 'Affected', + 'SG ID(s)', + 'Samples', + 'Individuals with SG Type Genome', + 'Individuals with SG Type Exome', + 'Individuals with SG type Transcriptome', + 'Individuals with SG type Mtseq', + 'Report Links', +] + const FamilyView: React.FunctionComponent> = () => { const { familyID } = useParams() const family_ID = familyID ? +familyID : -1 const [activeIndices, setActiveIndices] = React.useState([-1]) - const [mostRecent, setMostRecent] = React.useState('') const { loading, error, data } = useQuery(GET_FAMILY_INFO, { variables: { family_id: family_ID }, @@ -81,7 +88,6 @@ const FamilyView: React.FunctionComponent> = () => { if (!activeIndices.includes(indexToSet)) { setActiveIndices([...activeIndices, indexToSet]) } - setMostRecent(e) const element = document.getElementById(e) if (element) { const y = element.getBoundingClientRect().top + window.pageYOffset - 100 @@ -91,20 +97,38 @@ const FamilyView: React.FunctionComponent> = () => { [data, activeIndices] ) - const handleTitleClick = (e: React.MouseEvent, itemProps: AccordionTitleProps) => { - setMostRecent('') - const index = itemProps.index ?? -1 - if (index === -1) return - if (activeIndices.indexOf(+index) > -1) { - setActiveIndices(activeIndices.filter((i) => i !== index)) - } else { - setActiveIndices([...activeIndices, +index]) - } - } - if (loading) return if (error) return <>Error! {error.message} + const tableData = data?.family.participants.map((participant) => { + const types = [ + ...new Set( + participant.samples.map((s) => s.sequencingGroups.map((sg) => sg.type)).flat() + ), + ] + .flat() + .reduce((acc, color) => ((acc[color] = (acc[color] || 0) + 1), acc), {}) + const pedEntry = data?.family.project.pedigree.find( + (p) => p.individual_id === participant.externalId + ) + return { + individualID: participant.id, + externaldID: participant.externalId, + maternalID: pedEntry?.maternal_id, + paternalID: pedEntry?.paternal_id, + sex: pedEntry?.sex, + affected: pedEntry?.affected, + SG_ids: participant.samples + .map((sample) => sample.sequencingGroups.map((sg) => sg.id).join(', ')) + .join(', '), + samples: participant.samples.map((sample) => sample.id).join(', '), + individuals_SG_genome: types.genome ?? 0, + individuals_SG_exome: types.exome ?? 0, + individuals_SG_transcriptome: types.transcriptome ?? 0, + individuals_SG_mtseq: types.mtseq ?? 0, + } + }) + return data ? (
<> @@ -116,111 +140,33 @@ const FamilyView: React.FunctionComponent> = () => { externalId={data?.family.externalId} /> - ({ - key: item.id, - title: { - content: ( -

+ + + {HEADINGS.map((title) => ( + - {item.externalId} -

- ), - icon: ( - - ), - }, - content: { - content: ( -
- ({ - key: s.id, - title: { - content: ( - <> -

- {`${s.id}\t`} -

- -

- {s.externalId} -

- - ), - icon: , - }, - content: { - content: ( - <> -
- - sampleFieldsToDisplay.includes( - key - ) - ) - )} - /> - - -
- - ), - }, - }))} - exclusive={false} - /> -
- ), - }, - }))} - /> + {title} + + ))} + + + + {tableData?.map((row, i) => ( + + {Object.values(row).map((cell, j) => ( + {cell} + ))} + + ))} + +
) : ( diff --git a/web/src/pages/project/FamilyGrid.tsx b/web/src/pages/project/FamilyGrid.tsx new file mode 100644 index 000000000..aaede7867 --- /dev/null +++ b/web/src/pages/project/FamilyGrid.tsx @@ -0,0 +1,112 @@ +import * as React from 'react' +import { useQuery } from '@apollo/client' +import { Table as SUITable } from 'semantic-ui-react' +import FamilyLink from '../../shared/components/links/FamilyLink' +import LoadingDucks from '../../shared/components/LoadingDucks/LoadingDucks' +import { gql } from '../../__generated__/gql' +import Table from '../../shared/components/Table' + +interface FamilyGridProps { + projectName: string +} + +const GET_FAMILY_GRID_INFO = gql(` +query GetFamilyData($project: String!) { + project(name: $project ) { + id + families { + externalId + id + participants { + id + samples { + id + sequencingGroups { + type + } + } + } + } + pedigree + } +} +`) + +const HEADINGS = [ + 'Family Name', + 'Number of Individuals', + 'Number of Affected Individuals', + 'Individuals with SG type Genome', + 'Individuals with SG type Exome', + 'Individuals with SG type Transcriptome', + 'Individuals with SG type Mtseq', +] + +const FamilyGrid: React.FunctionComponent = ({ projectName }) => { + const { loading, error, data } = useQuery(GET_FAMILY_GRID_INFO, { + variables: { project: projectName }, + }) + + if (loading) return + if (error) return <>Error! {error.message} + + const tableData = data?.project.families.map((family) => { + const types = family.participants + .map((p) => [ + ...new Set(p.samples.map((s) => s.sequencingGroups.map((sg) => sg.type)).flat()), + ]) + .flat() + .reduce((acc, color) => ((acc[color] = (acc[color] || 0) + 1), acc), {}) + return { + family: { ID: family.id, externalID: family.externalId }, + individualsCount: family.participants.length, + affectedIndividuals: data.project.pedigree.filter( + (item) => item.family_id === family.externalId && item.affected === 2 + ).length, + individuals_SG_genome: types.genome ?? 0, + individuals_SG_exome: types.exome ?? 0, + individuals_SG_transcriptome: types.transcriptome ?? 0, + individuals_SG_mtseq: types.mtseq ?? 0, + } + }) + + return ( + + + + {HEADINGS.map((title) => ( + + {title} + + ))} + + + + {tableData?.map((row, i) => ( + + {Object.values(row).map((cell, j) => + j === 0 ? ( + + + {cell.externalID} + + + ) : ( + {cell} + ) + )} + + ))} + +
+ ) +} + +export default FamilyGrid diff --git a/web/src/pages/project/ProjectSummary.tsx b/web/src/pages/project/ProjectSummary.tsx index fa2982dcf..a4b8d8fa1 100644 --- a/web/src/pages/project/ProjectSummary.tsx +++ b/web/src/pages/project/ProjectSummary.tsx @@ -11,6 +11,7 @@ import MultiQCReports from './MultiQCReports' import SummaryStatistics from './SummaryStatistics' import BatchStatistics from './BatchStatistics' import ProjectGrid from './ProjectGrid' +import FamilyGrid from './FamilyGrid' import TotalsStats from './TotalsStats' import MuckError from '../../shared/components/MuckError' import LoadingDucks from '../../shared/components/LoadingDucks/LoadingDucks' @@ -212,7 +213,7 @@ const ProjectSummaryView: React.FunctionComponent = () => { />
- { totalSamples={summary?.total_samples_in_query} pageNumber={pageNumber} handleOnClick={handleOnClick} - /> + /> */} +
)}