From 9addd9d06447e7938f09c83c83ca82e4a96ab7e1 Mon Sep 17 00:00:00 2001
From: Mateusz Bolewski <mateusz.bolewski@allegro.pl>
Date: Thu, 9 Nov 2023 10:56:41 +0100
Subject: [PATCH] feat(pin): added Mirna pins and details

---
 .prettierrc                                   | 10 ++++
 README.md                                     | 15 +++++
 prettier.config.js                            | 12 ----
 .../MirnaAccordion.component.test.tsx         |  4 +-
 .../PinsList/PinsList.component.test.tsx      | 60 -------------------
 .../PinsList/PinsList.component.tsx           | 46 +++++++-------
 .../ResultsList/PinsList/PinsList.types.tsx   |  4 +-
 .../MirnaPinsListItem.component.tsx           | 53 ++++++++++++++++
 .../PinsListItem.component.utils.ts           |  2 +-
 .../PinsList/PinsListItem/index.ts            |  1 +
 .../ResultsList.component.test.tsx            |  9 +--
 src/redux/mirnas/mirnas.selectors.ts          | 16 ++++-
 src/types/models.ts                           |  2 +
 tailwind.config.ts                            |  1 +
 14 files changed, 129 insertions(+), 106 deletions(-)
 create mode 100644 .prettierrc
 delete mode 100644 prettier.config.js
 delete mode 100644 src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.test.tsx
 create mode 100644 src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/MirnaPinsListItem.component.tsx

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..033fa2fa
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+  "singleQuote": true,
+  "trailingComma": "all",
+  "printWidth": 100,
+  "arrowParens": "avoid",
+  "plugins": ["prettier-plugin-tailwindcss"],
+  "tailwindConfig": "./tailwind.config.ts",
+  "tailwindFunctions": ["twMerge"],
+  "tabWidth": 2
+}
diff --git a/README.md b/README.md
index f4da3c4c..43d15ce2 100644
--- a/README.md
+++ b/README.md
@@ -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:
diff --git a/prettier.config.js b/prettier.config.js
deleted file mode 100644
index eec5dca5..00000000
--- a/prettier.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-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;
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/MirnaAccordion/MirnaAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/MirnaAccordion/MirnaAccordion.component.test.tsx
index 334fce86..3cb87d8b 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/MirnaAccordion/MirnaAccordion.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/MirnaAccordion/MirnaAccordion.component.test.tsx
@@ -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({
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.test.tsx
deleted file mode 100644
index 676b58b4..00000000
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.test.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/* 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);
-  });
-});
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx
index caa8ea04..44bb49c8 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx
@@ -1,8 +1,6 @@
-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);
+  }
 };
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx
index 5369b84d..41f82ada 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx
@@ -1,9 +1,9 @@
-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';
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/MirnaPinsListItem.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/MirnaPinsListItem.component.tsx
new file mode 100644
index 00000000..2bb701d9
--- /dev/null
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/MirnaPinsListItem.component.tsx
@@ -0,0 +1,53 @@
+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>
+  );
+};
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts
index 4e4cb8ee..b5272139 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts
@@ -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',
   };
 
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/index.ts b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/index.ts
index 89b9aebc..bae1522b 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/index.ts
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/index.ts
@@ -1 +1,2 @@
 export { PinsListItem } from './PinsListItem.component';
+export { MirnaPinsListItem } from './MirnaPinsListItem.component';
diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
index 7e310b6b..c6c94821 100644
--- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
+++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/ResultsList.component.test.tsx
@@ -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);
diff --git a/src/redux/mirnas/mirnas.selectors.ts b/src/redux/mirnas/mirnas.selectors.ts
index 51ac4dea..6460f8b8 100644
--- a/src/redux/mirnas/mirnas.selectors.ts
+++ b/src/redux/mirnas/mirnas.selectors.ts
@@ -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;
+});
diff --git a/src/types/models.ts b/src/types/models.ts
index 29327e20..5f7f81bf 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -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>;
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 203d95ea..f63ef947 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -27,6 +27,7 @@ const config: Config = {
         divide: '#e1e0e6',
         orange: '#f48c40',
         purple: '#6400e3',
+        pink: '#f1009f',
       },
       height: {
         'calc-drawer': 'calc(100% - 104px)',
-- 
GitLab