Skip to content

Commit

Permalink
feat(studies): SKFP-1387 Add table and search
Browse files Browse the repository at this point in the history
  • Loading branch information
GaelleA committed Dec 5, 2024
1 parent f440213 commit f653e94
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 33 deletions.
17 changes: 17 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,13 @@ const en = {
noAffiliationOption: 'I do not have an institutional affiliation.',
},
},
publicStudies: {
title: 'Studies',
search: {
title: 'Search by study name',
placeholder: 'Kids First: Genomics of Orthopaedic Disease Program',
},
},
},
facets: {
file_id: 'File ID',
Expand Down Expand Up @@ -2194,6 +2201,16 @@ const en = {
study: {
count: '{count, plural, =0 {Study} =1 {Study} other {Studies}}',
study: 'Study',
study_code: 'Code',
study_name: 'Name',
program: 'Program',
domain: 'Domain',
external_id: 'dbGaP',
participant_count: 'Participants',
family_count: 'Families',
genomic: 'Genomics',
transcriptomic: 'Transcriptomics',
imaging: 'Imaging',
},
variant: {
participant: '{count, plural, =0 {Participant} =1 {Participant} other {Participants}}',
Expand Down
4 changes: 2 additions & 2 deletions src/services/api/arranger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ArrangerColumnStateResults,
ArrangerPhenotypes,
IStatistics,
IStudiesStatistics,
IStudiesStatistic,
ISuggestionPayload,
Suggestion,
SuggestionType,
Expand All @@ -36,7 +36,7 @@ const fetchStatistics = () =>
});

const fetchStudiesStatistics = () =>
sendRequest<IStudiesStatistics[]>({
sendRequest<IStudiesStatistic[]>({
method: 'GET',
url: `${ARRANGER_API_URL}/statistics/studies`,
headers: headers(),
Expand Down
25 changes: 18 additions & 7 deletions src/services/api/arranger/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ export type Suggestion = {
ensembl_gene_id?: string;
};

export interface IStudiesStatistic {
data_category: string[];
description?: string;
domain: string;
domains?: string[];
external_ids?: string[];
family_count?: number;
file_count?: number;
guid?: string;
is_harmonized?: boolean;
participant_count: number;
program: string;
study_code: string;
study_id: string;
study_name: string;
}

export interface IDiagnosis {
mondo_id: string;
count: number;
Expand All @@ -42,18 +59,12 @@ export interface IStatistics {
participants: number;
samples: number;
studies: number;
studiesStatistics: Record<string, Omit<IStudiesStatistics, 'study_code'>>;
studiesStatistics: IStudiesStatistic[];
genomes: number;
variants: number;
transcriptomes: number;
}

export interface IStudiesStatistics {
participant_count: number;
domain: string;
study_code: string;
}

export interface ArrangerSingleColumnState {
accessor: string;
canChangeShow: boolean;
Expand Down
12 changes: 2 additions & 10 deletions src/store/global/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createAsyncThunk } from '@reduxjs/toolkit';

import { ArrangerApi } from 'services/api/arranger';
import { IStatistics, IStudiesStatistics } from 'services/api/arranger/models';
import { IStatistics } from 'services/api/arranger/models';
import { RootState } from 'store/types';

const fetchStats = createAsyncThunk<IStatistics, void, { rejectValue: string; state: RootState }>(
Expand All @@ -10,17 +10,9 @@ const fetchStats = createAsyncThunk<IStatistics, void, { rejectValue: string; st
const { data: statistics } = await ArrangerApi.fetchStatistics();
const { data: studiesStatistics } = await ArrangerApi.fetchStudiesStatistics();

const formattedStudiesStatistics = studiesStatistics?.reduce(
(acc, { study_code, domain, participant_count }) => {
acc[study_code] = { participant_count, domain };
return acc;
},
{} as Record<string, Omit<IStudiesStatistics, 'study_code'>>,
);

const data: IStatistics = {
...statistics!,
studiesStatistics: formattedStudiesStatistics!,
studiesStatistics: studiesStatistics!,
};

return data;
Expand Down
13 changes: 6 additions & 7 deletions src/views/LandingPage/StudiesSection/Carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button } from 'antd';
import Carousel, { CarouselRef } from 'antd/lib/carousel';
import CarouselCard from 'views/LandingPage/StudiesSection/Carousel/Card';

import { IStudiesStatistics } from '../../../../services/api/arranger/models';
import { IStudiesStatistic } from '../../../../services/api/arranger/models';
import { useGlobals } from '../../../../store/global';

import styles from './index.module.css';
Expand All @@ -26,26 +26,25 @@ const studies = [
{ code: 'KF-ESGR', formattedCode: 'kfesgr' },
];

const formatStudies = (
studiesStatistics: Record<string, Omit<IStudiesStatistics, 'study_code'>> = {},
) =>
const formatStudies = (studiesStatistics: IStudiesStatistic[] = []) =>
studies.map((study) => {
const studyStats = studiesStatistics[study.code] || {};
const studyStats = studiesStatistics.find((studyPart) => studyPart.study_code === study.code);
const domain = studyStats?.domain || 'unknown';
const participants = studyStats?.participant_count || 0;
const domainTag = intl.get(`screen.loginPage.studies.tags.${domain}`);

return {
code: study.code,
title: intl.get(`screen.loginPage.studies.${study.formattedCode}.name`),
description: intl.get(`screen.loginPage.studies.${study.formattedCode}.description`),
tags: [intl.get(`screen.loginPage.studies.tags.${domain}`)] || [domain] || [],
tags: domainTag ? [domainTag] : domain ? [domain] : [],
participants: participants,
};
});

const LoginCarousel = () => {
const { stats } = useGlobals();
const { studiesStatistics = {} } = stats || {};
const { studiesStatistics = [] } = stats || {};
const formattedStudies = formatStudies(studiesStatistics); // Fixed studiesStatistics reference
const carouselRef = useRef<CarouselRef>(null);

Expand Down
18 changes: 18 additions & 0 deletions src/views/PublicStudies/PageContent/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.pageContent {
width: 100%;
}
.pageContent .tableWrapper {
width: 100%;
gap: 0 !important;
}
.pageContent .title {
margin: 0;
}
.pageContent .label {
margin-bottom: 8px;
}
.pageContent .inputContainer {
display: flex;
gap: 8px;
margin-bottom: 8px;
}
86 changes: 86 additions & 0 deletions src/views/PublicStudies/PageContent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useEffect, useState } from 'react';
import intl from 'react-intl-universal';
import ProLabel from '@ferlab/ui/core/components/ProLabel';
import ProTable from '@ferlab/ui/core/components/ProTable';
import GridCard from '@ferlab/ui/core/view/v2/GridCard';
import { Input, Space, Typography } from 'antd';
import { getColumns, TABLE_ID } from 'views/PublicStudies/utils';

import { useGlobals } from 'store/global';
import { getProTableDictionary } from 'utils/translation';

import styles from './index.module.css';

const { Title } = Typography;

const PageContent = () => {
const [searchValue, setSearchValue] = useState('');

const { stats, isFetchingStats } = useGlobals();
const { studiesStatistics = [] } = stats || {};

const [filteredStudies, setFilteredStudies] = useState(studiesStatistics);

useEffect(() => {
setFilteredStudies(studiesStatistics);
}, [studiesStatistics]);

const searchPrescription = (value: any) => {
if (value?.target?.value) {
const searchValue = value.target.value;
setSearchValue(searchValue);

const filteredValues = studiesStatistics.filter((study) =>
study.study_name.toLowerCase().includes(searchValue.toLowerCase()),
);
setFilteredStudies(filteredValues);
} else {
setSearchValue('');
setFilteredStudies(studiesStatistics);
}
};

const defaultColumns = getColumns();

return (
<Space direction="vertical" size={16} className={styles.pageContent}>
<Title className={styles.title} level={4}>
{intl.get('screen.publicStudies.title')}
</Title>

<div>
<ProLabel className={styles.label} title={intl.get('screen.publicStudies.search.title')} />
<div className={styles.inputContainer}>
<Input
allowClear
onChange={searchPrescription}
placeholder={intl.get('screen.publicStudies.search.placeholder')}
size="large"
value={searchValue}
/>
</div>
</div>

<GridCard
content={
<ProTable
tableId={TABLE_ID}
columns={defaultColumns}
wrapperClassName={styles.tableWrapper}
loading={isFetchingStats}
showSorterTooltip={false}
bordered
headerConfig={{
hideItemsCount: true,
}}
size="small"
dataSource={filteredStudies.map((i) => ({ ...i, key: i.study_code }))}
dictionary={getProTableDictionary()}
/>
}
/>
</Space>
);
};

export default PageContent;
20 changes: 20 additions & 0 deletions src/views/PublicStudies/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.studiesPage {
display: flex;
}

.scrollContent {
width: 100%;
padding: var(--default-page-content-padding);
}

.descriptionCell::after {
content: '';
display: block;
}

.dbgapLink {
margin-right: 8px;
}
.dbgapLink:last-child {
margin-right: 0;
}
32 changes: 27 additions & 5 deletions src/views/PublicStudies/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import ScrollContent from '@ferlab/ui/core/layout/ScrollContent';

import PublicLayout from 'components/PublicLayout';
import { fetchStats } from 'store/global/thunks';

import PageContent from './PageContent';
import { SCROLL_WRAPPER_ID } from './utils';

import style from './index.module.css';

const PublicStudies = () => {
const dispatch = useDispatch();

useEffect(() => {
dispatch(fetchStats());
}, [dispatch]);

const PublicStudies = () => (
<PublicLayout>
<span></span>
</PublicLayout>
);
return (
<PublicLayout>
<div className={style.studiesPage}>
<ScrollContent id={SCROLL_WRAPPER_ID} className={style.scrollContent}>
<PageContent />
</ScrollContent>
</div>
</PublicLayout>
);
};

export default PublicStudies;
Loading

0 comments on commit f653e94

Please sign in to comment.