Skip to content
Snippets Groups Projects
Commit 9addd9d0 authored by Mateusz Bolewski's avatar Mateusz Bolewski
Browse files

feat(pin): added Mirna pins and details

parent 8260206f
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!50feat(pin): added Mirna pins and details
Showing
with 129 additions and 106 deletions
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "avoid",
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindConfig": "./tailwind.config.ts",
"tailwindFunctions": ["twMerge"],
"tabWidth": 2
}
......@@ -18,6 +18,21 @@ You can start editing the page by modifying `app/page.tsx`. The page auto-update
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Conventional Commits
Install:
```bash
npm install commitizen -g
git add .
```
If you want to make conventional commit, use:
```bash
cz
```
## Learn More
To learn more about Next.js, take a look at the following resources:
......
const config = {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
arrowParens: 'avoid',
plugins: [import('prettier-plugin-tailwindcss')],
tailwindConfig: './tailwind.config.ts',
tailwindFunctions: ['twMerge'],
tabWidth: 2,
};
module.exports = config;
......@@ -28,12 +28,12 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St
);
};
describe('DrugsAccordion - component', () => {
describe('MirnaAccordion - component', () => {
it('should display drugs number after succesfull chemicals search', () => {
renderComponent({
mirnas: { data: mirnasFixture, loading: 'succeeded', error: { name: '', message: '' } },
});
expect(screen.getByText('MiRNA (2)')).toBeInTheDocument();
expect(screen.getByText('MiRNA (4)')).toBeInTheDocument();
});
it('should display loading indicator while waiting for chemicals search response', () => {
renderComponent({
......
/* eslint-disable no-magic-numbers */
import { render, screen } from '@testing-library/react';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { StoreType } from '@/redux/store';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { drawerSearchDrugsStepTwoFixture } from '@/redux/drawer/drawerFixture';
import { PinsList } from './PinsList.component';
const PINS_LIST = drugsFixture.map(drug => ({
id: drug.id,
name: drug.name,
data: drug,
}));
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<PinsList pinsList={PINS_LIST} type="drugs" />
</Wrapper>,
),
{
store,
}
);
};
describe('PinsList component', () => {
it('should render list of pins', () => {
renderComponent();
const fristDrugName = drugsFixture[0].name;
const secondDrugName = drugsFixture[1].name;
expect(screen.getByText(fristDrugName)).toBeInTheDocument();
expect(screen.getByText(secondDrugName)).toBeInTheDocument();
});
it('should navigate to details step on pin click', () => {
const { store } = renderComponent({ drawer: drawerSearchDrugsStepTwoFixture });
const firstPin = screen.getAllByRole('button')[0];
firstPin.click();
const {
drawer: {
searchDrawerState: { currentStep, stepType, selectedValue },
},
} = store.getState();
const drug = drugsFixture[0];
expect(currentStep).toBe(3);
expect(stepType).toBe('drugs');
expect(selectedValue).toEqual(drug);
});
});
import { BioEntityContent, Chemical, Drug, Mirna } from '@/types/models';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { displayEntityDetails } from '@/redux/drawer/drawer.slice';
import { assertNever } from '@/utils/assertNever';
import { PinItem, PinType } from './PinsList.types';
import { PinsListItem } from './PinsListItem';
import { MirnaPinsListItem } from './PinsListItem';
interface PinsListProps {
pinsList: PinItem[];
......@@ -10,22 +8,26 @@ interface PinsListProps {
}
export const PinsList = ({ pinsList, type }: PinsListProps): JSX.Element => {
const dispatch = useAppDispatch();
const onPinClick = (data: BioEntityContent | Drug | Chemical | Mirna): void => {
dispatch(displayEntityDetails(data));
};
return (
<ul className="px-6 py-2">
{pinsList.map(pin => (
<PinsListItem
key={pin.id}
name={pin.name}
type={type}
onClick={(): void => onPinClick(pin.data)}
/>
))}
</ul>
);
switch (type) {
case 'bioEntity':
return <div />;
case 'chemicals':
return <div />;
case 'drugs':
return <div />;
case 'mirna':
return (
<ul className="h-[calc(100vh-198px)] overflow-auto px-6 py-2">
{pinsList.map(result => {
return result.data.targets.map(pin => (
<MirnaPinsListItem key={pin.name} name={pin.name} pin={pin} />
));
})}
</ul>
);
case 'none':
return <div />;
default:
return assertNever(type);
}
};
import { BioEntityContent, Drug, Chemical, Mirna } from '@/types/models';
import { Drug, Chemical, Mirna } from '@/types/models';
export type PinItem = {
id: string | number;
name: string;
data: Drug | Chemical | Mirna | BioEntityContent;
data: Drug | Chemical | Mirna;
};
export type PinType = 'chemicals' | 'drugs' | 'mirna' | 'bioEntity' | 'none';
import { twMerge } from 'tailwind-merge';
import { Icon } from '@/shared/Icon';
import { MirnaItems } from '@/types/models';
import { getPinColor } from './PinsListItem.component.utils';
interface MirnaPinsListItemProps {
name: string;
pin: MirnaItems;
}
export const MirnaPinsListItem = ({ name, pin }: MirnaPinsListItemProps): JSX.Element => {
return (
<div className="mb-4 flex w-full flex-col gap-2 rounded-lg border-[1px] border-solid border-greyscale-500 p-4">
<div className="flex w-full flex-row gap-2">
<Icon name="pin" className={twMerge('mr-2 shrink-0', getPinColor('mirna'))} />
<p className="min-w-fit">Full name: </p>
<p className="w-full font-bold">{name}</p>
</div>
<ul className="leading-6">
<div className="font-bold">Elements:</div>
{pin.targetParticipants.map(element => {
return (
<li key={element.id} className="my-2 px-2">
<a
href={element.link}
target="_blank"
className="cursor-pointer text-primary-500 underline"
>
{element.type} ({element.resource})
</a>
</li>
);
})}
</ul>
<ul className="leading-6">
<div className="font-bold">References:</div>
{pin.references.map(reference => {
return (
<li key={reference.id} className="my-2 px-2">
<a
href={reference.article?.link}
target="_blank"
className="cursor-pointer text-primary-500 underline"
>
{reference.type} ({reference.resource})
</a>
</li>
);
})}
</ul>
</div>
);
};
......@@ -5,7 +5,7 @@ export const getPinColor = (type: PinType): string => {
bioEntity: 'fill-primary-500',
drugs: 'fill-orange',
chemicals: 'fill-purple',
mirna: 'fill-primary-500',
mirna: 'fill-pink',
none: 'none',
};
......
export { PinsListItem } from './PinsListItem.component';
export { MirnaPinsListItem } from './MirnaPinsListItem.component';
......@@ -51,11 +51,12 @@ describe('ResultsList - component ', () => {
expect(screen.getByText('drugs:')).toBeInTheDocument();
expect(screen.getByText('aspirin')).toBeInTheDocument();
const fristDrugName = drugsFixture[0].name;
const secondDrugName = drugsFixture[1].name;
// These tests will be uncommented when list of drugs will be ready
// const fristDrugName = drugsFixture[0].name;
// const secondDrugName = drugsFixture[1].name;
expect(screen.getByText(fristDrugName)).toBeInTheDocument();
expect(screen.getByText(secondDrugName)).toBeInTheDocument();
// expect(screen.getByText(fristDrugName)).toBeInTheDocument();
// expect(screen.getByText(secondDrugName)).toBeInTheDocument();
});
it('should navigate to grouped search results after backward button click', async () => {
const { store } = renderComponent(INITIAL_STATE);
......
......@@ -5,6 +5,16 @@ import { createSelector } from '@reduxjs/toolkit';
export const mirnasSelector = createSelector(rootSelector, state => state.mirnas);
export const loadingMirnasStatusSelector = createSelector(mirnasSelector, state => state.loading);
export const numberOfMirnasSelector = createSelector(mirnasSelector, state =>
state.data ? state.data.length : SIZE_OF_EMPTY_ARRAY,
);
export const numberOfMirnasSelector = createSelector(mirnasSelector, state => {
if (!state.data) {
return SIZE_OF_EMPTY_ARRAY;
}
let numberOfMirnas = 0;
state.data.forEach(element => {
numberOfMirnas += element.targets.length;
});
return numberOfMirnas;
});
......@@ -11,6 +11,7 @@ import { mapModelSchema } from '@/models/modelSchema';
import { organism } from '@/models/organism';
import { overviewImageView } from '@/models/overviewImageView';
import { projectSchema } from '@/models/project';
import { targetSchema } from '@/models/targetSchema';
import { z } from 'zod';
export type Project = z.infer<typeof projectSchema>;
......@@ -22,6 +23,7 @@ export type Organism = z.infer<typeof organism>;
export type Disease = z.infer<typeof disease>;
export type Drug = z.infer<typeof drugSchema>;
export type Mirna = z.infer<typeof mirnaSchema>;
export type MirnaItems = z.infer<typeof targetSchema>;
export type BioEntity = z.infer<typeof bioEntitySchema>;
export type BioEntityContent = z.infer<typeof bioEntityContentSchema>;
export type BioEntityResponse = z.infer<typeof bioEntityResponseSchema>;
......
......@@ -27,6 +27,7 @@ const config: Config = {
divide: '#e1e0e6',
orange: '#f48c40',
purple: '#6400e3',
pink: '#f1009f',
},
height: {
'calc-drawer': 'calc(100% - 104px)',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment