From 46f615a6e21018811bda288f448f0971ca4dbac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com> Date: Mon, 8 Apr 2024 09:51:03 +0200 Subject: [PATCH] feat: add publications list modal download partial --- .../PublicationsModalLayout.component.tsx | 29 ++++++++-- .../utils/getBasePublications.ts | 32 +++++++++++ .../utils/getStandarizedPublications.ts | 30 +++++++++++ .../utils/useDownloadPublicationsAsCSVFile.ts | 54 +++++++++++++++++++ src/types/publications.ts | 9 ++++ 5 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts create mode 100644 src/types/publications.ts diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.tsx index 8d92fe19..9ab85767 100644 --- a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.tsx +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/PublicationsModalLayout.component.tsx @@ -1,11 +1,15 @@ -import { twMerge } from 'tailwind-merge'; +import spinnerIcon from '@/assets/vectors/icons/spinner.svg'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { closeModal } from '@/redux/modal/modal.slice'; -import { Icon } from '@/shared/Icon'; import { filteredSizeSelector } from '@/redux/publications/publications.selectors'; -import { MODAL_ROLE } from './PublicationsModalLayout.constants'; +import { Button } from '@/shared/Button'; +import { Icon } from '@/shared/Icon'; +import Image from 'next/image'; +import { twMerge } from 'tailwind-merge'; import { PublicationsSearch } from '../PublicationsSearch'; +import { MODAL_ROLE } from './PublicationsModalLayout.constants'; +import { useDownloadPublicationsAsCSVFile } from './utils/useDownloadPublicationsAsCSVFile'; type ModalLayoutProps = { children: React.ReactNode; @@ -14,6 +18,7 @@ type ModalLayoutProps = { export const PublicationsModalLayout = ({ children }: ModalLayoutProps): JSX.Element => { const dispatch = useAppDispatch(); const numberOfPublications = useAppSelector(filteredSizeSelector); + const { downloadPublicationsAsCSVFile, isLoading } = useDownloadPublicationsAsCSVFile(); const handleCloseModal = (): void => { dispatch(closeModal()); @@ -27,8 +32,22 @@ export const PublicationsModalLayout = ({ children }: ModalLayoutProps): JSX.Ele <div className="flex h-full w-full items-center justify-center"> <div className={twMerge('flex h-5/6 w-10/12 flex-col overflow-hidden rounded-lg')}> <div className="flex items-center justify-between bg-white p-[24px] text-xl"> - <div className="font-semibold"> - <div>Publications ({numberOfPublications} results)</div> + <div className="flex items-center gap-4"> + <div className="font-semibold"> + <div>Publications ({numberOfPublications} results)</div> + </div> + <Button onClick={downloadPublicationsAsCSVFile} disabled={isLoading}> + {isLoading && ( + <Image + src={spinnerIcon} + alt="spinner icon" + height={12} + width={12} + className="animate-spin" + /> + )} + Download CSV + </Button> </div> <div className="flex flex-row flex-nowrap items-center"> <PublicationsSearch /> diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts new file mode 100644 index 00000000..7725df11 --- /dev/null +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getBasePublications.ts @@ -0,0 +1,32 @@ +import { publicationsResponseSchema } from '@/models/publicationsResponseSchema'; +import { apiPath } from '@/redux/apiPath'; +import { PUBLICATIONS_FETCHING_ERROR_PREFIX } from '@/redux/publications/publications.constatns'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { Publication, PublicationsResponse } from '@/types/models'; +import { getErrorMessage } from '@/utils/getErrorMessage'; +import { showToast } from '@/utils/showToast'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; + +interface Args { + length: number; +} + +export const getBasePublications = async ({ length }: Args): Promise<Publication[]> => { + try { + const response = await axiosInstance.get<PublicationsResponse>( + apiPath.getPublications({ params: { length } }), + ); + + const isDataValid = validateDataUsingZodSchema(response.data, publicationsResponseSchema); + + return isDataValid ? response.data.data : []; + } catch (error) { + const errorMessage = getErrorMessage({ error, prefix: PUBLICATIONS_FETCHING_ERROR_PREFIX }); + showToast({ + type: 'error', + message: errorMessage, + }); + + return []; + } +}; diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts new file mode 100644 index 00000000..061729de --- /dev/null +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/getStandarizedPublications.ts @@ -0,0 +1,30 @@ +import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; +import { Publication } from '@/types/models'; +import { StandarizedPublication } from '@/types/publications'; + +interface Options { + modelNameIdMap: Record<number, string>; +} + +export const getStandarizedPublication = ( + publication: Publication, + options: Options, +): StandarizedPublication => { + const { + publication: { article }, + elements, + } = publication; + const { modelNameIdMap } = options; + const { title, authors, journal, year, pubmedId } = article; + const mainElement = elements?.[FIRST_ARRAY_ELEMENT]; + + return { + pubmedId, + journal, + title, + year: year ? `${year}` : '', + authors: authors.join(','), + modelName: mainElement ? modelNameIdMap[mainElement.modelId] : '', + elementId: '', // TODO: + }; +}; diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts new file mode 100644 index 00000000..306515c5 --- /dev/null +++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModalLayout/utils/useDownloadPublicationsAsCSVFile.ts @@ -0,0 +1,54 @@ +import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { modelsNameMapSelector } from '@/redux/models/models.selectors'; +import { StandarizedPublication } from '@/types/publications'; +import { useState } from 'react'; +import { getBasePublications } from './getBasePublications'; +import { getStandarizedPublication } from './getStandarizedPublications'; + +export type DownloadPublicationsAsCSVFile = () => Promise<void>; + +interface UseDownloadPublicationsAsCSVFileResult { + downloadPublicationsAsCSVFile: DownloadPublicationsAsCSVFile; + isLoading: boolean; +} + +export const useDownloadPublicationsAsCSVFile = (): UseDownloadPublicationsAsCSVFileResult => { + const [isLoading, setIsLoading] = useState<boolean>(false); + const [publications, setPublications] = useState<StandarizedPublication[]>([]); + const modelNameIdMap = useAppSelector(modelsNameMapSelector); + + const getPublicationsAsList = async (): Promise<StandarizedPublication[]> => { + if (publications.length > SIZE_OF_EMPTY_ARRAY) { + return publications; + } + + const basePublications = await getBasePublications({ + length: 100, // TODO: change for number of all elements + }); + + const standarizedPublications = await Promise.all( + basePublications.map(async publication => + getStandarizedPublication(publication, { + modelNameIdMap, + }), + ), + ); + + setPublications(standarizedPublications); + return standarizedPublications; + }; + + const downloadPublicationsAsCSVFile = async (): Promise<void> => { + setIsLoading(true); + const data = await getPublicationsAsList(); + setIsLoading(false); + + console.log({ data }); + }; + + return { + downloadPublicationsAsCSVFile, + isLoading, + }; +}; diff --git a/src/types/publications.ts b/src/types/publications.ts new file mode 100644 index 00000000..a9c9bfb3 --- /dev/null +++ b/src/types/publications.ts @@ -0,0 +1,9 @@ +export interface StandarizedPublication { + pubmedId: string; + year: string; + journal: string; + authors: string; + title: string; + modelName: string; + elementId: string; +} -- GitLab