diff --git a/package.json b/package.json index 8a754dc0eed77ccbde955e6e3a335e05258a463e..935ff798b9226dca2c8f3e711c522f4b27e0ea45 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,8 @@ "check-types": "tsc --pretty --noEmit", "prepare": "husky install", "postinstall": "husky install", - "test": "jest --config ./jest.config.ts --transformIgnorePatterns 'node_modules/(?!@toolz/allow-react)/'", - "test:watch": "jest --watch --config ./jest.config.ts --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|.*\\.mjs$))'", - "test:ci": "jest --config ./jest.config.ts --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false --passWithNoTests --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|color-space|color-rgba|color-parse|.*\\.mjs$))'", + "test": "jest --config ./jest.config.ts --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|color-space|color-rgba|color-parse|.*\\.mjs$))'", + "test:watch": "jest --watch --config ./jest.config.ts --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|color-space|color-rgba|color-parse|.*\\.mjs$))'", "test:coverage": "jest --watchAll --coverage --config ./jest.config.ts --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|.*\\.mjs$))'", "test:coveragee": "jest --coverage --transformIgnorePatterns 'node_modules/(?!(ol|geotiff|quick-lru|.*\\.mjs$))'", "coverage": "open ./coverage/lcov-report/index.html", diff --git a/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.test.tsx deleted file mode 100644 index df05c8e0c5e7fcef43da5d5085d34a3c6ea14f43..0000000000000000000000000000000000000000 --- a/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.test.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import { - InitialStoreState, - getReduxWrapperWithStore, -} from '@/utils/testing/getReduxWrapperWithStore'; -import { StoreType } from '@/redux/store'; -import { statisticsFixture } from '@/models/fixtures/statisticsFixture'; -import { act } from 'react-dom/test-utils'; -import { Annotations } from './Annotations.component'; - -const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { - const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); - - return ( - render( - <Wrapper> - <Annotations /> - </Wrapper>, - ), - { - store, - } - ); -}; - -describe('Annotations - component', () => { - it('should display annotations checkboxes when fetching data is successful', async () => { - renderComponent({ - statistics: { - data: { - ...statisticsFixture, - elementAnnotations: { - compartment: 1, - pathway: 0, - }, - }, - loading: 'succeeded', - error: { - message: '', - name: '', - }, - }, - }); - const navigationButton = screen.getByTestId('accordion-item-button'); - - act(() => { - navigationButton.click(); - }); - - expect(screen.getByText('Select annotations')).toBeInTheDocument(); - - await waitFor(() => { - expect(screen.getByTestId('checkbox-filter')).toBeInTheDocument(); - expect(screen.getByLabelText('compartment')).toBeInTheDocument(); - expect(screen.getByLabelText('search-input')).toBeInTheDocument(); - }); - }); - it('should not display annotations checkboxes when fetching data fails', async () => { - renderComponent({ - statistics: { - data: undefined, - loading: 'failed', - error: { - message: '', - name: '', - }, - }, - }); - expect(screen.getByText('Select annotations')).toBeInTheDocument(); - const navigationButton = screen.getByTestId('accordion-item-button'); - act(() => { - navigationButton.click(); - }); - - expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument(); - }); - it('should not display annotations checkboxes when fetched data is empty object', async () => { - renderComponent({ - statistics: { - data: { - ...statisticsFixture, - elementAnnotations: {}, - }, - loading: 'failed', - error: { - message: '', - name: '', - }, - }, - }); - expect(screen.getByText('Select annotations')).toBeInTheDocument(); - const navigationButton = screen.getByTestId('accordion-item-button'); - act(() => { - navigationButton.click(); - }); - - expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument(); - }); - - it('should display loading message when fetching data is pending', async () => { - renderComponent({ - statistics: { - data: undefined, - loading: 'pending', - error: { - message: '', - name: '', - }, - }, - }); - expect(screen.getByText('Select annotations')).toBeInTheDocument(); - const navigationButton = screen.getByTestId('accordion-item-button'); - act(() => { - navigationButton.click(); - }); - - expect(screen.getByText('Loading...')).toBeInTheDocument(); - }); -}); diff --git a/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.tsx b/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.tsx deleted file mode 100644 index a68fd3894887e2c5fa30733e4388d8f8f76927eb..0000000000000000000000000000000000000000 --- a/src/components/Map/Drawer/ExportDrawer/Annotations/Annotations.component.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { - Accordion, - AccordionItem, - AccordionItemButton, - AccordionItemHeading, - AccordionItemPanel, -} from '@/shared/Accordion'; -import { useAppSelector } from '@/redux/hooks/useAppSelector'; -import { - elementAnnotationsSelector, - loadingStatisticsSelector, -} from '@/redux/statistics/statistics.selectors'; -import { CheckboxFilter } from '../CheckboxFilter'; - -export const Annotations = (): React.ReactNode => { - const loadingStatistics = useAppSelector(loadingStatisticsSelector); - const elementAnnotations = useAppSelector(elementAnnotationsSelector); - const isPending = loadingStatistics === 'pending'; - - const mappedElementAnnotations = elementAnnotations - ? Object.keys(elementAnnotations)?.map(el => ({ id: el, label: el })) - : []; - - return ( - <Accordion allowZeroExpanded> - <AccordionItem> - <AccordionItemHeading> - <AccordionItemButton>Select annotations</AccordionItemButton> - </AccordionItemHeading> - <AccordionItemPanel> - {isPending && <p>Loading...</p>} - {!isPending && mappedElementAnnotations && mappedElementAnnotations.length > 0 && ( - <CheckboxFilter options={mappedElementAnnotations} /> - )} - </AccordionItemPanel> - </AccordionItem> - </Accordion> - ); -}; diff --git a/src/components/Map/Drawer/ExportDrawer/CheckboxFilter/CheckboxFilter.component.tsx b/src/components/Map/Drawer/ExportDrawer/CheckboxFilter/CheckboxFilter.component.tsx index 68dbe9c6ecf1ea715852925c85e2f2d18b3d4ad2..54ac8df4fc0f8634d21a474a3d4cfca07b239434 100644 --- a/src/components/Map/Drawer/ExportDrawer/CheckboxFilter/CheckboxFilter.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/CheckboxFilter/CheckboxFilter.component.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react'; import lensIcon from '@/assets/vectors/icons/lens.svg'; import { twMerge } from 'tailwind-merge'; -type CheckboxItem = { id: string; label: string }; +export type CheckboxItem = { id: string; label: string }; type CheckboxFilterProps = { options: CheckboxItem[]; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/index.ts b/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/index.ts deleted file mode 100644 index 3b82aaf76f1b363c9cc2429bbe05d17938bdac29..0000000000000000000000000000000000000000 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Annotations } from './Annotations.component'; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.component.tsx b/src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.component.tsx deleted file mode 100644 index c6d8084fe24299b8f13a6fc07dc1aa6019188646..0000000000000000000000000000000000000000 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.component.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { CheckboxFilter } from '../../CheckboxFilter'; -import { CollapsibleSection } from '../../CollapsibleSection'; -import { COLUMNS } from './Columns.constants'; - -export const Columns = (): React.ReactNode => ( - <CollapsibleSection title="Select column"> - <CheckboxFilter options={COLUMNS} isSearchEnabled={false} /> - </CollapsibleSection> -); diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.component.tsx b/src/components/Map/Drawer/ExportDrawer/Elements/Elements.component.tsx index ffc3bf45e09d8512073f41cfa3b858c6627b5c66..fcf01b6a0c8fd6cae6b847e245f70e3d072508e7 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/Elements/Elements.component.tsx @@ -1,31 +1,16 @@ -import { useEffect } from 'react'; -import { useAppSelector } from '@/redux/hooks/useAppSelector'; -import { modelsDataSelector } from '@/redux/models/models.selectors'; -import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; -import { getCompartmentPathways } from '@/redux/compartmentPathways/compartmentPathways.thunks'; -import { Annotations } from '../Annotations'; -import { Types } from './Types'; -import { IncludedCompartmentPathways } from './IncludedCompartmentPathways '; -import { ExcludedCompartmentPathways } from './ExcludedCompartmentPathways'; -import { Columns } from './Columns'; -import { getModelsIds } from './Elements.utils'; +import { Export } from '../ExportCompound'; export const Elements = (): React.ReactNode => { - const models = useAppSelector(modelsDataSelector); - const dispatch = useAppDispatch(); - - useEffect(() => { - const modelsIds = getModelsIds(models); - dispatch(getCompartmentPathways(modelsIds)); - }, [dispatch, models]); - return ( <div data-testid="elements-tab"> - <Types /> - <Columns /> - <Annotations /> - <IncludedCompartmentPathways /> - <ExcludedCompartmentPathways /> + <Export> + <Export.Types /> + <Export.Columns /> + <Export.Annotations /> + <Export.IncludedCompartmentPathways /> + <Export.ExcludedCompartmentPathways /> + <Export.DownloadElements /> + </Export> </div> ); }; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.ts b/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.ts index 25c681809836f5f8fa2fb66b2d7e5dae79707fe1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.ts +++ b/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.ts @@ -1,42 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { CompartmentPathwayDetails, MapModel } from '@/types/models'; - -type AddedNames = { [key: string]: number }; - -type CheckboxElement = { id: string; label: string }; - -type CheckboxElements = CheckboxElement[]; - -export const getCompartmentPathwaysCheckboxElements = ( - items: CompartmentPathwayDetails[], -): CheckboxElements => { - const addedNames: AddedNames = {}; - - const setNameToIdIfUndefined = (item: CompartmentPathwayDetails): void => { - if (addedNames[item.name] === undefined) { - addedNames[item.name] = item.id; - } - }; - - items.forEach(setNameToIdIfUndefined); - - const parseIdAndLabel = ([name, id]: [name: string, id: number]): CheckboxElement => ({ - id: id.toString(), - label: name, - }); - - const sortByLabel = (a: CheckboxElement, b: CheckboxElement): number => { - if (a.label > b.label) return 1; - return -1; - }; - - const elements = Object.entries(addedNames).map(parseIdAndLabel).sort(sortByLabel); - - return elements; -}; - -export const getModelsIds = (models: MapModel[] | undefined): number[] => { - if (!models) return []; - - return models.map(model => model.idObject); -}; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/Annotations.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/Annotations.component.test.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Annotations/Annotations.component.test.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/Annotations.component.test.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/Annotations.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/Annotations.component.tsx similarity index 73% rename from src/components/Map/Drawer/ExportDrawer/Elements/Annotations/Annotations.component.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/Annotations.component.tsx index f3795e9b9f5c9828957e6691c5427fd42deacd2d..6f7034f871d9034f2a038493d426d6784d552b02 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Annotations/Annotations.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/Annotations.component.tsx @@ -1,13 +1,16 @@ -/* eslint-disable no-magic-numbers */ +import { useContext } from 'react'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { elementAnnotationsSelector, loadingStatisticsSelector, } from '@/redux/statistics/statistics.selectors'; +import { ZERO } from '@/constants/common'; import { CheckboxFilter } from '../../CheckboxFilter'; import { CollapsibleSection } from '../../CollapsibleSection'; +import { ExportContext } from '../ExportCompound.context'; export const Annotations = (): React.ReactNode => { + const { setAnnotations } = useContext(ExportContext); const loadingStatistics = useAppSelector(loadingStatisticsSelector); const elementAnnotations = useAppSelector(elementAnnotationsSelector); const isPending = loadingStatistics === 'pending'; @@ -19,8 +22,8 @@ export const Annotations = (): React.ReactNode => { return ( <CollapsibleSection title="Select annotations"> {isPending && <p>Loading...</p>} - {!isPending && mappedElementAnnotations && mappedElementAnnotations.length > 0 && ( - <CheckboxFilter options={mappedElementAnnotations} /> + {!isPending && mappedElementAnnotations && mappedElementAnnotations.length > ZERO && ( + <CheckboxFilter options={mappedElementAnnotations} onCheckedChange={setAnnotations} /> )} </CollapsibleSection> ); diff --git a/src/components/Map/Drawer/ExportDrawer/Annotations/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/index.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Annotations/index.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Annotations/index.ts diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.component.test.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.component.test.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.component.test.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..954a4c60a4354f675d4c7bab265f45d69c384039 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.component.tsx @@ -0,0 +1,15 @@ +import { useContext } from 'react'; +import { CheckboxFilter } from '../../CheckboxFilter'; +import { CollapsibleSection } from '../../CollapsibleSection'; +import { COLUMNS } from './Columns.constants'; +import { ExportContext } from '../ExportCompound.context'; + +export const Columns = (): React.ReactNode => { + const { setColumns } = useContext(ExportContext); + + return ( + <CollapsibleSection title="Select column"> + <CheckboxFilter options={COLUMNS} isSearchEnabled={false} onCheckedChange={setColumns} /> + </CollapsibleSection> + ); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.constants.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.constants.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Columns/Columns.constants.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/Columns.constants.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Columns/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/index.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Columns/index.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Columns/index.ts diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadElements/DownloadElements.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadElements/DownloadElements.tsx new file mode 100644 index 0000000000000000000000000000000000000000..53276caa74a5bd4b0851f3b189df50e79a2a53f3 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadElements/DownloadElements.tsx @@ -0,0 +1,13 @@ +import { useContext } from 'react'; +import { Button } from '@/shared/Button'; +import { ExportContext } from '../ExportCompound.context'; + +export const DownloadElements = (): React.ReactNode => { + const { handleDownloadElements } = useContext(ExportContext); + + return ( + <div className="mt-6"> + <Button onClick={handleDownloadElements}>Download</Button> + </div> + ); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadElements/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadElements/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadNetwork/DownloadNetwork.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadNetwork/DownloadNetwork.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbe769f0877561ab755049749e12577e13c1b005 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadNetwork/DownloadNetwork.tsx @@ -0,0 +1,13 @@ +import { useContext } from 'react'; +import { Button } from '@/shared/Button'; +import { ExportContext } from '../ExportCompound.context'; + +export const DownloadElements = (): React.ReactNode => { + const { handleDownloadNetwork } = useContext(ExportContext); + + return ( + <div className="mt-6"> + <Button onClick={handleDownloadNetwork}>Download</Button> + </div> + ); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadNetwork/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/DownloadNetwork/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.test.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.test.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.test.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx similarity index 65% rename from src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx index 73e95759251ec96571f7f7d88eb84dcccf3b6389..e6f8988d51e455e6a4b154a9e8614e97b99db966 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/ExcludedCompartmentPathways.component.tsx @@ -1,24 +1,32 @@ -/* eslint-disable no-magic-numbers */ +import { useContext } from 'react'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { compartmentPathwaysDataSelector, loadingCompartmentPathwaysSelector, } from '@/redux/compartmentPathways/compartmentPathways.selectors'; +import { ZERO } from '@/constants/common'; import { CheckboxFilter } from '../../CheckboxFilter'; import { CollapsibleSection } from '../../CollapsibleSection'; -import { getCompartmentPathwaysCheckboxElements } from '../Elements.utils'; +import { ExportContext } from '../ExportCompound.context'; +import { getCompartmentPathwaysCheckboxElements } from '../ExportCompound.utils'; export const ExcludedCompartmentPathways = (): React.ReactNode => { + const { setExcludedCompartmentPathways } = useContext(ExportContext); const loadingCompartmentPathways = useAppSelector(loadingCompartmentPathwaysSelector); const isPending = loadingCompartmentPathways === 'pending'; const compartmentPathways = useAppSelector(compartmentPathwaysDataSelector); const checkboxElements = getCompartmentPathwaysCheckboxElements(compartmentPathways); - const isCheckboxFilterVisible = !isPending && checkboxElements && checkboxElements.length > 0; + const isCheckboxFilterVisible = !isPending && checkboxElements && checkboxElements.length > ZERO; return ( <CollapsibleSection title="Select excluded compartment / pathways"> {isPending && <p>Loading...</p>} - {isCheckboxFilterVisible && <CheckboxFilter options={checkboxElements} />} + {isCheckboxFilterVisible && ( + <CheckboxFilter + options={checkboxElements} + onCheckedChange={setExcludedCompartmentPathways} + /> + )} </CollapsibleSection> ); }; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/index.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/ExcludedCompartmentPathways/index.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/ExcludedCompartmentPathways/index.ts diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4d4ca42f66701e74275d98a40710b8712a318eb5 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx @@ -0,0 +1,76 @@ +import { ReactNode, useCallback, useMemo, useState } from 'react'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { modelsIdsSelector } from '@/redux/models/models.selectors'; +import { CheckboxItem } from '../CheckboxFilter/CheckboxFilter.component'; +import { Types } from './Types'; +import { Columns } from './Columns'; +import { Annotations } from './Annotations'; +import { ExcludedCompartmentPathways } from './ExcludedCompartmentPathways'; +import { IncludedCompartmentPathways } from './IncludedCompartmentPathways '; +import { DownloadElements } from './DownloadElements/DownloadElements'; +import { ExportContext } from './ExportCompound.context'; +import { + getDownloadElementsBodyRequest, + getNetworkDownloadBodyRequest, +} from './ExportCompound.utils'; + +type ExportProps = { + children: ReactNode; +}; + +export const Export = ({ children }: ExportProps): JSX.Element => { + const [types, setTypes] = useState<CheckboxItem[]>([]); + const [columns, setColumns] = useState<CheckboxItem[]>([]); + const [annotations, setAnnotations] = useState<CheckboxItem[]>([]); + const modelIds = useAppSelector(modelsIdsSelector); + const [includedCompartmentPathways, setIncludedCompartmentPathways] = useState<CheckboxItem[]>( + [], + ); + const [excludedCompartmentPathways, setExcludedCompartmentPathways] = useState<CheckboxItem[]>( + [], + ); + + const handleDownloadElements = useCallback(() => { + getDownloadElementsBodyRequest({ + types, + columns, + modelIds, + annotations, + includedCompartmentPathways, + excludedCompartmentPathways, + }); + }, [ + types, + columns, + modelIds, + annotations, + includedCompartmentPathways, + excludedCompartmentPathways, + ]); + + const handleDownloadNetwork = useCallback(() => { + getNetworkDownloadBodyRequest(); + }, []); + + const globalContextValue = useMemo( + () => ({ + setTypes, + setColumns, + setAnnotations, + setIncludedCompartmentPathways, + setExcludedCompartmentPathways, + handleDownloadElements, + handleDownloadNetwork, + }), + [handleDownloadElements, handleDownloadNetwork], + ); + + return <ExportContext.Provider value={globalContextValue}>{children}</ExportContext.Provider>; +}; + +Export.Types = Types; +Export.Columns = Columns; +Export.Annotations = Annotations; +Export.IncludedCompartmentPathways = IncludedCompartmentPathways; +Export.ExcludedCompartmentPathways = ExcludedCompartmentPathways; +Export.DownloadElements = DownloadElements; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.context.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.context.ts new file mode 100644 index 0000000000000000000000000000000000000000..3490162eb61f13c9ddbf4f2a13d732693e98d003 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.context.ts @@ -0,0 +1,22 @@ +import { createContext } from 'react'; +import { CheckboxItem } from '../CheckboxFilter/CheckboxFilter.component'; + +export type ExportContextType = { + setTypes: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + setColumns: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + setAnnotations: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + setIncludedCompartmentPathways: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + setExcludedCompartmentPathways: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + handleDownloadElements: () => void; + handleDownloadNetwork: () => void; +}; + +export const ExportContext = createContext<ExportContextType>({ + setTypes: () => {}, + setColumns: () => {}, + setAnnotations: () => {}, + setIncludedCompartmentPathways: () => {}, + setExcludedCompartmentPathways: () => {}, + handleDownloadElements: () => {}, + handleDownloadNetwork: () => {}, +}); diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.test.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.test.ts similarity index 74% rename from src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.test.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.test.ts index 76d374425476c156e0d2c9d8b5c4a8e2725ce4ad..4a76f7813252aa83980004f5a44593a0dd290600 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Elements.utils.test.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.test.ts @@ -1,7 +1,6 @@ /* eslint-disable no-magic-numbers */ import { CompartmentPathwayDetails } from '@/types/models'; -import { modelsFixture } from '@/models/fixtures/modelsFixture'; -import { getCompartmentPathwaysCheckboxElements, getModelsIds } from './Elements.utils'; +import { getCompartmentPathwaysCheckboxElements } from './ExportCompound.utils'; describe('getCompartmentPathwaysCheckboxElements', () => { it('should return an empty array when given an empty items array', () => { @@ -45,17 +44,3 @@ describe('getCompartmentPathwaysCheckboxElements', () => { ]); }); }); - -const MODELS_IDS = modelsFixture.map(item => item.idObject); - -describe('getModelsIds', () => { - it('should return an empty array if models are not provided', () => { - const result = getModelsIds(undefined); - expect(result).toEqual([]); - }); - - it('should return an array of model IDs', () => { - const result = getModelsIds(modelsFixture); - expect(result).toEqual(MODELS_IDS); - }); -}); diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6d79ecef11234a89cca51ed5bbb310a2df502f3 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.utils.ts @@ -0,0 +1,73 @@ +/* eslint-disable no-magic-numbers */ +import { CompartmentPathwayDetails } from '@/types/models'; +import { CheckboxItem } from '../CheckboxFilter/CheckboxFilter.component'; + +type DownloadBodyRequest = { + types: string[]; + columns: string[]; + submaps: number[]; + annotations: string[]; + includedCompartmentIds: string[]; + excludedCompartmentIds: string[]; +}; + +type GetDownloadBodyRequestProps = { + types: CheckboxItem[]; + columns: CheckboxItem[]; + modelIds: number[]; + annotations: CheckboxItem[]; + includedCompartmentPathways: CheckboxItem[]; + excludedCompartmentPathways: CheckboxItem[]; +}; + +export const getDownloadElementsBodyRequest = ({ + types, + columns, + modelIds, + annotations, + includedCompartmentPathways, + excludedCompartmentPathways, +}: GetDownloadBodyRequestProps): DownloadBodyRequest => ({ + types: types.map(type => type.label), + columns: columns.map(column => column.label), + submaps: modelIds, + annotations: annotations.map(annotation => annotation.label), + includedCompartmentIds: includedCompartmentPathways.map(compartment => compartment.label), + excludedCompartmentIds: excludedCompartmentPathways.map(compartment => compartment.label), +}); + +export const getNetworkDownloadBodyRequest = (): object => ({}); + +type AddedNames = { [key: string]: number }; + +type CheckboxElement = { id: string; label: string }; + +type CheckboxElements = CheckboxElement[]; + +export const getCompartmentPathwaysCheckboxElements = ( + items: CompartmentPathwayDetails[], +): CheckboxElements => { + const addedNames: AddedNames = {}; + + const setNameToIdIfUndefined = (item: CompartmentPathwayDetails): void => { + if (addedNames[item.name] === undefined) { + addedNames[item.name] = item.id; + } + }; + + items.forEach(setNameToIdIfUndefined); + + const parseIdAndLabel = ([name, id]: [name: string, id: number]): CheckboxElement => ({ + id: id.toString(), + label: name, + }); + + const sortByLabel = (a: CheckboxElement, b: CheckboxElement): number => { + if (a.label > b.label) return 1; + return -1; + }; + + const elements = Object.entries(addedNames).map(parseIdAndLabel).sort(sortByLabel); + + return elements; +}; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /IncludedCompartmentPathways.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /IncludedCompartmentPathways.component.test.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /IncludedCompartmentPathways.component.test.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /IncludedCompartmentPathways.component.test.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx similarity index 68% rename from src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx index 77a64aed273ac294c018bc23197b1a1cd4036b0a..ff651ed8cf16442d6efe8f02aa2eaa73f707b2a0 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /IncludedCompartmentPathways.component.tsx @@ -1,14 +1,17 @@ -/* eslint-disable no-magic-numbers */ +import { useContext } from 'react'; import { compartmentPathwaysDataSelector, loadingCompartmentPathwaysSelector, } from '@/redux/compartmentPathways/compartmentPathways.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { ZERO } from '@/constants/common'; import { CheckboxFilter } from '../../CheckboxFilter'; import { CollapsibleSection } from '../../CollapsibleSection'; -import { getCompartmentPathwaysCheckboxElements } from '../Elements.utils'; +import { ExportContext } from '../ExportCompound.context'; +import { getCompartmentPathwaysCheckboxElements } from '../ExportCompound.utils'; export const IncludedCompartmentPathways = (): React.ReactNode => { + const { setIncludedCompartmentPathways } = useContext(ExportContext); const loadingCompartmentPathways = useAppSelector(loadingCompartmentPathwaysSelector); const isPending = loadingCompartmentPathways === 'pending'; const compartmentPathways = useAppSelector(compartmentPathwaysDataSelector); @@ -17,8 +20,11 @@ export const IncludedCompartmentPathways = (): React.ReactNode => { return ( <CollapsibleSection title="Select included compartment / pathways"> {isPending && <p>Loading...</p>} - {!isPending && checkboxElements && checkboxElements.length > 0 && ( - <CheckboxFilter options={checkboxElements} /> + {!isPending && checkboxElements && checkboxElements.length > ZERO && ( + <CheckboxFilter + options={checkboxElements} + onCheckedChange={setIncludedCompartmentPathways} + /> )} </CollapsibleSection> ); diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /index.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/IncludedCompartmentPathways /index.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/IncludedCompartmentPathways /index.ts diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.component.test.tsx similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.component.test.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.component.test.tsx diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.component.tsx similarity index 64% rename from src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.component.tsx rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.component.tsx index 0c37bdf6907d676eeaed5182c5002710327ff13c..9398790028d9a1e60cbaeee9bf85014523839570 100644 --- a/src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.component.tsx @@ -1,16 +1,25 @@ +import { useContext } from 'react'; import { elementTypesSelector } from '@/redux/configuration/configuration.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { getCheckboxElements } from './Types.utils'; import { CheckboxFilter } from '../../CheckboxFilter'; import { CollapsibleSection } from '../../CollapsibleSection'; +import { ExportContext } from '../ExportCompound.context'; export const Types = (): React.ReactNode => { + const { setTypes } = useContext(ExportContext); const elementTypes = useAppSelector(elementTypesSelector); const checkboxElements = getCheckboxElements(elementTypes); return ( <CollapsibleSection title="Select types"> - {checkboxElements && <CheckboxFilter options={checkboxElements} isSearchEnabled={false} />} + {checkboxElements && ( + <CheckboxFilter + options={checkboxElements} + isSearchEnabled={false} + onCheckedChange={setTypes} + /> + )} </CollapsibleSection> ); }; diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.utils.test.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.utils.test.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.utils.test.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.utils.test.ts diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.utils.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.utils.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Types/Types.utils.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/Types.utils.ts diff --git a/src/components/Map/Drawer/ExportDrawer/Elements/Types/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/index.ts similarity index 100% rename from src/components/Map/Drawer/ExportDrawer/Elements/Types/index.ts rename to src/components/Map/Drawer/ExportDrawer/ExportCompound/Types/index.ts diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc9ebec49c0de9036d71209be308feaecd41229c --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/index.ts @@ -0,0 +1 @@ +export { Export } from './ExportCompound.component'; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.tsx index 068348d1f068bbe22bd14e16b430a1948fee3453..dc308f3a50e8fce4094598be8785ba0e42418d9d 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.tsx @@ -1,23 +1,38 @@ +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; import { DrawerHeading } from '@/shared/DrawerHeading'; -import { useState } from 'react'; +import { getCompartmentPathways } from '@/redux/compartmentPathways/compartmentPathways.thunks'; +import { useEffect, useState } from 'react'; import { TabNavigator } from './TabNavigator'; import { Elements } from './Elements'; import { TAB_NAMES } from './TabNavigator/TabNavigator.constants'; import { TabNames } from './TabNavigator/TabNavigator.types'; +import { Network } from './Network'; +import { getModelsIds } from './ExportDrawer.component.utils'; export const ExportDrawer = (): React.ReactNode => { + const models = useAppSelector(modelsDataSelector); + const dispatch = useAppDispatch(); const [activeTab, setActiveTab] = useState<TabNames>(TAB_NAMES.ELEMENTS); const handleTabChange = (tabName: TabNames): void => { setActiveTab(tabName); }; + useEffect(() => { + const modelsIds = getModelsIds(models); + dispatch(getCompartmentPathways(modelsIds)); + }, [dispatch, models]); + return ( <div data-testid="export-drawer" className="h-full max-h-full"> <DrawerHeading title="Export" /> <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto px-6"> <TabNavigator activeTab={activeTab} onTabChange={handleTabChange} /> {activeTab === TAB_NAMES.ELEMENTS && <Elements />} + {activeTab === TAB_NAMES.NETWORK && <Network />} + {activeTab === TAB_NAMES.GRAPHICS && <div>Graphics</div>} </div> </div> ); diff --git a/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.utils.tsx b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.utils.tsx new file mode 100644 index 0000000000000000000000000000000000000000..20e5e8711bc36f2a2c962bf1a599296c59e6f408 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.component.utils.tsx @@ -0,0 +1,7 @@ +import { MapModel } from '@/types/models'; + +export const getModelsIds = (models: MapModel[] | undefined): number[] => { + if (!models) return []; + + return models.map(model => model.idObject); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportDrawer.utils.test.ts b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.utils.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee1aa52f9e3194b17cbdfddc9b963357211afc37 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportDrawer.utils.test.ts @@ -0,0 +1,16 @@ +import { modelsFixture } from '@/models/fixtures/modelsFixture'; +import { getModelsIds } from './ExportDrawer.component.utils'; + +const MODELS_IDS = modelsFixture.map(item => item.idObject); + +describe('getModelsIds', () => { + it('should return an empty array if models are not provided', () => { + const result = getModelsIds(undefined); + expect(result).toEqual([]); + }); + + it('should return an array of model IDs', () => { + const result = getModelsIds(modelsFixture); + expect(result).toEqual(MODELS_IDS); + }); +}); diff --git a/src/components/Map/Drawer/ExportDrawer/Network/Network.component.tsx b/src/components/Map/Drawer/ExportDrawer/Network/Network.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1438a0851f3581298f226532ff874bf44821a1b6 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/Network/Network.component.tsx @@ -0,0 +1,16 @@ +import { Export } from '../ExportCompound'; + +export const Network = (): React.ReactNode => { + return ( + <div data-testid="export-tab"> + <Export> + <Export.Types /> + <Export.Columns /> + <Export.Annotations /> + <Export.IncludedCompartmentPathways /> + <Export.ExcludedCompartmentPathways /> + <Export.DownloadElements /> + </Export> + </div> + ); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/Network/index.ts b/src/components/Map/Drawer/ExportDrawer/Network/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d64c4bfc39b924fccc516b5138431f7034a53ba --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/Network/index.ts @@ -0,0 +1 @@ +export { Network } from './Network.component'; diff --git a/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts b/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts index 2c2639032a77c6798b6ec8c8b648f4927a135e21..037eefa24fe80a7a82141bdedf7ce5c757e0b9b6 100644 --- a/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts +++ b/src/redux/compartmentPathways/compartmentPathways.reducers.test.ts @@ -10,7 +10,7 @@ import { compartmentPathwaysFixture, compartmentPathwaysOverLimitFixture, } from '@/models/fixtures/compartmentPathways'; -import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/Elements/Elements.utils'; +import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/ExportDrawer.component.utils'; import { apiPath } from '../apiPath'; import compartmentPathwaysReducer from './compartmentPathways.slice'; import { CompartmentPathwaysState } from './compartmentPathways.types'; diff --git a/src/redux/compartmentPathways/compartmentPathways.thunks.test.ts b/src/redux/compartmentPathways/compartmentPathways.thunks.test.ts index b2d801e304562f0a8da4aecd402b9b3aa0bfe906..407eb70d6a20ff4abb36a2408b2a0eb388c084b3 100644 --- a/src/redux/compartmentPathways/compartmentPathways.thunks.test.ts +++ b/src/redux/compartmentPathways/compartmentPathways.thunks.test.ts @@ -10,7 +10,7 @@ import { compartmentPathwaysFixture, compartmentPathwaysOverLimitFixture, } from '@/models/fixtures/compartmentPathways'; -import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/Elements/Elements.utils'; +import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/ExportDrawer.component.utils'; import { apiPath } from '../apiPath'; import compartmentPathwaysReducer from './compartmentPathways.slice'; import { CompartmentPathwaysState } from './compartmentPathways.types';