From 61f13eedaaabebc9cd323a635bb285149ebb36a0 Mon Sep 17 00:00:00 2001 From: Emmanuel DEMEY Date: Thu, 22 Aug 2024 14:51:47 +0200 Subject: [PATCH] fix: solve issue when updating an existing component with a code list (#894) * feat: solve issue when saving Component with a code list * feat: create reusable See button * fix: solve typescript issue * fix: remove onClick props --- src/packages/components/buttons/see.spec.tsx | 40 +++++++++++ src/packages/components/buttons/see.tsx | 25 +++++++ src/packages/components/index.ts | 1 + src/packages/model/structures/Component.ts | 1 + .../edit/tabs/statistical-information.js | 12 ++-- .../modules-datasets/datasets/view/view.js | 12 ++-- .../components/sims/sims-geography-picker.js | 9 +-- .../modules-operations/i18n/dictionary.js | 4 -- src/packages/modules-structures/apis/index.ts | 1 - .../{edit-container.js => edit-container.tsx} | 35 ++++----- .../components/component-detail/edit.js | 52 +++++++------- .../component-detail/view-container.js | 18 +++-- .../components/component-detail/view.js | 8 +-- .../visualisation/measureAttribute.js | 8 +-- .../components/component-search/search.js | 5 +- .../components/component-selector/index.js | 30 ++++---- .../component-specification-form/index.js | 6 +- .../components/components-list/index.js | 5 +- .../mutualized-component-selector/index.js | 8 +-- .../structure-component-selector/index.tsx | 10 ++- .../components/structure-search/search.js | 8 ++- .../structure-visualization/controls.js | 4 +- .../modules-structures/edition/component.js | 6 +- .../edition/components/index.js | 5 +- .../edition/update/index.js | 4 +- src/packages/modules-structures/home/home.js | 4 +- .../modules-structures/i18n/dictionary.js | 4 -- .../visualization/index.tsx | 6 +- src/packages/sdk/index.ts | 1 + .../apis => sdk}/structure-api.ts | 10 +-- .../sdk/structures/saveComponent.spec.ts | 71 +++++++++++++++++++ src/packages/sdk/structures/saveComponent.ts | 17 +++++ src/packages/utils/hooks/structures.spec.ts | 20 ++++++ src/packages/utils/hooks/structures.ts | 8 +++ 34 files changed, 304 insertions(+), 154 deletions(-) create mode 100644 src/packages/components/buttons/see.spec.tsx create mode 100644 src/packages/components/buttons/see.tsx rename src/packages/modules-structures/components/component-detail/{edit-container.js => edit-container.tsx} (74%) rename src/packages/{modules-structures/apis => sdk}/structure-api.ts (86%) create mode 100644 src/packages/sdk/structures/saveComponent.spec.ts create mode 100644 src/packages/sdk/structures/saveComponent.ts create mode 100644 src/packages/utils/hooks/structures.spec.ts create mode 100644 src/packages/utils/hooks/structures.ts diff --git a/src/packages/components/buttons/see.spec.tsx b/src/packages/components/buttons/see.spec.tsx new file mode 100644 index 000000000..ccc31b178 --- /dev/null +++ b/src/packages/components/buttons/see.spec.tsx @@ -0,0 +1,40 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { SeeButton } from './see'; // Chemin d'accès au fichier du composant + +describe('SeeButton', () => { + it('renders the button with the correct attributes and icon', () => { + render(); + + const button = screen.getByRole('button'); + + expect(button).not.toBeNull(); + expect(button.classList.contains('btn')).toBeTruthy(); + expect(button.classList.contains('btn-default')).toBeTruthy(); + + expect(button.getAttribute('aria-label')).toBe('See'); + expect(button.getAttribute('title')).toBe('See'); + expect(button.getAttribute('type')).toBe('button'); + + const icon = button.querySelector('span')!; + expect(icon.classList.contains('glyphicon')).toBeTruthy(); + expect(icon.classList.contains('glyphicon-eye-open')).toBeTruthy(); + }); + + it('calls the onClick handler when clicked', () => { + const handleClick = jest.fn(); + render(); + + const button = screen.getByRole('button'); + fireEvent.click(button); + + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('passes additional props to the button element', () => { + render(); + + const button = screen.getByRole('button'); + + expect(button.dataset.test).toEqual('see-button'); + }); +}); diff --git a/src/packages/components/buttons/see.tsx b/src/packages/components/buttons/see.tsx new file mode 100644 index 000000000..334b343a6 --- /dev/null +++ b/src/packages/components/buttons/see.tsx @@ -0,0 +1,25 @@ +import { createAllDictionary } from '../../utils/dictionnary'; + +const { D } = createAllDictionary({ + see: { + fr: 'Voir', + en: 'See', + }, +}); + +type SeeButtonTypes = { + onClick: (e: any) => void; +}; +export const SeeButton = ({ onClick, ...props }: Readonly) => { + return ( + + ); +}; diff --git a/src/packages/components/index.ts b/src/packages/components/index.ts index 2b5212103..751b1b1cc 100644 --- a/src/packages/components/index.ts +++ b/src/packages/components/index.ts @@ -3,6 +3,7 @@ export * from './data/creators'; export * from './advanced-search/home'; export * from './advanced-search/controls'; export * from './application-title'; +export * from './buttons/see'; export * from './check-second-lang'; export * from './confirmation-delete'; export * from './contributors/contributors'; diff --git a/src/packages/model/structures/Component.ts b/src/packages/model/structures/Component.ts index fbfcd6dcb..87116cdfb 100644 --- a/src/packages/model/structures/Component.ts +++ b/src/packages/model/structures/Component.ts @@ -11,6 +11,7 @@ export type Component = { required?: boolean; attachment?: string; codeList?: string; + fullCodeListValue?: string; concept?: string; }; diff --git a/src/packages/modules-datasets/datasets/edit/tabs/statistical-information.js b/src/packages/modules-datasets/datasets/edit/tabs/statistical-information.js index cd5521200..2b595f89f 100644 --- a/src/packages/modules-datasets/datasets/edit/tabs/statistical-information.js +++ b/src/packages/modules-datasets/datasets/edit/tabs/statistical-information.js @@ -1,12 +1,12 @@ import { D1 } from '../../../../deprecated-locales'; -import { useQuery } from '@tanstack/react-query'; import { withCodesLists } from '../../../../utils/hoc/withCodesLists'; import ReactSelect from 'react-select'; -import StructureAPI from '../../../../modules-structures/apis/structure-api'; import { TemporalField } from '../../components/temporalField'; import { CL_FREQ } from '../../../../redux/actions/constants/codeList'; import { Row } from '../../../../components'; import { convertCodesListsToSelectOption } from '../../../../modules-datasets/utils/codelist-to-select-options'; + +import { useStructures } from '../../../../utils/hooks/structures'; const StatisticalInformationTab = ({ editingDataset, setEditingDataset, @@ -14,12 +14,8 @@ const StatisticalInformationTab = ({ }) => { const clDataTypes = convertCodesListsToSelectOption(props['CL_DATA_TYPES']); - const { data: structures } = useQuery({ - queryKey: ['structures'], - queryFn: () => { - return StructureAPI.getStructures(); - }, - }); + const { data: structures } = useStructures(); + const structuresOptions = structures?.map(({ iri, labelLg1 }) => ({ value: iri, label: labelLg1 })) ?? []; diff --git a/src/packages/modules-datasets/datasets/view/view.js b/src/packages/modules-datasets/datasets/view/view.js index 7470381ba..f7cd064ca 100644 --- a/src/packages/modules-datasets/datasets/view/view.js +++ b/src/packages/modules-datasets/datasets/view/view.js @@ -5,8 +5,7 @@ import { useEffect, useState } from 'react'; import { Note } from '@inseefr/wilco'; import D, { D1, D2 } from '../../../deprecated-locales/build-dictionary'; import api from '../../api/datasets-api'; -import StructureAPI from '../../../modules-structures/apis/structure-api'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useThemes } from '../useThemes'; import { withCodesLists } from '../../../utils/hoc/withCodesLists'; import { useDataset } from '../../hooks'; @@ -31,16 +30,13 @@ import { getLocales } from '../../../redux/selectors'; import { useTitle } from '../../../utils/hooks/useTitle'; import { stringToDate } from '../../../utils/date-utils'; import { getSecondLang } from '../../../redux/second-lang'; +import { useStructures } from '../../../utils/hooks/structures'; const Dataset = (props) => { const { id } = useParams(); const history = useHistory(); - const { data: structures } = useQuery({ - queryKey: ['structures'], - queryFn: () => { - return StructureAPI.getStructures(); - }, - }); + const { data: structures } = useStructures(); + const [archivageUnits, setArchivageUnits] = useState([]); useEffect(() => { diff --git a/src/packages/modules-operations/components/sims/sims-geography-picker.js b/src/packages/modules-operations/components/sims/sims-geography-picker.js index 99bb35e42..f289fb9ba 100644 --- a/src/packages/modules-operations/components/sims/sims-geography-picker.js +++ b/src/packages/modules-operations/components/sims/sims-geography-picker.js @@ -13,6 +13,7 @@ import { } from '../../../redux/geographies.action'; import Auth from '../../../auth/components/auth'; import { ADMIN } from '../../../auth/roles'; +import { SeeButton } from '../../../components'; const accentsMap = new Map([ ['A', 'Á|À|Ã|Â|Ä'], @@ -127,14 +128,10 @@ const SimsGeographyPicker = ({ {D.btnNew} - + > { +const EditContainer = (props: any) => { const goBack = useGoBack(); - const { id } = useParams(); + const { id } = useParams<{ id: string }>(); const urlParams = new URLSearchParams(window.location.search); const type = urlParams.get('type'); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); const [component, setComponent] = useState({}); const [concepts, setConcepts] = useState([]); - const [codesLists, setCodesLists] = useState([]); - const [serverSideError, setServerSideError] = useState(''); + const [codesLists, setCodesLists] = useState([]); + const [serverSideError, setServerSideError] = useState(''); const [attributes, setAttributes] = useState([]); const stampListOptions = useStampsOptions(); @@ -30,23 +31,15 @@ const EditContainer = (props) => { ); const handleSave = useCallback( - (component) => { + (component: Component) => { setSaving(true); setServerSideError(''); - let request; - - if (component.id) { - request = api.putMutualizedComponent(component); - } else { - request = api.postMutualizedComponent(component); - } - - request + saveComponent(component) .then((id = component.id) => goBack(`/structures/components/${id}`, !component.id) ) - .catch((error) => { + .catch((error: string) => { setComponent(component); setServerSideError(error); }) @@ -57,11 +50,11 @@ const EditContainer = (props) => { useEffect(() => { const getComponent = id - ? api.getMutualizedComponent(id) + ? StructureApi.getMutualizedComponent(id) : Promise.resolve({}); Promise.all([ getComponent, - api.getMutualizedAttributes(), + StructureApi.getMutualizedAttributes(), ConceptsApi.getConceptList(), getFormattedCodeList(), ]) diff --git a/src/packages/modules-structures/components/component-detail/edit.js b/src/packages/modules-structures/components/component-detail/edit.js index 811b80801..17154339f 100644 --- a/src/packages/modules-structures/components/component-detail/edit.js +++ b/src/packages/modules-structures/components/component-detail/edit.js @@ -18,12 +18,11 @@ import { XSD_TYPES, IGEO_PAYS_OU_TERRITOIRE, } from '../../utils/constants'; -import { CodeListApi } from '../../../sdk'; +import { CodeListApi, StructureApi } from '../../../sdk'; import D, { D1, D2 } from '../../i18n/build-dictionary'; import './edit.scss'; import { CodesListPanel } from '../codes-list-panel/codes-list-panel'; import { API } from '../../../modules-codelists/apis'; -import api from '../../apis/structure-api'; import { useSelector } from 'react-redux'; import { convertToArrayIfDefined, sortArray } from '../../../utils/array-utils'; import { @@ -35,6 +34,7 @@ import { GlobalClientSideErrorBloc, ClientSideError, Select, + SeeButton, } from '../../../components'; import { useTitle } from '../../../utils/hooks/useTitle'; import { AppContext } from '../../../application/app-context'; @@ -98,7 +98,7 @@ const CodeListFormInput = ({ component, codesLists, setComponent }) => { return ( <> -
+
{ setComponent({ ...component, codeList: value }) } /> - + >
-
+ )} }
-
+
{D1.idTitle}
-
+ -
+
{D1.label} ({lg1}) @@ -492,7 +486,7 @@ export const DumbComponentDetailEdit = ({ )} {(component.range === XSD_INTEGER || component.range === XSD_FLOAT) && ( <> -
+
-
-
+ +
-
-
+ +
-
-
+ +
-
+ )} {component.range === XSD_CODE_LIST && ( @@ -731,7 +725,9 @@ const AttributeCodeList = ({ const AttributeValue = ({ onChange, value, codesLists, attributeId }) => { const [attribute, setAttribute] = useState(); useEffect(() => { - api.getMutualizedComponent(attributeId).then((body) => setAttribute(body)); + StructureApi.getMutualizedComponent(attributeId).then((body) => + setAttribute(body) + ); }, [attributeId]); if (!attribute) { diff --git a/src/packages/modules-structures/components/component-detail/view-container.js b/src/packages/modules-structures/components/component-detail/view-container.js index 6af80a5e4..ca1c3308b 100644 --- a/src/packages/modules-structures/components/component-detail/view-container.js +++ b/src/packages/modules-structures/components/component-detail/view-container.js @@ -2,8 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useGoBack } from '../../../utils/hooks/useGoBack'; import { Loading } from '../../../components'; import { ComponentDetailView } from './view'; -import api from '../../apis/structure-api'; -import { ConceptsApi } from '../../../sdk'; +import { ConceptsApi, StructureApi } from '../../../sdk'; import ComponentTitle from './title'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; @@ -33,15 +32,15 @@ const ViewContainer = (props) => { const handleDelete = useCallback(() => { setLoading(true); - api - .deleteMutualizedComponent(id) - .then(() => goBack('/structures/components')); + StructureApi.deleteMutualizedComponent(id).then(() => + goBack('/structures/components') + ); }, [id, goBack]); useEffect(() => { Promise.all([ - api.getMutualizedComponent(id), - api.getMutualizedAttributes(), + StructureApi.getMutualizedComponent(id), + StructureApi.getMutualizedAttributes(), ConceptsApi.getConceptList(), getFormattedCodeList(), ]) @@ -62,9 +61,8 @@ const ViewContainer = (props) => { const publishComponent = () => { setLoading(true); - return api - .publishMutualizedComponent(component) - .then(() => api.getMutualizedComponent(component.id)) + return StructureApi.publishMutualizedComponent(component) + .then(() => StructureApi.getMutualizedComponent(component.id)) .then((component) => setComponent(component)) .finally(() => setLoading(false)) .catch(setServerSideError); diff --git a/src/packages/modules-structures/components/component-detail/view.js b/src/packages/modules-structures/components/component-detail/view.js index 34a159cdb..90073c98c 100644 --- a/src/packages/modules-structures/components/component-detail/view.js +++ b/src/packages/modules-structures/components/component-detail/view.js @@ -22,6 +22,7 @@ import { DisseminationStatusVisualisation, ErrorBloc, CreationUpdateItems, + SeeButton, } from '../../../components'; import { renderMarkdownElement } from '../../../utils/html-utils'; import { useTitle } from '../../../utils/hooks/useTitle'; @@ -203,12 +204,9 @@ export const ComponentDetailView = ({ text={
{codeListValue} - + >
} title={D1.codesListTitle} diff --git a/src/packages/modules-structures/components/component-detail/visualisation/measureAttribute.js b/src/packages/modules-structures/components/component-detail/visualisation/measureAttribute.js index c24bcbd2f..4befca48f 100644 --- a/src/packages/modules-structures/components/component-detail/visualisation/measureAttribute.js +++ b/src/packages/modules-structures/components/component-detail/visualisation/measureAttribute.js @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; -import api from '../../../apis/structure-api'; import { MeasureAttributeValue } from './measureAttributeValue'; +import { StructureApi } from '../../../../sdk'; export const MeasureAttribute = ({ attribute, @@ -12,9 +12,9 @@ export const MeasureAttribute = ({ const [fullAttribute, setFullAttribute] = useState(); useEffect(() => { - api - .getMutualizedComponent(attributeId) - .then((body) => setFullAttribute(body)); + StructureApi.getMutualizedComponent(attributeId).then((body) => + setFullAttribute(body) + ); }, [attributeId]); if (!fullAttribute) { diff --git a/src/packages/modules-structures/components/component-search/search.js b/src/packages/modules-structures/components/component-search/search.js index d17a82edc..2fa248645 100644 --- a/src/packages/modules-structures/components/component-search/search.js +++ b/src/packages/modules-structures/components/component-search/search.js @@ -9,10 +9,9 @@ import { } from '../../../components'; import { Link, Redirect } from 'react-router-dom'; -import api from '../../apis/structure-api'; import D from '../../i18n/build-dictionary'; import { formatLabel } from '../../utils'; -import { ConceptsApi } from '../../../sdk'; +import { ConceptsApi, StructureApi } from '../../../sdk'; import { validateStateOptions } from '../../../model/ValidationState'; import { useStampsOptions } from '../../../utils/hooks/stamps'; import useUrlQueryParameters from '../../../utils/hooks/useUrlQueryParameters'; @@ -135,7 +134,7 @@ const SearchListContainer = () => { const stampListOptions = useStampsOptions(); useEffect(() => { Promise.all([ - api.getMutualizedComponentsForSearch(), + StructureApi.getMutualizedComponentsForSearch(), ConceptsApi.getConceptList(), ]) .then(([components, concepts]) => { diff --git a/src/packages/modules-structures/components/component-selector/index.js b/src/packages/modules-structures/components/component-selector/index.js index 10d7c18b6..22cd7275a 100644 --- a/src/packages/modules-structures/components/component-selector/index.js +++ b/src/packages/modules-structures/components/component-selector/index.js @@ -11,7 +11,7 @@ import { } from '../../utils/constants/dsd-components'; import { CodesListPanel } from '../codes-list-panel/codes-list-panel'; import { OBSERVATION } from '../../utils/constants'; -import Api from '../../apis/structure-api'; +import { StructureApi } from '../../../sdk'; const filterComponentDefinition = (type) => (componentDefinition) => componentDefinition?.component?.type === type; @@ -200,19 +200,21 @@ const ComponentSelector = ({ (id) => { const component = mutualizedComponents.find((c) => c.identifiant === id); if (component.type === MEASURE_PROPERTY_TYPE) { - Api.getMutualizedComponent(component.id).then((fullComponent) => { - const componentsToAdd = [component]; - Object.keys(fullComponent) - .filter((key) => key.indexOf('attribute_') === 0) - .forEach((iri) => { - const attribute = mutualizedComponents.find( - (c) => c.iri === fullComponent[iri] - ); - - componentsToAdd.push(attribute); - }); - addComponent(structureComponents, componentsToAdd); - }); + StructureApi.getMutualizedComponent(component.id).then( + (fullComponent) => { + const componentsToAdd = [component]; + Object.keys(fullComponent) + .filter((key) => key.indexOf('attribute_') === 0) + .forEach((iri) => { + const attribute = mutualizedComponents.find( + (c) => c.iri === fullComponent[iri] + ); + + componentsToAdd.push(attribute); + }); + addComponent(structureComponents, componentsToAdd); + } + ); } else { addComponent(structureComponents, component); } diff --git a/src/packages/modules-structures/components/component-specification-form/index.js b/src/packages/modules-structures/components/component-specification-form/index.js index d7da140fc..93406ab12 100644 --- a/src/packages/modules-structures/components/component-specification-form/index.js +++ b/src/packages/modules-structures/components/component-specification-form/index.js @@ -6,8 +6,8 @@ import { ATTRIBUTE_PROPERTY_TYPE, MEASURE_PROPERTY_TYPE, } from '../../utils/constants'; -import Api from '../../apis/structure-api'; import { TextInput, Column, Row, Select } from '../../../components'; +import { StructureApi } from '../../../sdk'; export const ComponentSpecificationForm = ({ structureComponents, @@ -21,7 +21,9 @@ export const ComponentSpecificationForm = ({ Promise.all( structureComponents .filter((c) => c.component.type === MEASURE_PROPERTY_TYPE) - .map((measure) => Api.getMutualizedComponent(measure.component.id)) + .map((measure) => + StructureApi.getMutualizedComponent(measure.component.id) + ) ).then((measures) => { setAttachments(getAllAttachment(measures, selectedComponent)); }); diff --git a/src/packages/modules-structures/components/components-list/index.js b/src/packages/modules-structures/components/components-list/index.js index a9a056fef..06145c144 100644 --- a/src/packages/modules-structures/components/components-list/index.js +++ b/src/packages/modules-structures/components/components-list/index.js @@ -4,12 +4,12 @@ import './component-list.scss'; import { useHistory } from 'react-router-dom'; import { formatLabel } from '../../utils'; -import api from '../../apis/structure-api'; import D from '../../i18n/build-dictionary'; import { HomePageMenu } from './menu'; import FilterToggleButtons from '../../../components/filter-toggle-buttons'; import { useTitle } from '../../../utils/hooks/useTitle'; import { MUTUALIZED_COMPONENT_TYPES } from '../../utils/constants'; +import { StructureApi } from '../../../sdk'; const ALL = 'ALL'; const sessionStorageKey = 'components-displayMode'; @@ -43,8 +43,7 @@ function ComponentsList() { .map(({ id, labelLg1, labelLg2 }) => ({ id, labelLg1, labelLg2 })); useEffect(() => { - api - .getMutualizedComponents() + StructureApi.getMutualizedComponents() .then((components) => { setItems(components); }) diff --git a/src/packages/modules-structures/components/mutualized-component-selector/index.js b/src/packages/modules-structures/components/mutualized-component-selector/index.js index 3226256b2..bd4a09579 100644 --- a/src/packages/modules-structures/components/mutualized-component-selector/index.js +++ b/src/packages/modules-structures/components/mutualized-component-selector/index.js @@ -8,6 +8,7 @@ import { ComponentDetail } from '../component-detail'; import Representation from '../representation'; import { UNPUBLISHED } from '../../..//model/ValidationState'; +import { SeeButton } from '../../../components'; export const MutualizedComponentsSelector = ({ hidden = false, @@ -67,13 +68,10 @@ export const MutualizedComponentsSelector = ({ ), actions: ( <> - + > + > +