diff --git a/README.md b/README.md index 9f55f6473..398a891c0 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,16 @@ Web application for the management of concepts, classifications and other statis [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=InseeFr_Bauhaus&metric=coverage)](https://sonarcloud.io/dashboard?id=InseeFr_Bauhaus) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -The documentation can be found in the [docs](https://github.com/InseeFr/Bauhaus/tree/master/docs) folder and [browsed online](https://inseefr.github.io/Bauhaus). +The documentation can be found in the [docs](https://github.com/InseeFr/Bauhaus/tree/main/docs) folder and [browsed online](https://inseefr.github.io/Bauhaus). [Storybook](https://inseefr.github.io/Bauhaus/storybook) is also available online. + +## How to start + +``` +git clone git@github.com:InseeFr/Bauhaus.git +cd Bauhaus +yarn +yarn build-insee +yarn start +``` diff --git a/app/package.json b/app/package.json index 470d81cbf..9d0fe17f2 100755 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "Bauhaus", - "version": "3.0.8", + "version": "3.0.9", "description": "Web application for the management of concepts, classifications and other statistical objects", "repository": { "type": "git", @@ -27,8 +27,8 @@ "dependencies": { "@inseefr/iam": "^0.0.1-rc11", "@inseefr/wilco": "0.0.5", - "bauhaus-structures": "^0.0.0", "bauhaus-operations": "^0.0.0", + "bauhaus-structures": "^0.0.0", "bootstrap": "3.4.1", "create-react-class": "^15.6.3", "dompurify": "2.2.9", diff --git a/app/src/js/applications/codelists/routes/index.js b/app/src/js/applications/codelists/routes/index.js index 01a37e403..6a99cf6e9 100644 --- a/app/src/js/applications/codelists/routes/index.js +++ b/app/src/js/applications/codelists/routes/index.js @@ -17,22 +17,14 @@ const CodesListComponent = () => {
- + - +
diff --git a/app/src/js/applications/concepts/home-container.js b/app/src/js/applications/concepts/home-container.js deleted file mode 100755 index 8f5710d2f..000000000 --- a/app/src/js/applications/concepts/home-container.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; -import { Loading } from '@inseefr/wilco'; -import ConceptsHome from './home'; -import { NOT_LOADED } from 'js/constants'; -import loadConceptList from 'js/actions/concepts/list'; -import { Auth } from 'bauhaus-utilities'; - -const ConceptsHomeContainer = ({ concepts, permission, loadConceptList }) => { - useEffect(() => { - if (!concepts) { - loadConceptList(); - } - }, [concepts, loadConceptList]); - if (!concepts) return ; - return ; -}; - -const mapStateToProps = state => { - const permission = Auth.getPermission(state); - if (!state.conceptList) { - return { - status: NOT_LOADED, - concepts: [], - }; - } - let { results: concepts, status, err } = state.conceptList; - return { - concepts, - status, - err, - permission, - }; -}; - -const mapDispatchToProps = { - loadConceptList, -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ConceptsHomeContainer); diff --git a/app/src/js/applications/concepts/home.js b/app/src/js/applications/concepts/home.js index d34282fa2..4b6d60b8f 100755 --- a/app/src/js/applications/concepts/home.js +++ b/app/src/js/applications/concepts/home.js @@ -1,5 +1,4 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect } from 'react'; import { PageTitle, SearchableList, @@ -7,16 +6,27 @@ import { PublishButton, ExportButton, VerticalMenu, + Loading } from '@inseefr/wilco'; import check from 'js/utils/auth'; -import { propTypes as conceptOverviewPropTypes } from 'js/utils/concepts/concept-overview'; -import { propTypes as permissionOverviewPropTypes } from 'js/utils/auth/permission-overview'; import D from 'js/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { Auth } from 'bauhaus-utilities'; +import loadConceptList from 'js/actions/concepts/list'; +import * as select from 'js/reducers'; -const ConceptsHome = ({ - concepts, - permission: { authType, roles } -}) => { +const ConceptsHome = () => { + const permission = useSelector(state => Auth.getPermission(state)) + const concepts = useSelector(state => select.getConceptList(state)); + const dispatch = useDispatch(); + useEffect(() => { + if (!concepts) { + dispatch(loadConceptList()) + } + }, [concepts, dispatch]); + if (!concepts) return ; + + const { authType, roles } = permission; const authImpl = check(authType); const adminOrContributor = authImpl.isAdminOrContributor(roles); const adminOrCreator = authImpl.isAdminOrConceptCreator(roles); @@ -51,9 +61,5 @@ const ConceptsHome = ({ ); } -ConceptsHome.propTypes = { - concepts: PropTypes.arrayOf(conceptOverviewPropTypes.isRequired), - permission: permissionOverviewPropTypes.isRequired, -}; export default ConceptsHome; diff --git a/app/src/js/applications/concepts/home.spec.js b/app/src/js/applications/concepts/home.spec.js deleted file mode 100644 index 195604677..000000000 --- a/app/src/js/applications/concepts/home.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import Concepts from './home'; -import { MemoryRouter } from 'react-router-dom'; - -describe('concept', () => { - it('renders without crashing', () => { - render( - , - { - wrapper: MemoryRouter, - } - ); - }); -}); diff --git a/app/src/js/applications/concepts/routes/index.js b/app/src/js/applications/concepts/routes/index.js index b9dd12c56..d12199942 100644 --- a/app/src/js/applications/concepts/routes/index.js +++ b/app/src/js/applications/concepts/routes/index.js @@ -1,7 +1,7 @@ import React from 'react'; import { Switch, Route } from 'react-router-dom'; -import ConceptsContainer from 'js/applications/concepts/home-container'; +import Concepts from 'js/applications/concepts/home'; import ConceptsSearchListContainer from 'js/applications/concepts/advanced-search/home-container'; import ConceptCompareContainer from 'js/applications/concepts/compare/home-container'; import ConceptSendContainer from 'js/applications/concepts/send/home-container'; @@ -31,7 +31,7 @@ export default () => { <> - + 0 ? `(${extraInformations.join(' - ')})` : ''}`; + return <>{content[label]} {extraInformations.length > 0 ? `(${extraInformations.join('-')})` : ''}; } const hitEls = hits.map((item) => (
  • @@ -52,7 +52,7 @@ const SearchableList = ({ )); return ( -
    + <>
    -
    + ); }; diff --git a/app/src/js/i18n/dictionary/operations/index.js b/app/src/js/i18n/dictionary/operations/index.js index fe1570642..ce1b5a7a7 100644 --- a/app/src/js/i18n/dictionary/operations/index.js +++ b/app/src/js/i18n/dictionary/operations/index.js @@ -9,10 +9,6 @@ const dictionary = { fr: 'Familles', en: 'Families', }, - creatorTitle: { - fr: 'Propriétaires', - en: 'Owners', - }, seriesTitle: { fr: 'Séries', en: 'Series', @@ -233,20 +229,20 @@ const dictionary = { en: 'You are about to permanently delete this documentation. Are you sure?', }, exportSimsTips: { - fr: 'Veuillez sélectionner les options d\'export', - en: 'Please select the export options' + fr: "Veuillez sélectionner les options d'export", + en: 'Please select the export options', }, exportSimsIncludeLg1: { fr: 'Inclure la première langue', - en: 'Include first language' + en: 'Include first language', }, exportSimsIncludeLg2: { fr: 'Inclure la seconde langue', - en: 'Include second language' + en: 'Include second language', }, exportSimsIncludeEmptyMas: { fr: 'Inclure les rubriques vides', - en: 'Include the empty rubrics' + en: 'Include the empty rubrics', }, ...documentsD, ...validationD, diff --git a/docs/book.json b/docs/book.json index 8b07516b3..4eee12aa0 100644 --- a/docs/book.json +++ b/docs/book.json @@ -1,33 +1,33 @@ { - "gitbook": "3.2.2", - "title": "Bauhaus", - "plugins": [ - "edit-link", - "prism", - "-highlight", - "github", - "anchorjs", - "image-captions", - "include-codeblock" - ], - "root": "./", - "pluginsConfig": { - "edit-link": { - "base": "https://github.com/InseeFr/Bauhaus/tree/master/docs", - "label": "Edit This Page" - }, - "github": { - "url": "https://github.com/InseeFr/Bauhaus/" - }, - "theme-default": { - "styles": { - "website": "gitbook.css" - } - }, - "prism": { - "lang": { - "shell": "bash" - } - } - } + "gitbook": "3.2.2", + "title": "Bauhaus", + "plugins": [ + "edit-link", + "prism", + "-highlight", + "github", + "anchorjs", + "image-captions", + "include-codeblock" + ], + "root": "./", + "pluginsConfig": { + "edit-link": { + "base": "https://github.com/InseeFr/Bauhaus/tree/main/docs", + "label": "Edit This Page" + }, + "github": { + "url": "https://github.com/InseeFr/Bauhaus/" + }, + "theme-default": { + "styles": { + "website": "gitbook.css" + } + }, + "prism": { + "lang": { + "shell": "bash" + } + } + } } diff --git a/docs/en/getting-started.md b/docs/en/getting-started.md index 0376ac7ba..355c85720 100644 --- a/docs/en/getting-started.md +++ b/docs/en/getting-started.md @@ -45,9 +45,9 @@ docker run -it -p 8083:80 -e BAUHAUS_API_URL=http://192.168.1.12:8081/api bauha If you're new to JavaScript, you might need to first install [node](https://nodejs.org/en/download/) and [yarn](https://github.com/yarnpkg/yarn) on your computer. -`yarn` is the `Node.js` package manager. `yarn install` will download all the dependencies needed by the project, as described in the `dependencies` and `devDepedencies` sections of the [package.json](https://github.com/InseeFr/Bauhaus/blob/master/package.json) file. +`yarn` is the `Node.js` package manager. `yarn install` will download all the dependencies needed by the project, as described in the `dependencies` and `devDepedencies` sections of the [package.json](https://github.com/InseeFr/Bauhaus/blob/main/package.json) file. -`yarn start` will launch the `dev` command defined in the `scripts` section of the same `package.json` file. This command will launch a local web server serving the main HTML file ([src/js/index.html](https://github.com/InseeFr/Bauhaus/blob/master/public/index.html)) and all the relevant assets. +`yarn start` will launch the `dev` command defined in the `scripts` section of the same `package.json` file. This command will launch a local web server serving the main HTML file ([src/js/index.html](https://github.com/InseeFr/Bauhaus/blob/main/public/index.html)) and all the relevant assets. `yarn build` will launch the compilation with some optimizations for production. It copies all the static assets and the resulting bundle file in the `dist` folder. diff --git a/docs/fr/getting-started.md b/docs/fr/getting-started.md index 1c2d6e6ff..7dfabc0a9 100644 --- a/docs/fr/getting-started.md +++ b/docs/fr/getting-started.md @@ -46,9 +46,9 @@ docker run -it -p 8083:80 -e BAUHAUS_API_URL=http://192.168.1.12:8081/api bauha Si vous débutez avec ces technologies, vous aurez vraisemblablement besoin d'installer dans un premier temps sur votre ordinateur [node](https://nodejs.org/en/download/) et [yarn](https://github.com/yarnpkg/yarn). -`yarn` est un gestionnaire de modules pour `Node.js`. `yarn install` téléchargera toutes les dépendances du projet, décrites dans la section `dependencies` et `devDepedencies` du fichier [package.json](https://github.com/InseeFr/Bauhaus/blob/master/package.json). +`yarn` est un gestionnaire de modules pour `Node.js`. `yarn install` téléchargera toutes les dépendances du projet, décrites dans la section `dependencies` et `devDepedencies` du fichier [package.json](https://github.com/InseeFr/Bauhaus/blob/main/package.json). -`yarn start` démarre un serveur de développement qui sert la page d'accueil de l'application ([src/js/index.html](https://github.com/InseeFr/Bauhaus/blob/master/public/index.html)) et toutes les ressources nécessaires. +`yarn start` démarre un serveur de développement qui sert la page d'accueil de l'application ([src/js/index.html](https://github.com/InseeFr/Bauhaus/blob/main/public/index.html)) et toutes les ressources nécessaires. `yarn build` lance la compilation du code avec des optimisations pour la mise en production. Elle copie toutes les ressources statiques et le fichier `JavaScript` compilé dans le dossier `dist`. diff --git a/package.json b/package.json index 797d4e5f4..bb2c1749a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "babel-jest": "26.0.1", "babel-loader": "8.1.0", "babel-polyfill": "^6.26.0", - "eslint": "7.28.0", + "eslint": "7.32.0", "eslint-config-prettier": "6.15.0", "eslint-config-react-app": "6.0.0", "eslint-plugin-flowtype": "5.2.0", @@ -50,15 +50,15 @@ "jest-localstorage-mock": "2.4.3", "lcov-result-merger": "3.1.0", "prettier": "2.3.2", - "webpack": "5.49.0" + "webpack": "5.58.1" }, "dependencies": { "dompurify": "2.2.9", "@babel/preset-react": "^7.12.13", "jest-environment-jsdom-sixteen": "^1.0.3", "prop-types": "15.7.2", - "react": "17.0.1", - "react-dom": "17.0.1", + "react": "17.0.2", + "react-dom": "17.0.2", "react-redux": "7.2.0", "react-router-dom": "5.2.0", "redux": "4.0.5", diff --git a/packages/codelists/src/components/code-detail/edit.js b/packages/codelists/src/components/code-detail/edit.js index 143af4e5a..c5602c83c 100644 --- a/packages/codelists/src/components/code-detail/edit.js +++ b/packages/codelists/src/components/code-detail/edit.js @@ -1,43 +1,39 @@ import React, { useState, useCallback, useEffect } from 'react'; import { - CancelButton, - SaveButton, - ActionToolbar, ErrorBloc, LabelRequired, Select, + ActionToolbar, } from '@inseefr/wilco'; import PropTypes from 'prop-types'; import { Stores } from 'bauhaus-utilities'; import { validateCode } from '../../utils'; import D, { D1, D2 } from '../../i18n/build-dictionary'; +import { emptyCode } from './empty-code'; import './edit.scss'; /** * TODO: - * - Afficher le TreeView à gauche - * - Ajouter le formulaire vide à droite - * - Ajouter les boutons (qui ne font rien pour le moment) - * - Gérer le click sur un code -> initaliser le formulaire avec les bonnes données - * - Gérer l'ajout d'un nouveau code - * - Gérer la sauvegarde d'un code existant - * - Gérer la transformation TreeView -> Structure de données pour l'API - * - Gérer la suppression d'un code et remontée des enfants - * - gérer la suppression d'un code et suppression des enfants + * Validation - Eviter d'avoir deux codes avec le meme code * - Gérer le DragnDrop + * + * - CSS pour libellés trop longs */ const DumbCodeDetailEdit = ({ code: initialCode, codes, - handleSave, - handleBack, serverSideError, + deleteCode, + deleteCodeWithChildren, + updateCode, + createCode, }) => { const [code, setCode] = useState({}); + const [updateMode, setUpdateMode] = useState(true); useEffect(() => { setCode({ ...initialCode }); + setUpdateMode(initialCode.code); }, [initialCode]); - const [parents, setParents] = useState(code.parents); const handleChange = useCallback( (e) => { @@ -50,6 +46,14 @@ const DumbCodeDetailEdit = ({ [code] ); + const isDescendant = (ancestor, descendant) => { + if (ancestor === descendant) return true; + if (descendant === '') return false; + return codes + .find((c) => c.code === descendant) + .parents?.some((parent) => isDescendant(ancestor, parent)); + }; + const codesOptions = codes .map((code) => { return { @@ -59,48 +63,46 @@ const DumbCodeDetailEdit = ({ }) .concat({ label: '', value: '' }); - const handleSaveClick = useCallback(() => { - handleSave(code); - }, [code, handleSave]); - - const { field, message } = validateCode(code); + const { field, message } = validateCode(code, codes, updateMode); return ( - - - - {message && } {serverSideError && } -
    +
    - {D.parentCodeTitle} - + code.parents?.find((p) => p === option.value) + )} + options={codesOptions.filter( + (c) => !code.code || !isDescendant(code.code, c.value) + )} + onChange={(parents) => { + setCode({ + ...code, + parents: parents?.map(({ value }) => value) || [], + }); + }} + multi + /> +
    - {D.idTitle} + {D.idTitle}
    @@ -126,6 +128,7 @@ const DumbCodeDetailEdit = ({ name="labelLg2" value={code.labelLg2} onChange={handleChange} + aria-invalid={field === 'labelLg2'} />
    @@ -135,7 +138,7 @@ const DumbCodeDetailEdit = ({ {D2.descriptionTitle} - + + + + + + + ); }; DumbCodeDetailEdit.propTypes = { code: PropTypes.object, - handleSave: PropTypes.func, - handleBack: PropTypes.func, secondLang: PropTypes.bool, }; -export const CodeDetailEdit = Stores.DisseminationStatus.withDisseminationStatusListOptions( - DumbCodeDetailEdit -); +export const CodeDetailEdit = + Stores.DisseminationStatus.withDisseminationStatusListOptions( + DumbCodeDetailEdit + ); diff --git a/packages/codelists/src/components/code-detail/empty-code.js b/packages/codelists/src/components/code-detail/empty-code.js new file mode 100644 index 000000000..75c7c1733 --- /dev/null +++ b/packages/codelists/src/components/code-detail/empty-code.js @@ -0,0 +1,8 @@ +export const emptyCode = { + code: '', + parents: [], + labelLg1: '', + labelLg2: '', + descriptionLg1: '', + descriptionLg2: '', +}; diff --git a/packages/codelists/src/components/code-detail/index.js b/packages/codelists/src/components/code-detail/index.js deleted file mode 100644 index 4932288ba..000000000 --- a/packages/codelists/src/components/code-detail/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { CodeDetailEdit } from './edit'; -import { CodeDetailView } from './view'; -import CodeTitle from './title'; -import { useSelector } from 'react-redux'; -import { Stores } from 'bauhaus-utilities'; - -export const CodeDetail = (props) => { - const secondLang = useSelector(Stores.SecondLang.getSecondLang); - - const [mode, setMode] = useState( - !props.readOnly && !props.code?.labelLg1 ? 'EDIT' : 'VIEW' - ); - - const handleViewUpdate = useCallback(() => setMode('EDIT'), []); - const handleEditUpdate = useCallback( - (code) => { - props.handleSave(code); - setMode('VIEW'); - }, - [props] - ); - const handleEditBack = useCallback( - () => (!props.code.labelLg1 ? props.handleBack() : setMode('VIEW')), - [props] - ); - return ( -
    - {mode === 'VIEW' && ( - - - - - - )} - {mode === 'EDIT' && ( - - )} -
    - ); -}; diff --git a/packages/codelists/src/components/code-detail/view.js b/packages/codelists/src/components/code-detail/view.js index cb9c08cb5..ff8b8509a 100644 --- a/packages/codelists/src/components/code-detail/view.js +++ b/packages/codelists/src/components/code-detail/view.js @@ -1,34 +1,21 @@ import React from 'react'; -import { - Note, - UpdateButton, - ActionToolbar, - ReturnButton, - ErrorBloc, -} from '@inseefr/wilco'; +import { Note, ActionToolbar, ReturnButton, ErrorBloc } from '@inseefr/wilco'; import D, { D1, D2 } from '../../i18n/build-dictionary'; -import { HTMLUtils, ValidationButton } from 'bauhaus-utilities'; +import { HTMLUtils } from 'bauhaus-utilities'; import PropTypes from 'prop-types'; import './view.scss'; export const CodeDetailView = ({ code, codes, - handleUpdate, handleBack, - updatable, secondLang, col = 3, - publishComponent, serverSideError, }) => { const descriptionLg1 = HTMLUtils.renderMarkdownElement(code.descriptionLg1); const descriptionLg2 = HTMLUtils.renderMarkdownElement(code.descriptionLg2); - const publish = () => { - publishComponent(); - }; - const codesOptions = codes.map((code) => { return { label: code.code + ' - ' + code.labelLg1, @@ -40,8 +27,6 @@ export const CodeDetailView = ({ - - {updatable && } @@ -96,9 +81,6 @@ export const CodeDetailView = ({ CodeDetailView.propTypes = { code: PropTypes.object, codes: PropTypes.array, - handleUpdate: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), handleBack: PropTypes.func, - updatable: PropTypes.bool, secondLang: PropTypes.bool, - publishComponent: PropTypes.func, }; diff --git a/packages/codelists/src/components/codelist-detail/code-detail.js b/packages/codelists/src/components/codelist-detail/code-detail.js deleted file mode 100644 index 2b5d079d8..000000000 --- a/packages/codelists/src/components/codelist-detail/code-detail.js +++ /dev/null @@ -1,14 +0,0 @@ -import { D1, D2 } from '../../i18n/build-dictionary'; - -export const rowParams = [ - { - dataField: 'code', - text: D1.codeTitle, - width: '8%', - isKey: true, - }, - { dataField: 'labelLg1', text: D1.codeLabel, width: '16%' }, - { dataField: 'labelLg2', text: D2.codeLabel, width: '16%' }, - { dataField: 'descriptionLg1', text: D1.codeDescription, width: '30%' }, - { dataField: 'descriptionLg2', text: D2.codeDescription, width: '30%' }, -]; diff --git a/packages/codelists/src/components/codelist-detail/codes-tree-edit.js b/packages/codelists/src/components/codelist-detail/codes-tree-edit.js new file mode 100644 index 000000000..f88b6a4d1 --- /dev/null +++ b/packages/codelists/src/components/codelist-detail/codes-tree-edit.js @@ -0,0 +1,84 @@ +import React, { useCallback, useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { Stores } from 'bauhaus-utilities'; +import RmesTree from '../tree'; +import { CodeDetailEdit } from '../code-detail/edit'; +import { treedData } from '../../utils'; +import { emptyCode } from '../code-detail/empty-code'; + +export const syncNodes = (previousNodes = [], nextNodes = []) => { + if(previousNodes.length !== nextNodes.length){ + return nextNodes + } + return nextNodes.map((node) => { + const previousNode = previousNodes.find(({ code }) => code === node.code); + + return { + ...node, + expanded: previousNode?.expanded || false, + children: syncNodes(previousNode?.children, node.children), + }; + }); +}; + +const CodesTreeEdit = ({ + codes, + deleteCode, + deleteCodeWithChildren, + updateCode, + createCode, +}) => { + const secondLang = useSelector(Stores.SecondLang.getSecondLang); + const [selectedCode, setSelectedCode] = useState(emptyCode); + const [tree, setTree] = useState([]); + + useEffect(() => { + const currentTree = treedData(Object.values(codes || {})); + setTree(syncNodes(tree, currentTree)); + // needs not to depend on tree to allow react-sortable-tree to update "expanded" + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [codes]); + + const seeClickHandler = useCallback( + (e) => { + const chosenCode = codes.find( + (c) => c.code === e.target.parentElement.dataset.componentId + ); + if (chosenCode) { + setSelectedCode(chosenCode); + } + }, + [codes] + ); + + return ( +
    +
    + +
    +
    + +
    +
    + ); +}; + +CodesTreeEdit.propTypes = { + codes: PropTypes.array, +}; + +export default CodesTreeEdit; diff --git a/packages/codelists/src/components/codelist-detail/codes-tree-edit.spec.js b/packages/codelists/src/components/codelist-detail/codes-tree-edit.spec.js new file mode 100644 index 000000000..e04d039b0 --- /dev/null +++ b/packages/codelists/src/components/codelist-detail/codes-tree-edit.spec.js @@ -0,0 +1,392 @@ +import { syncNodes } from './codes-tree-edit'; + +describe('syncNodes', () => { + it('should set expanded to true to the first child', () => { + const previous = [ + { + code: 1, + expanded: true, + children: [ + { + label: 'label11', + code: 2, + }, + ], + }, + ]; + + const next = [ + { + code: 1, + children: [ + { + code: 2, + label: 'label1', + }, + ], + }, + ]; + + expect(syncNodes(previous, next)).toEqual([ + { + code: 1, + expanded: true, + children: [ + { + children: [], + label: 'label1', + expanded: false, + code: 2, + }, + ], + }, + ]); + }); + + it('real example', () => { + const nextNodes = [ + { + code: 'S.11', + title: 'S.11 - Sociétés non financières', + label: 'Sociétés non financières', + parent: null, + children: [ + { + code: 'S.11003', + title: 'S.11003 - Sociétés non financières sous contrôle étranger', + label: 'Sociétés non financières sous contrôle étranger', + parent: 'S.11', + children: [ + { + code: 'IABL.11003', + title: + 'IABL.11003 - IABL non financières sous contrôle étranger', + label: 'IABL non financières sous contrôle étranger', + parent: 'S.11003', + }, + { + code: 'ISLB.11003', + title: + 'ISLB.11003 - ISLB non financières sous contrôle étranger', + label: 'ISLB non financières sous contrôle étranger', + parent: 'S.11003', + }, + ], + }, + { + code: 'ISLB.11', + title: 'ISLB.11 - Institutions sans but lucratif non financières', + label: 'Institutions sans but lucratif non financières', + parent: 'S.11', + children: [ + { + code: 'ISLB.11002', + title: 'ISLB.11002 - ISLB non financières privées nationales', + label: 'ISLB non financières privées nationales', + parent: 'ISLB.11', + }, + { + code: 'ISLB.11003', + title: + 'ISLB.11003 - ISLB non financières sous contrôle étranger', + label: 'ISLB non financières sous contrôle étranger', + parent: 'ISLB.11', + }, + { + code: 'ISLB.11001', + title: 'ISLB.11001 - ISLB non financières publiques', + label: 'ISLB non financières publiques', + parent: 'ISLB.11', + }, + ], + }, + { + code: 'S.11002', + title: 'S.11002 - Sociétés non financières privées nationales', + label: 'Sociétés non financières privées nationales', + parent: 'S.11', + children: [ + { + code: 'IABL.11002', + title: 'IABL.11002 - IABL non financières privées nationales', + label: 'IABL non financières privées nationales', + parent: 'S.11002', + }, + { + code: 'ISLB.11002', + title: 'ISLB.11002 - ISLB non financières privées nationales', + label: 'ISLB non financières privées nationales', + parent: 'S.11002', + }, + ], + }, + { + code: 'IABL.11', + title: 'IABL.11 - Institutions à but lucratif non financières', + label: 'Institutions à but lucratif non financières', + parent: 'S.11', + children: [ + { + code: 'IABL.11001', + title: 'IABL.11001 - IABL non financières publiques', + label: 'IABL non financières publiques', + parent: 'IABL.11', + }, + { + code: 'IABL.11003', + title: + 'IABL.11003 - IABL non financières sous contrôle étranger', + label: 'IABL non financières sous contrôle étranger', + parent: 'IABL.11', + }, + { + code: 'IABL.11002', + title: 'IABL.11002 - IABL non financières privées nationales', + label: 'IABL non financières privées nationales', + parent: 'IABL.11', + }, + ], + }, + { + code: 'S.11001', + title: 'S.11001 - Sociétés non financières publiques', + label: 'Sociétés non financières publiques', + parent: 'S.11', + children: [ + { + code: 'IABL.11001', + title: 'IABL.11001 - IABL non financières publiques', + label: 'IABL non financières publiques', + parent: 'S.11001', + }, + { + code: 'ISLB.11001', + title: 'ISLB.11001 - ISLB non financières publiques', + label: 'ISLB non financières publiques', + parent: 'S.11001', + }, + ], + }, + ], + }, + ]; + const previousNodes = [ + { + code: 'S.11', + title: 'S.11 - Sociétés non financières', + label: 'Sociétés non financières', + parent: null, + children: [ + { + code: 'S.11003', + title: 'S.11003 - Sociétés non financières sous contrôle étranger', + label: 'Sociétés non financières sous contrôle étranger', + parent: 'S.11', + children: [ + { + code: 'IABL.11003', + title: + 'IABL.11003 - IABL non financières sous contrôle étranger', + label: 'IABL non financières sous contrôle étranger', + parent: 'S.11003', + expanded: false, + children: [], + }, + { + code: 'ISLB.11003', + title: + 'ISLB.11003 - ISLB non financières sous contrôle étranger', + label: 'ISLB non financières sous contrôle étranger', + parent: 'S.11003', + expanded: false, + children: [], + }, + ], + expanded: true, + }, + { + code: 'ISLB.11', + title: 'ISLB.11 - Institutions sans but lucratif non financières', + label: 'Institutions sans but lucratif non financières', + parent: 'S.11', + children: [ + { + code: 'ISLB.11002', + title: 'ISLB.11002 - ISLB non financières privées nationales', + label: 'ISLB non financières privées nationales', + parent: 'ISLB.11', + expanded: false, + children: [], + }, + { + code: 'ISLB.11003', + title: + 'ISLB.11003 - ISLB non financières sous contrôle étranger', + label: 'ISLB non financières sous contrôle étranger', + parent: 'ISLB.11', + expanded: false, + children: [], + }, + { + code: 'ISLB.11001', + title: 'ISLB.11001 - ISLB non financières publiques', + label: 'ISLB non financières publiques', + parent: 'ISLB.11', + expanded: false, + children: [], + }, + ], + expanded: false, + }, + { + code: 'S.11002', + title: 'S.11002 - Sociétés non financières privées nationales', + label: 'Sociétés non financières privées nationales', + parent: 'S.11', + children: [ + { + code: 'IABL.11002', + title: 'IABL.11002 - IABL non financières privées nationales', + label: 'IABL non financières privées nationales', + parent: 'S.11002', + expanded: false, + children: [], + }, + { + code: 'ISLB.11002', + title: 'ISLB.11002 - ISLB non financières privées nationales', + label: 'ISLB non financières privées nationales', + parent: 'S.11002', + expanded: false, + children: [], + }, + ], + expanded: false, + }, + { + code: 'IABL.11', + title: 'IABL.11 - Institutions à but lucratif non financières', + label: 'Institutions à but lucratif non financières', + parent: 'S.11', + children: [ + { + code: 'IABL.11001', + title: 'IABL.11001 - IABL non financières publiques', + label: 'IABL non financières publiques', + parent: 'IABL.11', + expanded: false, + children: [], + }, + { + code: 'IABL.11003', + title: + 'IABL.11003 - IABL non financières sous contrôle étranger', + label: 'IABL non financières sous contrôle étranger', + parent: 'IABL.11', + expanded: false, + children: [], + }, + { + code: 'IABL.11002', + title: 'IABL.11002 - IABL non financières privées nationales', + label: 'IABL non financières privées nationales', + parent: 'IABL.11', + expanded: false, + children: [], + }, + ], + expanded: false, + }, + { + code: 'S.11001', + title: 'S.11001 - Sociétés non financières publiques', + label: 'Sociétés non financières publiques', + parent: 'S.11', + children: [ + { + code: 'IABL.11001', + title: 'IABL.11001 - IABL non financières publiques', + label: 'IABL non financières publiques', + parent: 'S.11001', + expanded: false, + children: [], + }, + { + code: 'ISLB.11001', + title: 'ISLB.11001 - ISLB non financières publiques', + label: 'ISLB non financières publiques', + parent: 'S.11001', + expanded: false, + children: [], + }, + ], + expanded: false, + }, + ], + expanded: true, + }, + ]; + expect(syncNodes(previousNodes, nextNodes)).toEqual(previousNodes); + }); + it('should set expanded to true to the second child child', () => { + const previous = [ + { + code: 1, + expanded: true, + children: [ + { + label: 'label11', + expanded: true, + code: 2, + children: [ + { + label: 'label3', + code: 3, + }, + ], + }, + ], + }, + ]; + + const next = [ + { + code: 1, + children: [ + { + code: 2, + label: 'label1', + children: [ + { + label: 'label3', + code: 3, + }, + ], + }, + ], + }, + ]; + + expect(syncNodes(previous, next)).toEqual([ + { + code: 1, + expanded: true, + children: [ + { + children: [ + { + children: [], + label: 'label3', + code: 3, + expanded: false, + }, + ], + label: 'label1', + expanded: true, + code: 2, + }, + ], + }, + ]); + }); +}); diff --git a/packages/codelists/src/components/codes-tree/index.js b/packages/codelists/src/components/codelist-detail/codes-tree-view.js similarity index 61% rename from packages/codelists/src/components/codes-tree/index.js rename to packages/codelists/src/components/codelist-detail/codes-tree-view.js index b35061989..68c0b384f 100644 --- a/packages/codelists/src/components/codes-tree/index.js +++ b/packages/codelists/src/components/codelist-detail/codes-tree-view.js @@ -1,13 +1,23 @@ import React, { useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import SlidingPanel from 'react-sliding-side-panel'; +import { useSelector } from 'react-redux'; +import { Stores } from 'bauhaus-utilities'; import D from '../../i18n/build-dictionary'; import { CollapsiblePanel } from '../collapsible-panel'; -import { CodeDetail } from '../code-detail'; import RmesTree from '../tree'; +import { CodeDetailView } from '../code-detail/view'; +import CodeTitle from '../code-detail/title'; -const CodesTree = ({ hidden = false, codes, tree, handleAdd, readOnly }) => { +const CodesTreeView = ({ + hidden = false, + codes, + tree, + handleChangeTree, + readOnly, +}) => { const [openPanel, setOpenPanel] = useState(false); + const secondLang = useSelector(Stores.SecondLang.getSecondLang); const [selectedCode, setSelectedCode] = useState(null); const seeClickHandler = useCallback( @@ -15,19 +25,14 @@ const CodesTree = ({ hidden = false, codes, tree, handleAdd, readOnly }) => { const chosenCode = codes.find( (c) => c.code === e.target.parentElement.dataset.componentId ); - setSelectedCode(chosenCode); - setOpenPanel(true); + if (chosenCode) { + setSelectedCode(chosenCode); + setOpenPanel(true); + } }, [codes] ); - /* const addClickHandler = useCallback( - (e) => { - handleAdd(e.id); - }, - [handleAdd] - ); */ - return (
  • - - {formatLabel(component)} - + const dataLinks = data.map((codelist) => ( +
  • + {formatLabel(codelist)}
  • )); return ( @@ -91,7 +89,7 @@ class SearchFormList extends AbstractAdvancedSearchComponent { redirect={} >
    - {D.componentTitle} + {D.codelistTitle}