diff --git a/client/src/pages/affiliationsTab.jsx b/client/src/pages/affiliationsTab.jsx new file mode 100644 index 00000000..ab4b02b0 --- /dev/null +++ b/client/src/pages/affiliationsTab.jsx @@ -0,0 +1,128 @@ +import { + Checkbox, + CheckboxGroup, + Col, + Notice, + Row, + Tab, + TextInput, +} from '@dataesr/react-dsfr'; +import PropTypes from 'prop-types'; +import { useEffect, useState } from 'react'; + +import AffiliationsView from './affiliationsView'; +import Gauge from '../components/gauge'; +import { status } from '../config'; +import { renderButtons } from '../utils/works'; + +export default function AffiliationsTab({ affiliations, tagAffiliations }) { + const [affiliationsNotice, setAffiliationsNotice] = useState(true); + const [filteredAffiliations, setFilteredAffiliations] = useState([]); + const [filteredAffiliationName, setFilteredAffiliationName] = useState(''); + const [filteredStatus, setFilteredStatus] = useState(Object.keys(status)); + const [selectedAffiliations, setSelectedAffiliations] = useState([]); + const [timer, setTimer] = useState(); + + useEffect(() => { + setFilteredAffiliations(affiliations); + }, [affiliations]); + + useEffect(() => { + if (timer) { + clearTimeout(timer); + } + const timerTmp = setTimeout(() => { + const filteredAffiliationsTmp = affiliations.filter((affiliation) => affiliation.name.includes(filteredAffiliationName) && filteredStatus.includes(affiliation.status)); + setFilteredAffiliations(filteredAffiliationsTmp); + }, 500); + setTimer(timerTmp); + // The timer should not be tracked + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [affiliations, filteredAffiliationName, filteredStatus]); + + const onStatusChange = (st) => { + if (filteredStatus.includes(st)) { + setFilteredStatus(filteredStatus.filter((filteredSt) => filteredSt !== st)); + } else { + setFilteredStatus(filteredStatus.concat([st])); + } + }; + + return ( + + {affiliationsNotice && ( + + + { setAffiliationsNotice(false); }} + title="All the affiliations of the works found in the French OSM and OpenAlex are listed below. + A filter is applied to view only the affiliations containing at least one of the matching query input" + /> + + + )} + + + {renderButtons(selectedAffiliations, tagAffiliations)} + + + ({ + ...st, + value: affiliations.filter((affiliation) => affiliation.status === st.id).length, + }))} + /> + + + + + + {Object.values(status).map((st) => ( + onStatusChange(st.id)} + size="sm" + /> + ))} + + setFilteredAffiliationName(e.target.value)} + value={filteredAffiliationName} + /> + + + !!affiliation.matches)} + selectedAffiliations={selectedAffiliations} + setSelectedAffiliations={setSelectedAffiliations} + /> + + + + + {renderButtons(selectedAffiliations, tagAffiliations)} + + + + ); +} + +AffiliationsTab.propTypes = { + affiliations: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + matches: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + nameHtml: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + works: PropTypes.arrayOf(PropTypes.string).isRequired, + worksNumber: PropTypes.number.isRequired, + })).isRequired, + tagAffiliations: PropTypes.func.isRequired, +}; diff --git a/client/src/pages/affiliationsView.jsx b/client/src/pages/affiliationsView.jsx index e63293da..10d4f942 100644 --- a/client/src/pages/affiliationsView.jsx +++ b/client/src/pages/affiliationsView.jsx @@ -1,6 +1,6 @@ -import PropTypes from 'prop-types'; import { Column } from 'primereact/column'; import { DataTable } from 'primereact/datatable'; +import PropTypes from 'prop-types'; import { nameTemplate, statusTemplate } from '../utils/templates'; diff --git a/client/src/pages/index.jsx b/client/src/pages/index.jsx index 78470d9d..9f05b8bf 100644 --- a/client/src/pages/index.jsx +++ b/client/src/pages/index.jsx @@ -3,12 +3,10 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ /* eslint-disable no-case-declarations */ import { - Button, Checkbox, CheckboxGroup, Col, Container, - Notice, Row, Tab, Tabs, @@ -19,7 +17,7 @@ import { useEffect, useState } from 'react'; import Actions from './actions'; import Filters from './filters'; -import AffiliationsView from './affiliationsView'; +import AffiliationsTab from './affiliationsTab'; import WorksView from './worksView'; import Gauge from '../components/gauge'; import { PageSpinner } from '../components/spinner'; @@ -33,7 +31,8 @@ import { } from '../utils/templates'; import { getData, -} from '../utils/works'; + renderButtons, +} from '../utils/works.jsx'; import { status } from '../config'; import 'primereact/resources/primereact.min.css'; @@ -42,27 +41,21 @@ import 'primereact/resources/themes/lara-light-indigo/theme.css'; const DATASOURCES = [{ key: 'bso', label: 'French OSM' }, { key: 'openalex', label: 'OpenAlex' }]; export default function Home() { - const [affiliationsNotice, setAffiliationsNotice] = useState(true); const [allAffiliations, setAllAffiliations] = useState([]); const [allDatasets, setAllDatasets] = useState([]); const [allPublications, setAllPublications] = useState([]); - const [filteredAffiliations, setFilteredAffiliations] = useState([]); const [filteredAffiliationName, setFilteredAffiliationName] = useState(''); - const [filteredAffiliationName2, setFilteredAffiliationName2] = useState(''); const [filteredDatasources, setFilteredDatasources] = useState(DATASOURCES.map((datasource) => datasource.key)); const [filteredPublications, setFilteredPublications] = useState([]); const [filteredStatus, setFilteredStatus] = useState(Object.keys(status)); - const [filteredStatus2, setFilteredStatus2] = useState(Object.keys(status)); const [filteredTypes, setFilteredTypes] = useState([]); const [filteredYears, setFilteredYears] = useState([]); const [isLoading, setIsLoading] = useState(false); const [options, setOptions] = useState({}); const [regexp, setRegexp] = useState(); - const [selectedAffiliations, setSelectedAffiliations] = useState([]); const [selectedDatasets, setSelectedDatasets] = useState([]); const [selectedPublications, setSelectedPublications] = useState([]); const [timer, setTimer] = useState(); - const [timer2, setTimer2] = useState(); const [types, setTypes] = useState([]); const [years, setYears] = useState([]); @@ -89,7 +82,6 @@ export default function Home() { .replace(/[^a-zA-Z0-9]/g, ''); const groupByAffiliations = () => { - setIsLoading(true); // Save already decided affiliations const decidedAffiliations = Object.values(allAffiliations).filter((affiliation) => affiliation.status !== status.tobedecided.id); // Compute distinct affiliations of the undecided works @@ -133,7 +125,6 @@ export default function Home() { allAffiliationsTmp = Object.values(allAffiliationsTmp) .map((affiliation, index) => ({ ...affiliation, id: index.toString(), works: [...new Set(affiliation.works)], worksNumber: [...new Set(affiliation.works)].length })); setAllAffiliations(allAffiliationsTmp); - setFilteredAffiliations(allAffiliationsTmp); setIsLoading(false); }; @@ -204,19 +195,6 @@ export default function Home() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [allPublications, filteredAffiliationName, filteredDatasources, filteredStatus, filteredTypes, filteredYears]); - useEffect(() => { - if (timer2) { - clearTimeout(timer2); - } - const timerTmp2 = setTimeout(() => { - const filteredAffiliationsTmp = allAffiliations.filter((affiliation) => affiliation.name.includes(filteredAffiliationName2) && filteredStatus2.includes(affiliation.status)); - setFilteredAffiliations(filteredAffiliationsTmp); - }, 500); - setTimer2(timerTmp2); - // The timer should not be tracked - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [allAffiliations, filteredAffiliationName2, filteredStatus2]); - useEffect(() => { groupByAffiliations(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -252,26 +230,8 @@ export default function Home() { const affiliationIds = affiliations.map((affiliation) => affiliation.id); allAffiliationsTmp.filter((affiliation) => affiliationIds.includes(affiliation.id)).map((affiliation) => affiliation.status = action); setAllAffiliations(allAffiliationsTmp); - setSelectedAffiliations([]); }; - const renderButtons = (selected, fn) => ( - <> - {Object.values(status).map((st) => ( - - ))} - - ); - const onDatasourcesChange = (datasource) => { if (filteredDatasources.includes(datasource.key)) { setFilteredDatasources(filteredDatasources.filter((filteredDatasource) => filteredDatasource !== datasource.key)); @@ -288,14 +248,6 @@ export default function Home() { } }; - const onStatusChange2 = (st) => { - if (filteredStatus2.includes(st)) { - setFilteredStatus2(filteredStatus2.filter((filteredSt2) => filteredSt2 !== st)); - } else { - setFilteredStatus2(filteredStatus2.concat([st])); - } - }; - const onTypesChange = (type) => { if (filteredTypes.includes(type)) { setFilteredTypes(filteredTypes.filter((filteredType) => filteredType !== type)); @@ -332,73 +284,13 @@ export default function Home() { setAllPublications={setAllPublications} tagAffiliations={tagAffiliations} /> - {isLoading && } {allAffiliations.length > 0 && ( - - {affiliationsNotice && ( - - - { setAffiliationsNotice(false); }} - title="All the affiliations of the works found in the French OSM and OpenAlex are listed below. A filter is applied to view only the affiliations containing at least one of the matching query input" - /> - - - )} - - - {renderButtons(selectedAffiliations, tagAffiliations)} - - - ({ - ...st, - value: allAffiliations.filter((affiliation) => affiliation.status === st.id).length, - }))} - /> - - - {(isFetching || isLoading) && ()} - {!isFetching && !isLoading && ( - - - - {Object.values(status).map((st) => ( - onStatusChange2(st.id)} - size="sm" - /> - ))} - - setFilteredAffiliationName2(e.target.value)} - value={filteredAffiliationName2} - /> - - - !!affiliation.matches)} - selectedAffiliations={selectedAffiliations} - setSelectedAffiliations={setSelectedAffiliations} - /> - - - )} - - - {renderButtons(selectedAffiliations, tagAffiliations)} - - - + diff --git a/client/src/pages/views/affiliationsTab.jsx b/client/src/pages/views/affiliationsTab.jsx deleted file mode 100644 index fd2a7c19..00000000 --- a/client/src/pages/views/affiliationsTab.jsx +++ /dev/null @@ -1 +0,0 @@ -export default function AffiliationsTab({ \ No newline at end of file diff --git a/client/src/utils/templates.jsx b/client/src/utils/templates.jsx index 95695955..0c5ade23 100644 --- a/client/src/utils/templates.jsx +++ b/client/src/utils/templates.jsx @@ -3,7 +3,7 @@ import { Badge } from '@dataesr/react-dsfr'; import { Tooltip } from 'react-tooltip'; -import { getIdLink } from './works'; +import { getIdLink } from './works.jsx'; import { status } from '../config'; const affiliationsTemplate = (rowData) => ( diff --git a/client/src/utils/works.js b/client/src/utils/works.jsx similarity index 64% rename from client/src/utils/works.js rename to client/src/utils/works.jsx index 5031a1c5..ed636504 100644 --- a/client/src/utils/works.js +++ b/client/src/utils/works.jsx @@ -1,3 +1,7 @@ +import { Button } from '@dataesr/react-dsfr'; + +import { status } from '../config'; + const { VITE_API, } = import.meta.env; @@ -36,7 +40,25 @@ const getIdLink = (type, id) => { return (prefix !== null) ? `${prefix}${id}` : false; }; +const renderButtons = (selected, fn) => ( + <> + {Object.values(status).map((st) => ( + + ))} + +); + export { getData, getIdLink, + renderButtons, };