From 9bc5bcee9a969824c0bc03065da4a71232d4c903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 10 Jun 2024 10:10:09 -0300 Subject: [PATCH 01/48] chore: remove log of web vitals --- src/index.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index acdea4ba3..ba839fec8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' -import reportWebVitals from './reportWebVitals' import './i18n' import { LoadingProvider } from './sections/loading/LoadingProvider' import { ThemeProvider } from '@iqss/dataverse-design-system' @@ -18,8 +17,3 @@ root.render( ) - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(console.log) From 055b63ee72dd368cc8c401c6ff0048068071ae0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 10 Jun 2024 10:56:59 -0300 Subject: [PATCH 02/48] feat: routing --- src/Router.tsx | 4 ++++ src/sections/Route.enum.ts | 1 + .../edit-dataset-menu/EditDatasetMenu.tsx | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/src/Router.tsx b/src/Router.tsx index f7c2feaba..060b695c1 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -36,6 +36,10 @@ const router = createBrowserRouter( path: Route.UPLOAD_DATASET_FILES, element: UploadDatasetFilesFactory.create() }, + { + path: Route.EDIT_DATASET_METADATA, + element:
Edit dataset metadata
+ }, { path: Route.FILES, element: FileFactory.create() diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index bdce8e57d..ab6c4c0d4 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -6,6 +6,7 @@ export enum Route { DATASETS = '/datasets', CREATE_DATASET = '/datasets/create', UPLOAD_DATASET_FILES = '/datasets/upload-files', + EDIT_DATASET_METADATA = '/datasets/edit-metadata', FILES = '/files', COLLECTIONS = '/collections' } diff --git a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx index 7df59e0f1..bdee13419 100644 --- a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx @@ -33,6 +33,10 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { navigate(`${Route.UPLOAD_DATASET_FILES}?persistentId=${dataset.persistentId}`) return } + if (eventKey === EditDatasetMenuItems.METADATA) { + navigate(`${Route.EDIT_DATASET_METADATA}?persistentId=${dataset.persistentId}`) + return + } showModal() } From 7b02217cd024abe72bf14ad887fe1393af79cdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 10 Jun 2024 15:11:50 -0300 Subject: [PATCH 03/48] feat: basic view --- public/locales/en/editDatasetMetadata.json | 4 ++ src/Router.tsx | 3 +- .../EditDatasetMetadata.tsx | 44 +++++++++++++++++++ .../EditDatasetMetadataFactory.tsx | 26 +++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 public/locales/en/editDatasetMetadata.json create mode 100644 src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx create mode 100644 src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx diff --git a/public/locales/en/editDatasetMetadata.json b/public/locales/en/editDatasetMetadata.json new file mode 100644 index 000000000..3f67500b5 --- /dev/null +++ b/public/locales/en/editDatasetMetadata.json @@ -0,0 +1,4 @@ +{ + "pageTitle": "Edit Dataset Metadata", + "breadcrumbActionItem": "Edit Metadata" +} diff --git a/src/Router.tsx b/src/Router.tsx index 060b695c1..f44267638 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -7,6 +7,7 @@ import { CreateDatasetFactory } from './sections/create-dataset/CreateDatasetFac import { FileFactory } from './sections/file/FileFactory' import { CollectionFactory } from './sections/collection/CollectionFactory' import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/UploadDatasetFilesFactory' +import { EditDatasetMetadataFactory } from './sections/edit-dataset-metadata/EditDatasetMetadataFactory' import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' const router = createBrowserRouter( @@ -38,7 +39,7 @@ const router = createBrowserRouter( }, { path: Route.EDIT_DATASET_METADATA, - element:
Edit dataset metadata
+ element: EditDatasetMetadataFactory.create() }, { path: Route.FILES, diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx new file mode 100644 index 000000000..f93112613 --- /dev/null +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from 'react-i18next' +import { useDataset } from '../dataset/DatasetContext' +import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' +import { useEffect } from 'react' +import { useLoading } from '../loading/LoadingContext' +import { PageNotFound } from '../page-not-found/PageNotFound' + +interface EditDatasetMetadataProps { + somethingElse?: string +} + +export const EditDatasetMetadata = ({ somethingElse }: EditDatasetMetadataProps) => { + const { t } = useTranslation('editDatasetMetadata') + const { dataset, isLoading } = useDataset() + const { setIsLoading } = useLoading() + + useEffect(() => { + setIsLoading(isLoading) + }, [isLoading, setIsLoading]) + + if (isLoading) { + return

Loading...

+ } + + return ( + <> + {!dataset ? ( + + ) : ( +
+ +

Edit Dataset Metadata page

+

+ Persistent Id is :{dataset.persistentId} +

+
+ )} + + ) +} diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx new file mode 100644 index 000000000..c184a3e92 --- /dev/null +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx @@ -0,0 +1,26 @@ +import { ReactElement } from 'react' +import { useSearchParams } from 'react-router-dom' +import { EditDatasetMetadata } from './EditDatasetMetadata' +import { DatasetProvider } from '../dataset/DatasetProvider' +import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository' +// import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' + +const datasetRepository = new DatasetJSDataverseRepository() +// const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() + +export class EditDatasetMetadataFactory { + static create(): ReactElement { + return + } +} + +function EditDatasetMetadataWithParams() { + const [searchParams] = useSearchParams() + const persistentId = searchParams.get('persistentId') ?? undefined + + return ( + + + + ) +} From f20697e11cd0ed54f7c5671ce458820ee4d16c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 10 Jun 2024 17:14:05 -0300 Subject: [PATCH 04/48] feat: basic layout --- public/locales/en/editDatasetMetadata.json | 13 ++++++- .../EditDatasetMetadata.module.scss | 3 ++ .../EditDatasetMetadata.tsx | 39 ++++++++++++++----- .../edit-dataset-metadata/HostCollection.tsx | 30 ++++++++++++++ 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 src/sections/edit-dataset-metadata/EditDatasetMetadata.module.scss create mode 100644 src/sections/edit-dataset-metadata/HostCollection.tsx diff --git a/public/locales/en/editDatasetMetadata.json b/public/locales/en/editDatasetMetadata.json index 3f67500b5..189f80de9 100644 --- a/public/locales/en/editDatasetMetadata.json +++ b/public/locales/en/editDatasetMetadata.json @@ -1,4 +1,13 @@ { - "pageTitle": "Edit Dataset Metadata", - "breadcrumbActionItem": "Edit Metadata" + "breadcrumbActionItem": "Edit Metadata", + "infoAlert": { + "heading": "Edit Dataset Metadata", + "text": "Add more metadata about this dataset to help others easily find it." + }, + "hostCollection": { + "label": "Host Collection", + "description": "The collection which contains this dataset." + }, + "metadata": "Metadata", + "requiredFields": "Asterisks indicate required fields" } diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.module.scss b/src/sections/edit-dataset-metadata/EditDatasetMetadata.module.scss new file mode 100644 index 000000000..b255228a9 --- /dev/null +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadata.module.scss @@ -0,0 +1,3 @@ +.tab-container { + padding: 1em 0; +} diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx index f93112613..8d857a367 100644 --- a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx @@ -1,15 +1,19 @@ +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { Alert, Tabs } from '@iqss/dataverse-design-system' import { useDataset } from '../dataset/DatasetContext' -import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' -import { useEffect } from 'react' import { useLoading } from '../loading/LoadingContext' import { PageNotFound } from '../page-not-found/PageNotFound' +import { HostCollection } from './HostCollection' +import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' +import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' +import styles from './EditDatasetMetadata.module.scss' interface EditDatasetMetadataProps { - somethingElse?: string + who?: string } -export const EditDatasetMetadata = ({ somethingElse }: EditDatasetMetadataProps) => { +export const EditDatasetMetadata = ({ who: _who }: EditDatasetMetadataProps) => { const { t } = useTranslation('editDatasetMetadata') const { dataset, isLoading } = useDataset() const { setIsLoading } = useLoading() @@ -27,17 +31,32 @@ export const EditDatasetMetadata = ({ somethingElse }: EditDatasetMetadataProps) {!dataset ? ( ) : ( -
+ <> -

Edit Dataset Metadata page

-

- Persistent Id is :{dataset.persistentId} -

-
+ + {t('infoAlert.text')} + + + + + +
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi saepe iure + consequatur asperiores doloribus sapiente eius aspernatur hic officia illum + veritatis dolor molestiae ex perspiciatis accusamus ipsam minus animi, minima + molestias natus quaerat aliquam ea. Eius nisi ut hic laudantium quam, veniam porro + consequatur iure animi tenetur quod incidunt error harum debitis, pariatur + temporibus ipsa nobis cupiditate quas nihil rerum distinctio ullam! Fugit amet ipsum + omnis veniam, ad neque inventore. Laboriosam maiores autem officiis, aut atque natus + fugit. +
+
+
+ )} ) diff --git a/src/sections/edit-dataset-metadata/HostCollection.tsx b/src/sections/edit-dataset-metadata/HostCollection.tsx new file mode 100644 index 000000000..184e114c4 --- /dev/null +++ b/src/sections/edit-dataset-metadata/HostCollection.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from 'react-i18next' +import { Col, Form } from '@iqss/dataverse-design-system' +import { Dataset } from '../../dataset/domain/models/Dataset' + +interface HostCollectionProps { + dataset: Dataset +} + +export const HostCollection = ({ dataset }: HostCollectionProps) => { + const { t } = useTranslation('editDatasetMetadata') + + const datasetParentCollectionName = dataset.hierarchy + .toArray() + .filter((item) => item.type === 'collection') + .at(-1)?.name + + return ( + + + {t('hostCollection.label')} + + + + + + ) +} From d012e80ed0105f345d16ce872894b2d3985da4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 12 Jun 2024 10:52:07 -0300 Subject: [PATCH 05/48] feat: improve host collection --- src/Router.tsx | 2 ++ .../EditDatasetMetadata.tsx | 2 +- .../edit-dataset-metadata/HostCollection.tsx | 19 ++++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Router.tsx b/src/Router.tsx index f44267638..8c13aaede 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -10,6 +10,8 @@ import { UploadDatasetFilesFactory } from './sections/upload-dataset-files/Uploa import { EditDatasetMetadataFactory } from './sections/edit-dataset-metadata/EditDatasetMetadataFactory' import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' +// TODO: Implement scroll restoration on navigation + const router = createBrowserRouter( [ { diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx index 8d857a367..921b2a4e6 100644 --- a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx @@ -40,7 +40,7 @@ export const EditDatasetMetadata = ({ who: _who }: EditDatasetMetadataProps) => {t('infoAlert.text')} - + diff --git a/src/sections/edit-dataset-metadata/HostCollection.tsx b/src/sections/edit-dataset-metadata/HostCollection.tsx index 184e114c4..1144ea351 100644 --- a/src/sections/edit-dataset-metadata/HostCollection.tsx +++ b/src/sections/edit-dataset-metadata/HostCollection.tsx @@ -1,18 +1,23 @@ +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Col, Form } from '@iqss/dataverse-design-system' -import { Dataset } from '../../dataset/domain/models/Dataset' +import { UpwardHierarchyNode } from '../../shared/hierarchy/domain/models/UpwardHierarchyNode' interface HostCollectionProps { - dataset: Dataset + datasetHierarchy: UpwardHierarchyNode } -export const HostCollection = ({ dataset }: HostCollectionProps) => { +export const HostCollection = ({ datasetHierarchy }: HostCollectionProps) => { const { t } = useTranslation('editDatasetMetadata') - const datasetParentCollectionName = dataset.hierarchy - .toArray() - .filter((item) => item.type === 'collection') - .at(-1)?.name + const datasetParentCollectionName = useMemo( + () => + datasetHierarchy + .toArray() + .filter((item) => item.type === 'collection') + .at(-1)?.name, + [datasetHierarchy] + ) return ( From 52fdc4ff528d2049f1821f3683442020b4d7093d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 12 Jun 2024 14:21:21 -0300 Subject: [PATCH 06/48] feat: rename and move some elements --- src/Router.tsx | 1 + .../EditDatasetMetadataFactory.tsx | 11 ++-- ...ata.tsx => EditDatasetMetadataSection.tsx} | 39 ++++++++---- .../edit-dataset-metadata/HostCollection.tsx | 20 +----- .../form/DatasetMetadataForm/Form/index.tsx | 7 +++ .../shared/form/DatasetMetadataForm/index.tsx | 41 ++++++++++++ .../useGetMetadataBlocksInfo.tsx | 62 +++++++++++++++++++ 7 files changed, 148 insertions(+), 33 deletions(-) rename src/sections/edit-dataset-metadata/{EditDatasetMetadata.tsx => EditDatasetMetadataSection.tsx} (55%) create mode 100644 src/sections/shared/form/DatasetMetadataForm/Form/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx diff --git a/src/Router.tsx b/src/Router.tsx index 8c13aaede..05fda95e1 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -11,6 +11,7 @@ import { EditDatasetMetadataFactory } from './sections/edit-dataset-metadata/Edi import { DatasetNonNumericVersion } from './dataset/domain/models/Dataset' // TODO: Implement scroll restoration on navigation +// TODO: JSF shows a select similar to Select Multiple but with the option of selecting only one item, I think for when options are more than certain number const router = createBrowserRouter( [ diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx index c184a3e92..0645388b8 100644 --- a/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadataFactory.tsx @@ -1,12 +1,12 @@ import { ReactElement } from 'react' import { useSearchParams } from 'react-router-dom' -import { EditDatasetMetadata } from './EditDatasetMetadata' +import { EditDatasetMetadataSection } from './EditDatasetMetadataSection' import { DatasetProvider } from '../dataset/DatasetProvider' import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository' -// import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' +import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' const datasetRepository = new DatasetJSDataverseRepository() -// const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() +const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() export class EditDatasetMetadataFactory { static create(): ReactElement { @@ -20,7 +20,10 @@ function EditDatasetMetadataWithParams() { return ( - + ) } diff --git a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx b/src/sections/edit-dataset-metadata/EditDatasetMetadataSection.tsx similarity index 55% rename from src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx rename to src/sections/edit-dataset-metadata/EditDatasetMetadataSection.tsx index 921b2a4e6..d6c033455 100644 --- a/src/sections/edit-dataset-metadata/EditDatasetMetadata.tsx +++ b/src/sections/edit-dataset-metadata/EditDatasetMetadataSection.tsx @@ -1,19 +1,27 @@ -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Alert, Tabs } from '@iqss/dataverse-design-system' +import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' +import { MetadataBlockInfoRepository } from '../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { useDataset } from '../dataset/DatasetContext' import { useLoading } from '../loading/LoadingContext' import { PageNotFound } from '../page-not-found/PageNotFound' import { HostCollection } from './HostCollection' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' +import { DatasetMetadataForm } from '../shared/form/DatasetMetadataForm' +import { UpwardHierarchyNode } from '../../shared/hierarchy/domain/models/UpwardHierarchyNode' import styles from './EditDatasetMetadata.module.scss' interface EditDatasetMetadataProps { - who?: string + datasetRepository: DatasetRepository + metadataBlockInfoRepository: MetadataBlockInfoRepository } -export const EditDatasetMetadata = ({ who: _who }: EditDatasetMetadataProps) => { +export const EditDatasetMetadataSection = ({ + datasetRepository, + metadataBlockInfoRepository +}: EditDatasetMetadataProps) => { const { t } = useTranslation('editDatasetMetadata') const { dataset, isLoading } = useDataset() const { setIsLoading } = useLoading() @@ -22,6 +30,15 @@ export const EditDatasetMetadata = ({ who: _who }: EditDatasetMetadataProps) => setIsLoading(isLoading) }, [isLoading, setIsLoading]) + const datasetParentCollection: UpwardHierarchyNode = useMemo( + () => + dataset?.hierarchy + .toArray() + .filter((item) => item.type === 'collection') + .at(-1) as UpwardHierarchyNode, + [dataset] + ) + if (isLoading) { return

Loading...

} @@ -40,19 +57,17 @@ export const EditDatasetMetadata = ({ who: _who }: EditDatasetMetadataProps) => {t('infoAlert.text')} - +
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi saepe iure - consequatur asperiores doloribus sapiente eius aspernatur hic officia illum - veritatis dolor molestiae ex perspiciatis accusamus ipsam minus animi, minima - molestias natus quaerat aliquam ea. Eius nisi ut hic laudantium quam, veniam porro - consequatur iure animi tenetur quod incidunt error harum debitis, pariatur - temporibus ipsa nobis cupiditate quas nihil rerum distinctio ullam! Fugit amet ipsum - omnis veniam, ad neque inventore. Laboriosam maiores autem officiis, aut atque natus - fugit. +
diff --git a/src/sections/edit-dataset-metadata/HostCollection.tsx b/src/sections/edit-dataset-metadata/HostCollection.tsx index 1144ea351..eb9a6aade 100644 --- a/src/sections/edit-dataset-metadata/HostCollection.tsx +++ b/src/sections/edit-dataset-metadata/HostCollection.tsx @@ -1,34 +1,20 @@ -import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Col, Form } from '@iqss/dataverse-design-system' -import { UpwardHierarchyNode } from '../../shared/hierarchy/domain/models/UpwardHierarchyNode' interface HostCollectionProps { - datasetHierarchy: UpwardHierarchyNode + collectionName: string } -export const HostCollection = ({ datasetHierarchy }: HostCollectionProps) => { +export const HostCollection = ({ collectionName }: HostCollectionProps) => { const { t } = useTranslation('editDatasetMetadata') - const datasetParentCollectionName = useMemo( - () => - datasetHierarchy - .toArray() - .filter((item) => item.type === 'collection') - .at(-1)?.name, - [datasetHierarchy] - ) - return ( {t('hostCollection.label')} - + ) diff --git a/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx b/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx new file mode 100644 index 000000000..64ad3a4fd --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx @@ -0,0 +1,7 @@ +export const Form = () => { + return ( +
+

Form

+
+ ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx new file mode 100644 index 000000000..6b8b71f96 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -0,0 +1,41 @@ +import { useGetMetadataBlocksInfo } from './useGetMetadataBlocksInfo' +import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' +import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { Form } from './Form' + +type DatasetMetadataFormProps = { + mode: DatasetMetadataFormMode + collectionId: string + datasetRepository: DatasetRepository + metadataBlockInfoRepository: MetadataBlockInfoRepository +} + +export type DatasetMetadataFormMode = 'create' | 'edit' + +export const DatasetMetadataForm = ({ + mode, + collectionId, + datasetRepository, + metadataBlockInfoRepository +}: DatasetMetadataFormProps) => { + const { + metadataBlocksInfo, + isLoading: isLoadingMetadataBlocksInfo, + error: errorLoadingMetadataBlocksInfo + } = useGetMetadataBlocksInfo({ + mode, + collectionId, + metadataBlockInfoRepository + }) + + console.log({ metadataBlocksInfo, isLoadingMetadataBlocksInfo, errorLoadingMetadataBlocksInfo }) + + // formDefaultValues here + return ( + <> + {/* is loading && skeleton else show form */} + +
+ + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx new file mode 100644 index 000000000..62523e51d --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react' +import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId' +import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFieldsHelper } from '../../../create-dataset/MetadataFieldsHelper' +import { DatasetMetadataFormMode } from '.' + +interface Props { + mode: DatasetMetadataFormMode + collectionId: string + metadataBlockInfoRepository: MetadataBlockInfoRepository +} + +interface UseGetMetadataBlocksInfoReturn { + metadataBlocksInfo: MetadataBlockInfo[] + error: string | null + isLoading: boolean +} + +export const useGetMetadataBlocksInfo = ({ + mode, + collectionId, + metadataBlockInfoRepository +}: Props): UseGetMetadataBlocksInfoReturn => { + const [metadataBlocksInfo, setMetadataBlocksInfo] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + console.log({ mode, collectionId }) + + useEffect(() => { + const handleGetDatasetMetadataBlockFields = async () => { + setIsLoading(true) + try { + const metadataBlocks: MetadataBlockInfo[] = + await getDisplayedOnCreateMetadataBlockInfoByCollectionId( + metadataBlockInfoRepository, + collectionId + ) + const mappedMetadataBlocks = + MetadataFieldsHelper.replaceDotNamesKeysWithSlash(metadataBlocks) + setMetadataBlocksInfo(mappedMetadataBlocks) + } catch (err) { + console.error(err) + const errorMessage = + err instanceof Error && err.message + ? err.message + : /* istanbul ignore next */ 'Something went wrong getting the information from the metadata blocks. Try again later.' + setError(errorMessage) + } finally { + setIsLoading(false) + } + } + void handleGetDatasetMetadataBlockFields() + }, [collectionId, metadataBlockInfoRepository]) + + return { + metadataBlocksInfo, + error, + isLoading + } +} From 0cefebee5850ba4e439abd08c6e9d0abae0ba4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 12 Jun 2024 16:29:59 -0300 Subject: [PATCH 07/48] feat: rendering form fields --- public/locales/en/editDatasetMetadata.json | 2 +- .../form/DatasetMetadataForm/Form/index.tsx | 7 - .../MetadataFieldsHelper.ts | 279 ++++++++++++++++++ .../Fields/ComposeFieldMultiple.tsx | 90 ++++++ .../Fields/ComposedField.tsx | 42 +++ .../MetadataFormField/Fields/Primitive.tsx | 87 ++++++ .../Fields/PrimitiveMultiple.tsx | 134 +++++++++ .../MetadataFormField/Fields/Vocabulary.tsx | 71 +++++ .../Fields/VocabularyMultiple.tsx | 70 +++++ .../MetadataFormField/Fields/index.ts | 6 + .../MetadataFormField/index.module.scss | 31 ++ .../MetadataFormField/index.tsx | 173 +++++++++++ .../MetadataFormField/useDefineRules.ts | 72 +++++ .../DynamicFieldsButtons.module.scss | 4 + .../DynamicFieldsButtons.tsx | 47 +++ .../MetadataBlockFormFields/index.tsx | 24 ++ .../MetadataForm/MetadataFormSkeleton.tsx | 77 +++++ .../MetadataForm/index.module.scss | 3 + .../MetadataForm/index.tsx | 162 ++++++++++ .../shared/form/DatasetMetadataForm/index.tsx | 32 +- .../useGetMetadataBlocksInfo.tsx | 20 +- .../DatasetMetadataForm/useSubmitDataset.ts | 87 ++++++ 22 files changed, 1498 insertions(+), 22 deletions(-) delete mode 100644 src/sections/shared/form/DatasetMetadataForm/Form/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/index.ts create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.module.scss create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss create mode 100644 src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx create mode 100644 src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts diff --git a/public/locales/en/editDatasetMetadata.json b/public/locales/en/editDatasetMetadata.json index 189f80de9..0c571529d 100644 --- a/public/locales/en/editDatasetMetadata.json +++ b/public/locales/en/editDatasetMetadata.json @@ -1,5 +1,5 @@ { - "breadcrumbActionItem": "Edit Metadata", + "breadcrumbActionItem": "Edit Dataset Metadata", "infoAlert": { "heading": "Edit Dataset Metadata", "text": "Add more metadata about this dataset to help others easily find it." diff --git a/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx b/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx deleted file mode 100644 index 64ad3a4fd..000000000 --- a/src/sections/shared/form/DatasetMetadataForm/Form/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const Form = () => { - return ( -
-

Form

-
- ) -} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts new file mode 100644 index 000000000..2b4086c04 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts @@ -0,0 +1,279 @@ +import { + MetadataBlockInfo, + MetadataField +} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { + DatasetDTO, + DatasetMetadataBlockValuesDTO, + DatasetMetadataChildFieldValueDTO +} from '../../../../dataset/domain/useCases/DTOs/DatasetDTO' + +export type DatasetMetadataFormValues = Record + +export type MetadataBlockFormValues = Record< + string, + string | PrimitiveMultipleFormValue | VocabularyMultipleFormValue | ComposedFieldValues +> + +type VocabularyMultipleFormValue = string[] + +type PrimitiveMultipleFormValue = { value: string }[] + +type ComposedFieldValues = ComposedSingleFieldValue | ComposedSingleFieldValue[] + +type ComposedSingleFieldValue = Record + +export class MetadataFieldsHelper { + public static replaceDotNamesKeysWithSlash( + metadataBlocks: MetadataBlockInfo[] + ): MetadataBlockInfo[] { + for (const block of metadataBlocks) { + if (block.metadataFields) { + this.dotReplacer(block.metadataFields) + } + } + return metadataBlocks + } + + private static dotReplacer(metadataFields: Record | undefined) { + if (!metadataFields) return + + for (const key in metadataFields) { + const field = metadataFields[key] + if (field.name.includes('.')) { + field.name = field.name.replace(/\./g, '/') + } + if (field.childMetadataFields) { + this.dotReplacer(field.childMetadataFields) + } + } + } + + public static getFormDefaultValues( + metadataBlocks: MetadataBlockInfo[] + ): DatasetMetadataFormValues { + const formDefaultValues: DatasetMetadataFormValues = {} + + for (const block of metadataBlocks) { + const blockValues: MetadataBlockFormValues = {} + + for (const field of Object.values(block.metadataFields)) { + const fieldName = field.name + + if (field.typeClass === 'compound') { + const childFieldsWithEmptyValues: Record = {} + + if (field.childMetadataFields) { + for (const childField of Object.values(field.childMetadataFields)) { + if (childField.typeClass === 'primitive') { + childFieldsWithEmptyValues[childField.name] = '' + } + + if (childField.typeClass === 'controlledVocabulary') { + childFieldsWithEmptyValues[childField.name] = '' + } + } + } + blockValues[fieldName] = field.multiple + ? [childFieldsWithEmptyValues] + : childFieldsWithEmptyValues + } + + if (field.typeClass === 'primitive') { + blockValues[fieldName] = field.multiple ? [{ value: '' }] : '' + } + + if (field.typeClass === 'controlledVocabulary') { + blockValues[fieldName] = field.multiple ? [] : '' + } + } + + formDefaultValues[block.name] = blockValues + } + + return formDefaultValues + } + + public static replaceSlashKeysWithDot(obj: DatasetMetadataFormValues): DatasetMetadataFormValues { + const formattedNewObject: DatasetMetadataFormValues = {} + + for (const key in obj) { + const blockKey = key.replace(/\//g, '.') + const metadataBlockFormValues = obj[key] + + formattedNewObject[blockKey] = {} + + Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { + const newFieldName = fieldName.replace(/\//g, '.') + + if ( + this.isPrimitiveFieldValue(fieldValue) || + this.isVocabularyMultipleFieldValue(fieldValue) || + this.isPrimitiveMultipleFieldValue(fieldValue) + ) { + formattedNewObject[blockKey][newFieldName] = fieldValue + return + } + + if (this.isComposedSingleFieldValue(fieldValue)) { + formattedNewObject[blockKey][newFieldName] = {} + Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { + const newNestedFieldName = nestedFieldName.replace(/\//g, '.') + const parentOfNestedField = formattedNewObject[blockKey][ + newFieldName + ] as ComposedSingleFieldValue + + parentOfNestedField[newNestedFieldName] = nestedFieldValue + }) + return + } + + if (this.isComposedMultipleFieldValue(fieldValue)) { + formattedNewObject[blockKey][newFieldName] = fieldValue.map((composedFieldValues) => { + const composedField: ComposedSingleFieldValue = {} + + Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { + const newNestedFieldName = nestedFieldName.replace(/\//g, '.') + + composedField[newNestedFieldName] = nestedFieldValue + }) + + return composedField + }) + } + }) + } + + return formattedNewObject + } + + public static formatFormValuesToDatasetDTO(formValues: DatasetMetadataFormValues): DatasetDTO { + const metadataBlocks: DatasetDTO['metadataBlocks'] = [] + + for (const metadataBlockName in formValues) { + const formattedMetadataBlock: DatasetMetadataBlockValuesDTO = { + name: metadataBlockName, + fields: {} + } + const metadataBlockFormValues = formValues[metadataBlockName] + + Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { + if (this.isPrimitiveFieldValue(fieldValue)) { + if (fieldValue !== '') { + formattedMetadataBlock.fields[fieldName] = fieldValue + return + } + return + } + if (this.isVocabularyMultipleFieldValue(fieldValue)) { + if (fieldValue.length > 0) { + formattedMetadataBlock.fields[fieldName] = fieldValue + return + } + return + } + + if (this.isPrimitiveMultipleFieldValue(fieldValue)) { + const primitiveMultipleFieldValues = fieldValue + .map((primitiveField) => primitiveField.value) + .filter((v) => v !== '') + + if (primitiveMultipleFieldValues.length > 0) { + formattedMetadataBlock.fields[fieldName] = primitiveMultipleFieldValues + return + } + return + } + + if (this.isComposedSingleFieldValue(fieldValue)) { + const formattedMetadataChildFieldValue: DatasetMetadataChildFieldValueDTO = {} + + Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { + if (nestedFieldValue !== '') { + formattedMetadataChildFieldValue[nestedFieldName] = nestedFieldValue + } + }) + if (Object.keys(formattedMetadataChildFieldValue).length > 0) { + formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValue + return + } + return + } + + if (this.isComposedMultipleFieldValue(fieldValue)) { + const formattedMetadataChildFieldValues: DatasetMetadataChildFieldValueDTO[] = [] + + fieldValue.forEach((composedFieldValues) => { + const composedField: DatasetMetadataChildFieldValueDTO = {} + Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { + if (nestedFieldValue !== '') { + composedField[nestedFieldName] = nestedFieldValue + } + }) + if (Object.keys(composedField).length > 0) { + formattedMetadataChildFieldValues.push(composedField) + } + }) + if (formattedMetadataChildFieldValues.length > 0) { + formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValues + } + + return + } + }) + + metadataBlocks.push(formattedMetadataBlock) + } + return { metadataBlocks } + } + + /* + * To define the field name that will be used to register the field in the form + * Most basic could be: metadataBlockName.name eg: citation.title + * If the field is part of a compound field, the name will be: metadataBlockName.compoundParentName.name eg: citation.author.authorName + * If the field is part of an array of fields, the name will be: metadataBlockName.fieldsArrayIndex.name eg: citation.alternativeTitle.0.value + * If the field is part of a compound field that is part of an array of fields, the name will be: metadataBlockName.compoundParentName.fieldsArrayIndex.name eg: citation.author.0.authorName + */ + public static defineFieldName( + name: string, + metadataBlockName: string, + compoundParentName?: string, + fieldsArrayIndex?: number + ) { + if (fieldsArrayIndex !== undefined && !compoundParentName) { + return `${metadataBlockName}.${name}.${fieldsArrayIndex}.value` + } + if (fieldsArrayIndex !== undefined && compoundParentName) { + return `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${name}` + } + + if (compoundParentName) { + return `${metadataBlockName}.${compoundParentName}.${name}` + } + return `${metadataBlockName}.${name}` + } + + private static isPrimitiveFieldValue = (value: unknown): value is string => { + return typeof value === 'string' + } + private static isPrimitiveMultipleFieldValue = ( + value: unknown + ): value is PrimitiveMultipleFormValue => { + return Array.isArray(value) && value.every((v) => typeof v === 'object' && 'value' in v) + } + private static isVocabularyMultipleFieldValue = ( + value: unknown + ): value is VocabularyMultipleFormValue => { + return Array.isArray(value) && value.every((v) => typeof v === 'string') + } + private static isComposedSingleFieldValue = ( + value: unknown + ): value is ComposedSingleFieldValue => { + return typeof value === 'object' && !Array.isArray(value) + } + private static isComposedMultipleFieldValue = ( + value: unknown + ): value is ComposedSingleFieldValue[] => { + return Array.isArray(value) && value.every((v) => typeof v === 'object') + } +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx new file mode 100644 index 000000000..db672ad4a --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx @@ -0,0 +1,90 @@ +import { useFieldArray, useFormContext } from 'react-hook-form' +import { type MetadataField } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFormField, type CommonFieldProps } from '..' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { DynamicFieldsButtons } from '../../dynamic-fields-buttons/DynamicFieldsButtons' +import cn from 'classnames' +import styles from '../index.module.scss' + +interface ComposedFieldMultipleProps extends CommonFieldProps { + metadataBlockName: string + childMetadataFields: Record + compoundParentName?: string + fieldsArrayIndex?: number +} + +export const ComposedFieldMultiple = ({ + metadataBlockName, + title, + name, + description, + isRequired, + childMetadataFields +}: ComposedFieldMultipleProps) => { + const { control } = useFormContext() + + const { + fields: fieldsArray, + insert, + remove + } = useFieldArray({ + name: `${metadataBlockName}.${name}`, + control: control + }) + + const handleOnAddField = (index: number) => { + const firstChildFieldName = Object.values(childMetadataFields)[0].name + + const newField = Object.entries(childMetadataFields).reduce((acc, [_, metadataField]) => { + acc[metadataField.name] = '' + return acc + }, {} as Record) + + insert(index + 1, newField, { + shouldFocus: true, + focusName: `${metadataBlockName}.${name}.${index + 1}.${firstChildFieldName}` + }) + } + + const handleOnRemoveField = (index: number) => remove(index) + + return ( + + {fieldsArray.map((field, index) => { + return ( + + + {Object.entries(childMetadataFields).map( + ([childMetadataFieldKey, childMetadataFieldInfo]) => { + return ( + + ) + } + )} + + + handleOnAddField(index)} + onRemoveButtonClick={() => handleOnRemoveField(index)} + originalField={index === 0} + /> + + + ) + })} + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx new file mode 100644 index 000000000..ed4b724d5 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposedField.tsx @@ -0,0 +1,42 @@ +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { type MetadataField } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFormField, type CommonFieldProps } from '..' +import styles from '../index.module.scss' + +interface ComposedFieldProps extends CommonFieldProps { + metadataBlockName: string + childMetadataFields: Record + compoundParentName?: string + fieldsArrayIndex?: number +} + +export const ComposedField = ({ + metadataBlockName, + title, + name, + description, + isRequired, + childMetadataFields +}: ComposedFieldProps) => { + return ( + + + + {Object.entries(childMetadataFields).map( + ([childMetadataFieldKey, childMetadataFieldInfo]) => { + return ( + + ) + } + )} + + + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx new file mode 100644 index 000000000..896177f0a --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx @@ -0,0 +1,87 @@ +import { useMemo } from 'react' +import { Controller, useFormContext } from 'react-hook-form' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { TypeMetadataFieldOptions } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { type CommonFieldProps } from '..' + +interface PrimitiveProps extends CommonFieldProps { + metadataBlockName: string + compoundParentName?: string + fieldsArrayIndex?: number +} +export const Primitive = ({ + name, + compoundParentName, + metadataBlockName, + rulesToApply, + description, + title, + watermark, + type, + isRequired, + withinMultipleFieldsGroup, + fieldsArrayIndex +}: PrimitiveProps) => { + const { control } = useFormContext() + + const builtFieldName = useMemo( + () => + MetadataFieldsHelper.defineFieldName( + name, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ), + [name, metadataBlockName, compoundParentName, fieldsArrayIndex] + ) + + const isTextArea = type === TypeMetadataFieldOptions.Textbox + + return ( + + + {title} + + + ( + + + + {isTextArea ? ( + + ) : ( + + )} + + {error?.message} + + + + )} + /> + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx new file mode 100644 index 000000000..2ca8022cb --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/PrimitiveMultiple.tsx @@ -0,0 +1,134 @@ +import { useCallback, useMemo } from 'react' +import { Controller, useFieldArray, useFormContext } from 'react-hook-form' +import { Col, Form, Row } from '@iqss/dataverse-design-system' +import { TypeMetadataFieldOptions } from '../../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { DynamicFieldsButtons } from '../../dynamic-fields-buttons/DynamicFieldsButtons' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { type CommonFieldProps } from '..' +import cn from 'classnames' +import styles from '../index.module.scss' + +interface PrimitiveMultipleProps extends CommonFieldProps { + metadataBlockName: string + compoundParentName?: string + fieldsArrayIndex?: number +} + +export const PrimitiveMultiple = ({ + name, + compoundParentName, + metadataBlockName, + description, + title, + watermark, + type, + isRequired, + rulesToApply +}: PrimitiveMultipleProps) => { + const { control } = useFormContext() + + const { + fields: fieldsArray, + insert, + remove + } = useFieldArray({ + name: `${metadataBlockName}.${name}`, + control: control + }) + + const builtFieldNameWithIndex = useCallback( + (fieldIndex: number) => { + return MetadataFieldsHelper.defineFieldName( + name, + metadataBlockName, + compoundParentName, + fieldIndex + ) + }, + [name, metadataBlockName, compoundParentName] + ) + + // We give the label the same ID as the first field, so that clicking on the label focuses the first field + const controlID = useMemo(() => builtFieldNameWithIndex(0), [builtFieldNameWithIndex]) + + const handleOnAddField = (index: number) => { + insert( + index + 1, + { value: '' }, + { + shouldFocus: true, + focusName: builtFieldNameWithIndex(index + 1) + } + ) + } + + const handleOnRemoveField = (index: number) => remove(index) + + const isTextArea = type === TypeMetadataFieldOptions.Textbox + + return ( + + + {title} + + + {(fieldsArray as { id: string; value: string }[]).map((field, index) => ( + + ( + <> + + {isTextArea ? ( + + ) : ( + + )} + {error?.message} + + + + handleOnAddField(index)} + onRemoveButtonClick={() => handleOnRemoveField(index)} + originalField={index === 0} + /> + + + )} + /> + + ))} + + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx new file mode 100644 index 000000000..26717a859 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx @@ -0,0 +1,71 @@ +import { useMemo } from 'react' +import { Controller, useFormContext } from 'react-hook-form' +import { Form, Row, Col } from '@iqss/dataverse-design-system' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { type CommonFieldProps } from '..' + +interface VocabularyProps extends CommonFieldProps { + metadataBlockName: string + options: string[] + compoundParentName?: string + fieldsArrayIndex?: number +} +export const Vocabulary = ({ + name, + compoundParentName, + metadataBlockName, + rulesToApply, + description, + title, + options, + isRequired, + withinMultipleFieldsGroup, + fieldsArrayIndex +}: VocabularyProps) => { + const { control } = useFormContext() + + const builtFieldName = useMemo( + () => + MetadataFieldsHelper.defineFieldName( + name, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ), + [name, metadataBlockName, compoundParentName, fieldsArrayIndex] + ) + + return ( + ( + + + {title} + + + + + + + {options.map((option) => ( + + ))} + + {error?.message} + + + + + )} + /> + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx new file mode 100644 index 000000000..5d003312a --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx @@ -0,0 +1,70 @@ +import { useMemo } from 'react' +import { Controller, useFormContext } from 'react-hook-form' +import { Form, Row, Col } from '@iqss/dataverse-design-system' +import { MetadataFieldsHelper } from '../../../../MetadataFieldsHelper' +import { type CommonFieldProps } from '..' + +interface VocabularyProps extends CommonFieldProps { + metadataBlockName: string + options: string[] + compoundParentName?: string + fieldsArrayIndex?: number +} +export const VocabularyMultiple = ({ + name, + compoundParentName, + metadataBlockName, + rulesToApply, + description, + title, + options, + isRequired, + fieldsArrayIndex +}: VocabularyProps) => { + const { control } = useFormContext() + + const builtFieldName = useMemo( + () => + MetadataFieldsHelper.defineFieldName( + name, + metadataBlockName, + compoundParentName, + fieldsArrayIndex + ), + [name, metadataBlockName, compoundParentName, fieldsArrayIndex] + ) + + return ( + ( + + + {title} + + + + + + {error?.message} + + + + + )} + /> + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/index.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/index.ts new file mode 100644 index 000000000..ea34e27b7 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/index.ts @@ -0,0 +1,6 @@ +export * from './ComposeFieldMultiple' +export * from './ComposedField' +export * from './Primitive' +export * from './PrimitiveMultiple' +export * from './Vocabulary' +export * from './VocabularyMultiple' diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.module.scss b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.module.scss new file mode 100644 index 000000000..0e06c8140 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.module.scss @@ -0,0 +1,31 @@ +.composed-fields-grid { + display: grid; + grid-template-columns: 1fr; + + @media screen and (min-width: 768px) { + grid-template-columns: 1fr 1fr; + column-gap: 1rem; + } + + & > div:has(textarea) { + grid-column: span 2; + } +} + +.dynamic-fields-button-container { + margin-top: calc(24px + 8px); // 24px text label height & 8px its margin bottom + + &.on-composed-multiple { + @media screen and (max-width: 575px) { + margin-top: 0; + } + } + + &.on-primitive-multiple { + margin-top: 0; + + @media screen and (max-width: 575px) { + margin-top: 1rem; + } + } +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.tsx new file mode 100644 index 000000000..9ca420d0c --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/index.tsx @@ -0,0 +1,173 @@ +import { DefinedRules, useDefineRules } from './useDefineRules' +import { + Primitive, + PrimitiveMultiple, + Vocabulary, + VocabularyMultiple, + ComposedField, + ComposedFieldMultiple +} from './Fields' +import { + TypeClassMetadataFieldOptions, + type MetadataField, + type TypeMetadataField +} from '../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' + +export interface CommonFieldProps { + name: string + rulesToApply: DefinedRules + description: string + title: string + watermark: string + type: TypeMetadataField + isRequired: boolean + withinMultipleFieldsGroup?: boolean +} + +interface Props { + metadataFieldInfo: MetadataField + metadataBlockName: string + withinMultipleFieldsGroup?: boolean + compoundParentName?: string + fieldsArrayIndex?: number +} + +export const MetadataFormField = ({ + metadataFieldInfo, + metadataBlockName, + withinMultipleFieldsGroup = false, + compoundParentName, + fieldsArrayIndex +}: Props) => { + const { + name, + type, + title, + multiple, + typeClass, + isRequired, + description, + watermark, + childMetadataFields, + controlledVocabularyValues + } = metadataFieldInfo + + const rulesToApply = useDefineRules({ metadataFieldInfo }) + + const isSafeCompound = + typeClass === TypeClassMetadataFieldOptions.Compound && + childMetadataFields !== undefined && + Object.keys(childMetadataFields).length > 0 + + const isSafeControlledVocabulary = + typeClass === TypeClassMetadataFieldOptions.ControlledVocabulary && + controlledVocabularyValues !== undefined && + controlledVocabularyValues.length > 0 + + const isSafePrimitive = typeClass === TypeClassMetadataFieldOptions.Primitive + + if (isSafePrimitive) { + if (multiple) { + return ( + + ) + } + return ( + + ) + } + + if (isSafeControlledVocabulary) { + if (multiple) { + return ( + + ) + } + return ( + + ) + } + + if (isSafeCompound) { + if (multiple) { + return ( + + ) + } + + return ( + + ) + } + + return null +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts new file mode 100644 index 000000000..48dde728e --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts @@ -0,0 +1,72 @@ +import { UseControllerProps } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { + DateFormatsOptions, + type MetadataField, + TypeMetadataFieldOptions +} from '../../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { + isValidURL, + isValidFloat, + isValidEmail, + isValidInteger, + isValidDateFormat +} from '../../../../../../../metadata-block-info/domain/models/fieldValidations' + +interface Props { + metadataFieldInfo: MetadataField +} + +export type DefinedRules = UseControllerProps['rules'] + +export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { + const { t } = useTranslation('createDataset') + const { type, displayName, isRequired, watermark } = metadataFieldInfo + + const rulesToApply: DefinedRules = { + required: isRequired ? t('datasetForm.field.required', { displayName }) : false, + validate: (value: string) => { + if (!value) { + return true + } + + if (type === TypeMetadataFieldOptions.URL) { + if (!isValidURL(value)) { + return t('datasetForm.field.invalid.url', { displayName }) + } + return true + } + if (type === TypeMetadataFieldOptions.Date) { + const acceptedDateFormat = + watermark === 'YYYY-MM-DD' ? DateFormatsOptions.YYYYMMDD : undefined + + if (!isValidDateFormat(value, acceptedDateFormat)) { + return t('datasetForm.field.invalid.date', { displayName, dateFormat: watermark }) + } + return true + } + if (type === TypeMetadataFieldOptions.Email) { + if (!isValidEmail(value)) { + return t('datasetForm.field.invalid.email', { displayName }) + } + return true + } + if (type === TypeMetadataFieldOptions.Int) { + if (!isValidInteger(value)) { + return t('datasetForm.field.invalid.int', { displayName }) + } + return true + } + if (type === TypeMetadataFieldOptions.Float) { + if (!isValidFloat(value)) { + return t('datasetForm.field.invalid.float', { displayName }) + } + return true + } + + return true + } + } + + return rulesToApply +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss new file mode 100644 index 000000000..54423ad8c --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss @@ -0,0 +1,4 @@ +.container { + display: flex; + gap: 1rem; +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx new file mode 100644 index 000000000..854a5bd9d --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx @@ -0,0 +1,47 @@ +import { MouseEvent } from 'react' +import { Button, Tooltip } from '@iqss/dataverse-design-system' +import { Dash, Plus } from 'react-bootstrap-icons' +import { useTranslation } from 'react-i18next' +import styles from './DynamicFieldsButtons.module.scss' + +interface AddFieldButtonsProps { + fieldName: string + originalField?: boolean + onAddButtonClick: (event: MouseEvent) => void + onRemoveButtonClick: (event: MouseEvent) => void +} + +export function DynamicFieldsButtons({ + fieldName, + originalField, + onAddButtonClick, + onRemoveButtonClick +}: AddFieldButtonsProps) { + const { t } = useTranslation('createDataset') + return ( +
+ + + + {!originalField && ( + + + + )} +
+ ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx new file mode 100644 index 000000000..e91094e32 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/index.tsx @@ -0,0 +1,24 @@ +import { type MetadataBlockInfo } from '../../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MetadataFormField } from './MetadataFormField' + +interface Props { + metadataBlock: MetadataBlockInfo +} + +export const MetadataBlockFormFields = ({ metadataBlock }: Props) => { + const { metadataFields, name: metadataBlockName } = metadataBlock + + return ( + <> + {Object.entries(metadataFields).map(([metadataFieldKey, metadataFieldInfo]) => { + return ( + + ) + })} + + ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx new file mode 100644 index 000000000..ea9fff918 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx @@ -0,0 +1,77 @@ +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' +import { Accordion, Col, Row } from '@iqss/dataverse-design-system' +import { SeparationLine } from '../../../layout/SeparationLine/SeparationLine' +import 'react-loading-skeleton/dist/skeleton.css' +//TODO:ME Check if we can add skeleton css once in the app and if tests still pass + +export const MetadataFormSkeleton = () => { + return ( + +
+ + + + + + + + {Array.from({ length: 4 }).map((_, index) => ( + + + + + + + + + + {index === 2 && ( + + + + )} + + + + ))} + {Array.from({ length: 3 }).map((_, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + ))} + + + + + + +
+ + + +
+
+
+ ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss new file mode 100644 index 000000000..9e1bd7c5d --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.module.scss @@ -0,0 +1,3 @@ +.form-container { + scroll-margin-top: 62px; +} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx new file mode 100644 index 000000000..e3c322f64 --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx @@ -0,0 +1,162 @@ +import { MouseEvent, useMemo, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { FieldErrors, FormProvider, useForm } from 'react-hook-form' +import { Form, Accordion, Alert, Button } from '@iqss/dataverse-design-system' +import { type DatasetRepository } from '../../../../../dataset/domain/repositories/DatasetRepository' +import { type MetadataBlockInfo } from '../../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { type DatasetMetadataFormValues } from '../MetadataFieldsHelper' +import { type DatasetMetadataFormMode } from '..' +import { Route } from '../../../../Route.enum' +import { SubmissionStatus, useSubmitDataset } from '../useSubmitDataset' +import { MetadataBlockFormFields } from './MetadataBlockFormFields' +import { RequiredFieldText } from '../../RequiredFieldText/RequiredFieldText' +import { SeparationLine } from '../../../layout/SeparationLine/SeparationLine' +import styles from './index.module.scss' + +interface FormProps { + mode: DatasetMetadataFormMode + collectionId: string + formDefaultValues: DatasetMetadataFormValues + metadataBlocksInfo: MetadataBlockInfo[] + errorLoadingMetadataBlocksInfo: string | null + datasetRepository: DatasetRepository +} + +export const MetadataForm = ({ + mode, + collectionId, + formDefaultValues, + metadataBlocksInfo, + errorLoadingMetadataBlocksInfo, + datasetRepository +}: FormProps) => { + const navigate = useNavigate() + const { t } = useTranslation('createDataset') + + const accordionRef = useRef(null) + const formContainerRef = useRef(null) + + const { submissionStatus, submitError, submitForm } = useSubmitDataset( + mode, + collectionId, + datasetRepository, + onCreateDatasetError + ) + + const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocksInfo) + + const form = useForm({ + mode: 'onChange', + defaultValues: formDefaultValues + }) + + const handleCancel = (event: MouseEvent) => { + event.preventDefault() + navigate(Route.HOME) + } + + const onInvalidSubmit = (errors: FieldErrors) => { + if (!accordionRef.current) return + /* + Get the first metadata block accordion item with an error, and if it's collapsed, open it + Only for the case when accordion is closed, otherwise focus is already handled by react-hook-form + */ + const firstMetadataBlockNameWithError = Object.keys(errors)[0] + + const accordionItemsButtons: HTMLButtonElement[] = Array.from( + accordionRef.current.querySelectorAll('button.accordion-button') + ) + + accordionItemsButtons.forEach((button) => { + const parentItem = button.closest('.accordion-item') + const itemBlockName = parentItem?.id.split('-').pop() + const buttonIsCollapsed = button.classList.contains('collapsed') + + if (itemBlockName === firstMetadataBlockNameWithError && buttonIsCollapsed) { + button.click() + + setTimeout( + /* istanbul ignore next */ () => { + const focusedElement = document.activeElement + focusedElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }) + }, + 800 + ) + } + }) + } + + function onCreateDatasetError() { + if (formContainerRef.current) { + formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) + } + } + + const disableSubmitButton = useMemo(() => { + return isErrorLoadingMetadataBlocks || submissionStatus === SubmissionStatus.IsSubmitting + }, [isErrorLoadingMetadataBlocks, submissionStatus]) + + return ( +
+ + {isErrorLoadingMetadataBlocks && ( + + {errorLoadingMetadataBlocksInfo} + + )} + {submissionStatus === SubmissionStatus.IsSubmitting && ( +

{t('datasetForm.status.submitting')}

+ )} + + {submissionStatus === SubmissionStatus.SubmitComplete && ( +

{t('datasetForm.status.success')}

+ )} + {submissionStatus === SubmissionStatus.Errored && ( + + {submitError} + + )} + + + + {metadataBlocksInfo.length > 0 && ( + + {metadataBlocksInfo.map((metadataBlock, index) => ( + + {metadataBlock.displayName} + + + + + ))} + + )} + + + + {mode === 'create' && ( + + {t('metadataTip.content')} + + )} + + + + + +
+ ) +} diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx index 6b8b71f96..016f01b19 100644 --- a/src/sections/shared/form/DatasetMetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -1,7 +1,11 @@ +import { useEffect } from 'react' +import { useLoading } from '../../../loading/LoadingContext' import { useGetMetadataBlocksInfo } from './useGetMetadataBlocksInfo' import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' -import { Form } from './Form' +import { MetadataFieldsHelper } from './MetadataFieldsHelper' +import { MetadataFormSkeleton } from './MetadataForm/MetadataFormSkeleton' +import { MetadataForm } from './MetadataForm' type DatasetMetadataFormProps = { mode: DatasetMetadataFormMode @@ -18,6 +22,8 @@ export const DatasetMetadataForm = ({ datasetRepository, metadataBlockInfoRepository }: DatasetMetadataFormProps) => { + const { setIsLoading } = useLoading() + const { metadataBlocksInfo, isLoading: isLoadingMetadataBlocksInfo, @@ -28,14 +34,24 @@ export const DatasetMetadataForm = ({ metadataBlockInfoRepository }) - console.log({ metadataBlocksInfo, isLoadingMetadataBlocksInfo, errorLoadingMetadataBlocksInfo }) + const formDefaultValues = MetadataFieldsHelper.getFormDefaultValues(metadataBlocksInfo) - // formDefaultValues here - return ( - <> - {/* is loading && skeleton else show form */} + useEffect(() => { + setIsLoading(isLoadingMetadataBlocksInfo) + }, [isLoadingMetadataBlocksInfo, setIsLoading]) -
- + if (isLoadingMetadataBlocksInfo) { + return + } + + return ( + ) } diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx index 62523e51d..527e9c44f 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { getMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId' import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId' import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' @@ -26,22 +27,28 @@ export const useGetMetadataBlocksInfo = ({ const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) - console.log({ mode, collectionId }) - useEffect(() => { const handleGetDatasetMetadataBlockFields = async () => { setIsLoading(true) try { - const metadataBlocks: MetadataBlockInfo[] = - await getDisplayedOnCreateMetadataBlockInfoByCollectionId( + let metadataBlocks: MetadataBlockInfo[] = [] + + if (mode === 'edit') { + metadataBlocks = await getMetadataBlockInfoByCollectionId( metadataBlockInfoRepository, collectionId ) + } else { + metadataBlocks = await getDisplayedOnCreateMetadataBlockInfoByCollectionId( + metadataBlockInfoRepository, + collectionId + ) + } + const mappedMetadataBlocks = MetadataFieldsHelper.replaceDotNamesKeysWithSlash(metadataBlocks) setMetadataBlocksInfo(mappedMetadataBlocks) } catch (err) { - console.error(err) const errorMessage = err instanceof Error && err.message ? err.message @@ -51,8 +58,9 @@ export const useGetMetadataBlocksInfo = ({ setIsLoading(false) } } + void handleGetDatasetMetadataBlockFields() - }, [collectionId, metadataBlockInfoRepository]) + }, [collectionId, metadataBlockInfoRepository, mode]) return { metadataBlocksInfo, diff --git a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts new file mode 100644 index 000000000..0b6c29eea --- /dev/null +++ b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts @@ -0,0 +1,87 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' +import { createDataset } from '../../../../dataset/domain/useCases/createDataset' +import { MetadataFieldsHelper, type DatasetMetadataFormValues } from './MetadataFieldsHelper' +import { getValidationFailedFieldError } from '../../../../metadata-block-info/domain/models/fieldValidations' +import { type DatasetMetadataFormMode } from '.' +import { Route } from '../../../Route.enum' + +export enum SubmissionStatus { + NotSubmitted = 'NotSubmitted', + IsSubmitting = 'IsSubmitting', + SubmitComplete = 'SubmitComplete', + Errored = 'Errored' +} + +type UseSubmitDatasetReturnType = + | { + submissionStatus: + | SubmissionStatus.NotSubmitted + | SubmissionStatus.IsSubmitting + | SubmissionStatus.SubmitComplete + submitForm: (formData: DatasetMetadataFormValues) => void + submitError: null + } + | { + submissionStatus: SubmissionStatus.Errored + submitForm: (formData: DatasetMetadataFormValues) => void + submitError: string + } + +export function useSubmitDataset( + mode: DatasetMetadataFormMode, + collectionId: string, + datasetRepository: DatasetRepository, + onSubmitErrorCallback: () => void +): UseSubmitDatasetReturnType { + const navigate = useNavigate() + const { t } = useTranslation('createDataset') + + const [submissionStatus, setSubmissionStatus] = useState( + SubmissionStatus.NotSubmitted + ) + const [submitError, setSubmitError] = useState(null) + + const submitForm = (formData: DatasetMetadataFormValues): void => { + setSubmissionStatus(SubmissionStatus.IsSubmitting) + + const formDataBackToOriginalKeys = MetadataFieldsHelper.replaceSlashKeysWithDot(formData) + + const formattedFormValues = MetadataFieldsHelper.formatFormValuesToDatasetDTO( + formDataBackToOriginalKeys + ) + + if (mode === 'create') { + createDataset(datasetRepository, formattedFormValues, collectionId) + .then(({ persistentId }) => { + setSubmitError(null) + setSubmissionStatus(SubmissionStatus.SubmitComplete) + navigate(`${Route.DATASETS}?persistentId=${persistentId}`, { + state: { created: true } + }) + return + }) + .catch((err) => { + const errorMessage = + err instanceof Error && err.message + ? getValidationFailedFieldError(err.message) ?? err.message + : t('validationAlert.content') + + setSubmitError(errorMessage) + setSubmissionStatus(SubmissionStatus.Errored) + + onSubmitErrorCallback() + }) + } else { + // TODO:ME Update dataset metadata use case here + } + } + + return { + submissionStatus, + submitForm, + submitError + } as UseSubmitDatasetReturnType +} From c3b91a6be80b2e4093c49b6d5ab5ced567ba0d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Wed, 12 Jun 2024 17:01:38 -0300 Subject: [PATCH 08/48] feat: use own component translations --- public/locales/en/datasetMetadataForm.json | 37 +++++ public/locales/en/editDatasetMetadata.json | 5 +- .../MetadataFormField/useDefineRules.ts | 14 +- .../DynamicFieldsButtons.tsx | 10 +- .../MetadataForm/MetadataFormSkeleton.tsx | 130 +++++++++--------- .../MetadataForm/index.tsx | 32 ++--- .../shared/form/DatasetMetadataForm/index.tsx | 3 + .../DatasetMetadataForm/useSubmitDataset.ts | 2 +- .../RequiredFieldText/RequiredFieldText.tsx | 2 +- 9 files changed, 134 insertions(+), 101 deletions(-) create mode 100644 public/locales/en/datasetMetadataForm.json diff --git a/public/locales/en/datasetMetadataForm.json b/public/locales/en/datasetMetadataForm.json new file mode 100644 index 000000000..3a4ba41a0 --- /dev/null +++ b/public/locales/en/datasetMetadataForm.json @@ -0,0 +1,37 @@ +{ + "metadataTip": { + "title": "Metadata Tip", + "content": "After adding the dataset, click the Edit Dataset button to add more metadata." + }, + "validationAlert": { + "title": "Validation Error", + "content": "Required fields were missed or there was a validation error. Please scroll down to see details." + }, + "requiredFields": "Asterisks indicate required fields", + "field": { + "required": "{{displayName}} is required", + "invalid": { + "url": "{{displayName}} is not a valid URL", + "email": "{{displayName}} is not a valid email", + "int": "{{displayName}} is not a valid integer", + "float": "{{displayName}} is not a valid float", + "date": "{{displayName}} is not a valid date. Please use the format {{dateFormat}}" + } + }, + "status": { + "submitting": "Submitting...", + "success": "Form submitted successfully!", + "failed": "Error: Submission failed." + }, + "states": { + "submitting": "Form Submitting", + "submissionSuccess": "Form submission successful" + }, + "addRowButton": "Add", + "deleteRowButton": "Delete", + "saveButton": { + "createMode": "Save Dataset", + "editMode": "Save Changes" + }, + "cancelButton": "Cancel" +} diff --git a/public/locales/en/editDatasetMetadata.json b/public/locales/en/editDatasetMetadata.json index 0c571529d..ee406b0da 100644 --- a/public/locales/en/editDatasetMetadata.json +++ b/public/locales/en/editDatasetMetadata.json @@ -6,8 +6,7 @@ }, "hostCollection": { "label": "Host Collection", - "description": "The collection which contains this dataset." + "description": "The collection which contains this data." }, - "metadata": "Metadata", - "requiredFields": "Asterisks indicate required fields" + "metadata": "Metadata" } diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts index 48dde728e..1ae811882 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/useDefineRules.ts @@ -20,11 +20,11 @@ interface Props { export type DefinedRules = UseControllerProps['rules'] export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { - const { t } = useTranslation('createDataset') + const { t } = useTranslation('datasetMetadataForm') const { type, displayName, isRequired, watermark } = metadataFieldInfo const rulesToApply: DefinedRules = { - required: isRequired ? t('datasetForm.field.required', { displayName }) : false, + required: isRequired ? t('field.required', { displayName }) : false, validate: (value: string) => { if (!value) { return true @@ -32,7 +32,7 @@ export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { if (type === TypeMetadataFieldOptions.URL) { if (!isValidURL(value)) { - return t('datasetForm.field.invalid.url', { displayName }) + return t('field.invalid.url', { displayName }) } return true } @@ -41,25 +41,25 @@ export const useDefineRules = ({ metadataFieldInfo }: Props): DefinedRules => { watermark === 'YYYY-MM-DD' ? DateFormatsOptions.YYYYMMDD : undefined if (!isValidDateFormat(value, acceptedDateFormat)) { - return t('datasetForm.field.invalid.date', { displayName, dateFormat: watermark }) + return t('field.invalid.date', { displayName, dateFormat: watermark }) } return true } if (type === TypeMetadataFieldOptions.Email) { if (!isValidEmail(value)) { - return t('datasetForm.field.invalid.email', { displayName }) + return t('field.invalid.email', { displayName }) } return true } if (type === TypeMetadataFieldOptions.Int) { if (!isValidInteger(value)) { - return t('datasetForm.field.invalid.int', { displayName }) + return t('field.invalid.int', { displayName }) } return true } if (type === TypeMetadataFieldOptions.Float) { if (!isValidFloat(value)) { - return t('datasetForm.field.invalid.float', { displayName }) + return t('field.invalid.float', { displayName }) } return true } diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx index 854a5bd9d..bafa1ca4f 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/dynamic-fields-buttons/DynamicFieldsButtons.tsx @@ -17,27 +17,27 @@ export function DynamicFieldsButtons({ onAddButtonClick, onRemoveButtonClick }: AddFieldButtonsProps) { - const { t } = useTranslation('createDataset') + const { t } = useTranslation('datasetMetadataForm') return (
- + {!originalField && ( - + diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx index ea9fff918..d22762be2 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataFormSkeleton.tsx @@ -2,76 +2,74 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' import { Accordion, Col, Row } from '@iqss/dataverse-design-system' import { SeparationLine } from '../../../layout/SeparationLine/SeparationLine' import 'react-loading-skeleton/dist/skeleton.css' -//TODO:ME Check if we can add skeleton css once in the app and if tests still pass -export const MetadataFormSkeleton = () => { - return ( - -
- - - - - - - - {Array.from({ length: 4 }).map((_, index) => ( - - - - - - - - - - {index === 2 && ( - - - - )} - - - - ))} - {Array.from({ length: 3 }).map((_, index) => ( - - - - - - - - - - - - - - - - - - - +//TODO:ME Check if we can add skeleton css once in the app and if tests still pass +export const MetadataFormSkeleton = () => ( + +
+ + + + + + + + {Array.from({ length: 4 }).map((_, index) => ( + + + + + + + + + + {index === 2 && ( + - - - - ))} - - - + )} + + + + ))} + {Array.from({ length: 3 }).map((_, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + ))} + + + - + -
- +
+ - -
+
- - ) -} +
+
+) diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx index e3c322f64..f344fcddc 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx @@ -32,25 +32,23 @@ export const MetadataForm = ({ datasetRepository }: FormProps) => { const navigate = useNavigate() - const { t } = useTranslation('createDataset') + const { t } = useTranslation('datasetMetadataForm') const accordionRef = useRef(null) const formContainerRef = useRef(null) + const onCreateMode = mode === 'create' + const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocksInfo) + + const form = useForm({ mode: 'onChange', defaultValues: formDefaultValues }) + const { submissionStatus, submitError, submitForm } = useSubmitDataset( mode, collectionId, datasetRepository, - onCreateDatasetError + onSubmitDatasetError ) - const isErrorLoadingMetadataBlocks = Boolean(errorLoadingMetadataBlocksInfo) - - const form = useForm({ - mode: 'onChange', - defaultValues: formDefaultValues - }) - const handleCancel = (event: MouseEvent) => { event.preventDefault() navigate(Route.HOME) @@ -87,7 +85,7 @@ export const MetadataForm = ({ }) } - function onCreateDatasetError() { + function onSubmitDatasetError() { if (formContainerRef.current) { formContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }) } @@ -100,18 +98,16 @@ export const MetadataForm = ({ return (
+ {isErrorLoadingMetadataBlocks && ( {errorLoadingMetadataBlocksInfo} )} - {submissionStatus === SubmissionStatus.IsSubmitting && ( -

{t('datasetForm.status.submitting')}

- )} + {submissionStatus === SubmissionStatus.IsSubmitting &&

{t('status.submitting')}

} + + {submissionStatus === SubmissionStatus.SubmitComplete &&

{t('status.success')}

} - {submissionStatus === SubmissionStatus.SubmitComplete && ( -

{t('datasetForm.status.success')}

- )} {submissionStatus === SubmissionStatus.Errored && ( {submitError} @@ -138,14 +134,14 @@ export const MetadataForm = ({ - {mode === 'create' && ( + {onCreateMode && ( {t('metadataTip.content')} )}
diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts index 2b4086c04..7326aa7cf 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts @@ -7,6 +7,10 @@ import { DatasetMetadataBlockValuesDTO, DatasetMetadataChildFieldValueDTO } from '../../../../dataset/domain/useCases/DTOs/DatasetDTO' +import { + DatasetMetadataBlocks, + DatasetMetadataFields +} from '../../../../dataset/domain/models/Dataset' export type DatasetMetadataFormValues = Record @@ -29,27 +33,28 @@ export class MetadataFieldsHelper { ): MetadataBlockInfo[] { for (const block of metadataBlocks) { if (block.metadataFields) { - this.dotReplacer(block.metadataFields) + this.metadataBlocksInfoDotReplacer(block.metadataFields) } } return metadataBlocks } - - private static dotReplacer(metadataFields: Record | undefined) { + private static metadataBlocksInfoDotReplacer( + metadataFields: Record | undefined + ) { if (!metadataFields) return for (const key in metadataFields) { const field = metadataFields[key] if (field.name.includes('.')) { - field.name = field.name.replace(/\./g, '/') + field.name = this.replaceDotWithSlash(field.name) } if (field.childMetadataFields) { - this.dotReplacer(field.childMetadataFields) + this.metadataBlocksInfoDotReplacer(field.childMetadataFields) } } } - public static getFormDefaultValues( + public static getCreateFormDefaultValues( metadataBlocks: MetadataBlockInfo[] ): DatasetMetadataFormValues { const formDefaultValues: DatasetMetadataFormValues = {} @@ -98,13 +103,13 @@ export class MetadataFieldsHelper { const formattedNewObject: DatasetMetadataFormValues = {} for (const key in obj) { - const blockKey = key.replace(/\//g, '.') + const blockKey = this.replaceSlashWithDot(key) const metadataBlockFormValues = obj[key] formattedNewObject[blockKey] = {} Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => { - const newFieldName = fieldName.replace(/\//g, '.') + const newFieldName = this.replaceSlashWithDot(fieldName) if ( this.isPrimitiveFieldValue(fieldValue) || @@ -118,7 +123,7 @@ export class MetadataFieldsHelper { if (this.isComposedSingleFieldValue(fieldValue)) { formattedNewObject[blockKey][newFieldName] = {} Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => { - const newNestedFieldName = nestedFieldName.replace(/\//g, '.') + const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName) const parentOfNestedField = formattedNewObject[blockKey][ newFieldName ] as ComposedSingleFieldValue @@ -133,7 +138,7 @@ export class MetadataFieldsHelper { const composedField: ComposedSingleFieldValue = {} Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => { - const newNestedFieldName = nestedFieldName.replace(/\//g, '.') + const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName) composedField[newNestedFieldName] = nestedFieldValue }) @@ -227,6 +232,42 @@ export class MetadataFieldsHelper { return { metadataBlocks } } + public static addFieldValuesToMetadataBlocksInfo( + metadataBlocksInfo: MetadataBlockInfo[], + datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks + ): MetadataBlockInfo[] { + // To avoid mutating the original metadataBlocksInfo object + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const clonedMetadataBlocksInfo: MetadataBlockInfo[] = structuredClone(metadataBlocksInfo) + + const currentValuesMap: Record = + datasetMetadaBlocksCurrentValues.reduce((map, block) => { + map[block.name] = block.fields + return map + }, {} as Record) + + // Add the current values to the metadata fields + clonedMetadataBlocksInfo.forEach((block) => { + const currentBlockValues = currentValuesMap[block.name] + + if (currentBlockValues) { + // TODO:ME Check if we need to test also without dots slash + Object.keys(block.metadataFields).forEach((fieldName) => { + const field = block.metadataFields[fieldName] + + if (fieldName in currentBlockValues) { + field.value = currentBlockValues[fieldName] + } + }) + } + }) + + return clonedMetadataBlocksInfo + } + + private static replaceDotWithSlash = (str: string) => str.replace(/\./g, '/') + private static replaceSlashWithDot = (str: string) => str.replace(/\//g, '.') + /* * To define the field name that will be used to register the field in the form * Most basic could be: metadataBlockName.name eg: citation.title diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx index 7c567af33..1d11e5431 100644 --- a/src/sections/shared/form/DatasetMetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { useLoading } from '../../../loading/LoadingContext' import { useGetMetadataBlocksInfo } from './useGetMetadataBlocksInfo' import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' @@ -6,17 +6,26 @@ import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/dom import { MetadataFieldsHelper } from './MetadataFieldsHelper' import { MetadataFormSkeleton } from './MetadataForm/MetadataFormSkeleton' import { MetadataForm } from './MetadataForm' +import { DatasetMetadataBlocks } from '../../../../dataset/domain/models/Dataset' -type DatasetMetadataFormProps = { - mode: DatasetMetadataFormMode - collectionId: string - datasetRepository: DatasetRepository - metadataBlockInfoRepository: MetadataBlockInfoRepository -} +type DatasetMetadataFormProps = + | { + mode: 'create' + collectionId: string + datasetRepository: DatasetRepository + metadataBlockInfoRepository: MetadataBlockInfoRepository + datasetMetadaBlocksCurrentValues?: never + } + | { + mode: 'edit' + collectionId: string + datasetRepository: DatasetRepository + metadataBlockInfoRepository: MetadataBlockInfoRepository + datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks + } export type DatasetMetadataFormMode = 'create' | 'edit' -// TODO:ME Ask about collection name and labels on top? is needed? // TODO:ME Keep both accordions open as in JSF version ? // TODO:ME Add Save and cancel button on top also but where ? and only on edit mode ? // TODO:ME After removing form from create-dataset also remove unused translations @@ -24,9 +33,11 @@ export const DatasetMetadataForm = ({ mode, collectionId, datasetRepository, - metadataBlockInfoRepository + metadataBlockInfoRepository, + datasetMetadaBlocksCurrentValues }: DatasetMetadataFormProps) => { const { setIsLoading } = useLoading() + const onEditMode = mode === 'edit' const { metadataBlocksInfo, @@ -38,13 +49,34 @@ export const DatasetMetadataForm = ({ metadataBlockInfoRepository }) - const formDefaultValues = MetadataFieldsHelper.getFormDefaultValues(metadataBlocksInfo) + console.log({ metadataBlocksInfo, datasetMetadaBlocksCurrentValues }) + + //TODO:ME Remove 'as' and only run this if onEditMode + const withAddedFieldsValues = MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + metadataBlocksInfo, + datasetMetadaBlocksCurrentValues as DatasetMetadataBlocks + ) + + console.log({ withAddedFieldsValues }) + + const formDefaultValues = useMemo(() => { + if (metadataBlocksInfo.length === 0) return undefined + + // if (onEditMode) { + // return MetadataFieldsHelper.getEditFormDefaultValues( + // metadataBlocksInfo, + // datasetMetadaBlocksCurrentValues + // ) + // } + + return MetadataFieldsHelper.getCreateFormDefaultValues(metadataBlocksInfo) + }, [metadataBlocksInfo, datasetMetadaBlocksCurrentValues, onEditMode]) useEffect(() => { setIsLoading(isLoadingMetadataBlocksInfo) }, [isLoadingMetadataBlocksInfo, setIsLoading]) - if (isLoadingMetadataBlocksInfo) { + if (isLoadingMetadataBlocksInfo || !formDefaultValues) { return } diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx index 527e9c44f..55e5d65fd 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -3,7 +3,7 @@ import { getMetadataBlockInfoByCollectionId } from '../../../../metadata-block-i import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../../../metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId' import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' -import { MetadataFieldsHelper } from '../../../create-dataset/MetadataFieldsHelper' +import { MetadataFieldsHelper } from './MetadataFieldsHelper' import { DatasetMetadataFormMode } from '.' interface Props { @@ -47,6 +47,7 @@ export const useGetMetadataBlocksInfo = ({ const mappedMetadataBlocks = MetadataFieldsHelper.replaceDotNamesKeysWithSlash(metadataBlocks) + setMetadataBlocksInfo(mappedMetadataBlocks) } catch (err) { const errorMessage = From 2c70c2886b0e7a147276b46bd8d0d520e6f9badd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 14 Jun 2024 09:40:36 -0300 Subject: [PATCH 12/48] feat: form default values on edit mode working :) --- .../domain/models/MetadataBlockInfo.ts | 9 ++++- .../MetadataFieldsHelper.ts | 34 ++++++++++++++----- .../MetadataForm/index.tsx | 2 ++ .../shared/form/DatasetMetadataForm/index.tsx | 34 ++++++++----------- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts index b9e96afb4..4e2cd657d 100644 --- a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts +++ b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts @@ -8,6 +8,10 @@ export interface MetadataBlockInfo { displayOnCreate: boolean } +export interface MetadataBlockInfoWithMaybeValues extends MetadataBlockInfo { + metadataFields: Record +} + export interface MetadataField { name: string displayName: string @@ -24,7 +28,10 @@ export interface MetadataField { controlledVocabularyValues?: string[] childMetadataFields?: Record displayOnCreate: boolean - value?: DatasetMetadataFieldValue // Only present after adding values to the metadata fields +} + +export interface MetadataFieldWithMaybeValue extends MetadataField { + value?: DatasetMetadataFieldValue } export const TypeMetadataFieldOptions = { diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts index 7326aa7cf..b0197fc5f 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts @@ -1,6 +1,8 @@ import { MetadataBlockInfo, - MetadataField + MetadataBlockInfoWithMaybeValues, + MetadataField, + MetadataFieldWithMaybeValue } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { DatasetDTO, @@ -54,8 +56,8 @@ export class MetadataFieldsHelper { } } - public static getCreateFormDefaultValues( - metadataBlocks: MetadataBlockInfo[] + public static getFormDefaultValues( + metadataBlocks: MetadataBlockInfoWithMaybeValues[] ): DatasetMetadataFormValues { const formDefaultValues: DatasetMetadataFormValues = {} @@ -85,7 +87,9 @@ export class MetadataFieldsHelper { } if (field.typeClass === 'primitive') { - blockValues[fieldName] = field.multiple ? [{ value: '' }] : '' + console.log(fieldName, field.value) + + blockValues[fieldName] = this.getPrimitiveFieldDefaultFormValue(field) } if (field.typeClass === 'controlledVocabulary') { @@ -99,6 +103,20 @@ export class MetadataFieldsHelper { return formDefaultValues } + private static getPrimitiveFieldDefaultFormValue( + field: MetadataFieldWithMaybeValue + ): string | PrimitiveMultipleFormValue { + if (field.multiple) { + const castedFieldValue = field.value as string[] + + if (!castedFieldValue) return [{ value: '' }] + + return castedFieldValue.map((stringValue) => ({ value: stringValue })) + } + const castedFieldValue = field.value as string | undefined + return castedFieldValue ?? '' + } + public static replaceSlashKeysWithDot(obj: DatasetMetadataFormValues): DatasetMetadataFormValues { const formattedNewObject: DatasetMetadataFormValues = {} @@ -235,10 +253,10 @@ export class MetadataFieldsHelper { public static addFieldValuesToMetadataBlocksInfo( metadataBlocksInfo: MetadataBlockInfo[], datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks - ): MetadataBlockInfo[] { - // To avoid mutating the original metadataBlocksInfo object + ): MetadataBlockInfoWithMaybeValues[] { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const clonedMetadataBlocksInfo: MetadataBlockInfo[] = structuredClone(metadataBlocksInfo) + const clonedMetadataBlocksInfo: MetadataBlockInfoWithMaybeValues[] = + structuredClone(metadataBlocksInfo) const currentValuesMap: Record = datasetMetadaBlocksCurrentValues.reduce((map, block) => { @@ -246,12 +264,10 @@ export class MetadataFieldsHelper { return map }, {} as Record) - // Add the current values to the metadata fields clonedMetadataBlocksInfo.forEach((block) => { const currentBlockValues = currentValuesMap[block.name] if (currentBlockValues) { - // TODO:ME Check if we need to test also without dots slash Object.keys(block.metadataFields).forEach((fieldName) => { const field = block.metadataFields[fieldName] diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx index d5f1711dd..1c0cb066d 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/index.tsx @@ -113,6 +113,8 @@ export const MetadataForm = ({ return isErrorLoadingMetadataBlocks || submissionStatus === SubmissionStatus.IsSubmitting }, [isErrorLoadingMetadataBlocks, submissionStatus]) + console.log({ formDefaultValues }) + return (
diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx index 1d11e5431..5fc5674e1 100644 --- a/src/sections/shared/form/DatasetMetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -49,28 +49,24 @@ export const DatasetMetadataForm = ({ metadataBlockInfoRepository }) - console.log({ metadataBlocksInfo, datasetMetadaBlocksCurrentValues }) + // If we are in edit mode, we need to add the values to the metadata blocks info + const metadataBlocksInfoWithValues = useMemo(() => { + if (metadataBlocksInfo.length === 0) return null - //TODO:ME Remove 'as' and only run this if onEditMode - const withAddedFieldsValues = MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( - metadataBlocksInfo, - datasetMetadaBlocksCurrentValues as DatasetMetadataBlocks - ) - - console.log({ withAddedFieldsValues }) + return onEditMode + ? MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + metadataBlocksInfo, + datasetMetadaBlocksCurrentValues + ) + : null + }, [metadataBlocksInfo, datasetMetadaBlocksCurrentValues, onEditMode]) + // Set the form default values object based on the metadata blocks info const formDefaultValues = useMemo(() => { - if (metadataBlocksInfo.length === 0) return undefined - - // if (onEditMode) { - // return MetadataFieldsHelper.getEditFormDefaultValues( - // metadataBlocksInfo, - // datasetMetadaBlocksCurrentValues - // ) - // } - - return MetadataFieldsHelper.getCreateFormDefaultValues(metadataBlocksInfo) - }, [metadataBlocksInfo, datasetMetadaBlocksCurrentValues, onEditMode]) + return onEditMode && metadataBlocksInfoWithValues !== null + ? MetadataFieldsHelper.getFormDefaultValues(metadataBlocksInfoWithValues) + : MetadataFieldsHelper.getFormDefaultValues(metadataBlocksInfo) + }, [metadataBlocksInfo, metadataBlocksInfoWithValues, onEditMode]) useEffect(() => { setIsLoading(isLoadingMetadataBlocksInfo) From 8911e5f4025de9b7c8ccc215617cbdd76c0398bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 14 Jun 2024 16:41:09 -0300 Subject: [PATCH 13/48] feat(DesignSystem): new value prop --- .../components/form/form-group/form-element/FormSelect.tsx | 4 +++- .../components/form/form-group/form-element/FormTextArea.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx index fb99ce748..c70a3c39d 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelect.tsx @@ -5,17 +5,19 @@ import * as React from 'react' export interface FormSelectProps extends Omit, 'size'> { + value?: string isInvalid?: boolean isValid?: boolean disabled?: boolean } export const FormSelect = React.forwardRef(function FormSelect( - { isInvalid, isValid, disabled, children, ...props }: PropsWithChildren, + { value, isInvalid, isValid, disabled, children, ...props }: PropsWithChildren, ref ) { return ( } {...props} /> From 63ac8b43f7df6abcb7e2df73884f36f12f845d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 14 Jun 2024 16:42:19 -0300 Subject: [PATCH 14/48] feat: field inputs value --- .../MetadataFormField/Fields/ComposeFieldMultiple.tsx | 2 +- .../MetadataFormField/Fields/Primitive.tsx | 1 + .../MetadataFormField/Fields/PrimitiveMultiple.tsx | 4 +++- .../MetadataFormField/Fields/Vocabulary.tsx | 8 ++++++-- .../MetadataFormField/Fields/VocabularyMultiple.tsx | 3 ++- .../MetadataFormField/index.module.scss | 4 ++++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx index db672ad4a..7d0e49977 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/ComposeFieldMultiple.tsx @@ -52,7 +52,7 @@ export const ComposedFieldMultiple = ({ {fieldsArray.map((field, index) => { return ( - + {Object.entries(childMetadataFields).map( ([childMetadataFieldKey, childMetadataFieldInfo]) => { diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx index 896177f0a..a5371e6d6 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Primitive.tsx @@ -58,6 +58,7 @@ export const Primitive = ({ {isTextArea ? ( ( + render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => ( <> {isTextArea ? ( ( + render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => ( - + {options.map((option) => (