diff --git a/client/package.json b/client/package.json index d086e56..66539bc 100644 --- a/client/package.json +++ b/client/package.json @@ -17,7 +17,7 @@ "classnames": "^2.3.2", "highcharts": "^11.4.8", "highcharts-react-official": "^3.2.1", - "intro.js-react": "^1.0.0", + "intro.js": "^7.2.0", "papaparse": "^5.4.1", "primereact": "^9.6.0", "prop-types": "^15.8.1", diff --git a/client/src/pages/openalex-affiliations/results/index.jsx b/client/src/pages/openalex-affiliations/results/index.jsx index 6997468..9f6a244 100644 --- a/client/src/pages/openalex-affiliations/results/index.jsx +++ b/client/src/pages/openalex-affiliations/results/index.jsx @@ -14,7 +14,7 @@ import { TextInput, } from '@dataesr/dsfr-plus'; import { useQuery } from '@tanstack/react-query'; -import { Steps } from 'intro.js-react'; +import introJs from 'intro.js'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; @@ -32,6 +32,7 @@ import ListView from './list-view'; import 'intro.js/introjs.css'; import 'primereact/resources/primereact.min.css'; import 'primereact/resources/themes/lara-light-indigo/theme.css'; +import '../../../styles/index.scss'; const { VITE_APP_TAG_LIMIT } = import.meta.env; @@ -55,32 +56,138 @@ export default function Affiliations() { const [rorMessage, setRorMessage] = useState(''); const [rorMessageType, setRorMessageType] = useState(''); const [rorsToRemove, setRorsToRemove] = useState([]); - const [stepsEnabled, setStepsEnabled] = useState(false); const [stepsEnabledList, setStepsEnabledList] = useState(false); const [uniqueRors, setUniqueRors] = useState({}); - const steps = [ - { - element: '.step-search-summary', - intro: 'Here is your search', - }, - { - element: '.step-search-go-back', - intro: 'Click here to modify your search', - }, - { - element: '.step-action-add-remove-ror', - intro: 'Click here to add or remove ROR from selected affiliations', - }, - { - element: '.step-action-export', - intro: 'Click here to export corrections, choose between CSV and JSONL format', - }, - { - element: '.step-action-feedback', - intro: 'Click here to send feedback to OpenAlex', - }, - ]; + const toggleRemovedRor = (affiliationId, rorId) => { + const updatedAffiliations = affiliations.map((affiliation) => { + if (affiliation.id === affiliationId) { + return { + ...affiliation, + removeList: affiliation.removeList.includes(rorId) + ? affiliation.removeList.filter((item) => item !== rorId) + : [...affiliation.removeList, rorId], + }; + } + return affiliation; + }); + setAffiliations(updatedAffiliations); + }; + + const removeRorMultiple = () => { + const selectedRorIds = rorsToRemove.filter((_ror) => _ror.removed).map((_ror) => _ror.rorId); + const affiliationsTmp = affiliations.map((affiliation) => { + if (affiliation.selected) { + return { + ...affiliation, + addList: affiliation.addList.filter((item) => !selectedRorIds.includes(item.rorId)), + removeList: [...new Set([...affiliation.removeList, ...selectedRorIds])].filter((item) => affiliation.rors.map((_ror) => _ror.rorId).includes(item)), + selected: false, + }; + } + return affiliation; + }); + setAffiliations(affiliationsTmp); + setRorsToRemove([]); + setIsRemoveModalOpen(false); + }; + + const removeRorFromAddList = (affiliationId, rorId) => { + const updatedAffiliations = affiliations.map((affiliation) => { + if (affiliation.id === affiliationId) { + if (affiliation.addList.find((item) => item.rorId === rorId)) { + return { ...affiliation, addList: affiliation.addList.filter((item) => item.rorId !== rorId) }; + } + } + return affiliation; + }); + setAffiliations(updatedAffiliations); + }; + + const addRor = () => { + const updatedAffiliations = affiliations.map((affiliation) => { + if (affiliation.selected + && !affiliation.addList.some((item) => item.rorId === cleanRor.rorId) + && !affiliation.rors.some((item) => item.rorId === cleanRor.rorId) + ) { + return { + ...affiliation, + addList: [...affiliation.addList, cleanRor], + selected: false, + }; + } + return { + ...affiliation, + selected: false, + }; + }); + setAffiliations(updatedAffiliations); + setRor(''); + setCleanRor({}); + setIsAddModalOpen(false); + }; + + const getCleanRor = async () => { + setIsLoadingRor(true); + const cleanRorData = await getRorData(ror); + setCleanRor(cleanRorData[0]); + setIsLoadingRor(false); + }; + + const setSelectAffiliations = (affiliationIds) => { + const updatedAffiliations = affiliations.map((affiliation) => { + if (affiliationIds.includes(affiliation.id)) { + return { ...affiliation, selected: !affiliation.selected }; + } + return affiliation; + }); + setAffiliations(updatedAffiliations); + }; + + const getUniqueRors = () => { + const selectedRors = {}; + const selectedAffiliations = filteredAffiliations.filter((affiliation) => affiliation.selected); + + // initial RORs + selectedAffiliations.forEach((affiliation) => { + affiliation.rors.forEach((_ror) => { + if (affiliation.removeList.includes(_ror.rorId)) return; + if (!selectedRors[_ror.rorId]) { + selectedRors[_ror.rorId] = { ..._ror, count: 0 }; + } + selectedRors[_ror.rorId].count += 1; + }); + }); + + // added RORs + selectedAffiliations.forEach((affiliation) => { + affiliation.addList.forEach((_ror) => { + if (!selectedRors[_ror.rorId]) { + selectedRors[_ror.rorId] = { ..._ror, count: 0 }; + } + selectedRors[_ror.rorId].count += 1; + }); + }); + + const rorArray = Object.keys(selectedRors).map((rorId) => ({ + rorId, + rorName: selectedRors[rorId].rorName, + rorCountry: selectedRors[rorId].rorCountry, + count: selectedRors[rorId].count, + removed: false, + })); + return rorArray; + }; + + const resetCorrections = () => { + const affiliationsTmp = affiliations.map((affiliation) => ({ + ...affiliation, + addList: [], + removeList: [], + rorToCorrect: affiliation.rors, + })); + setAffiliations(affiliationsTmp); + }; const { data, error, isFetched, isFetching, refetch } = useQuery({ queryKey: ['openalex-affiliations', JSON.stringify(options)], @@ -100,11 +207,6 @@ export default function Affiliations() { enabled: false, }); - useEffect(() => { - // Enable guided tour only for the first visit - if (localStorage.getItem('works-magnet-tour-results') !== 'done') setStepsEnabled(true); - }, [setStepsEnabled]); - useEffect(() => { const get = async () => { const addedRors = await Promise.all( @@ -185,9 +287,7 @@ export default function Affiliations() { }, [options, refetch]); useEffect(() => { - setAffiliations(data?.affiliations?.filter( - (affiliation) => affiliation.source === 'OpenAlex', - ).map((affiliation) => ({ + setAffiliations(data?.affiliations?.map((affiliation) => ({ ...affiliation, addList: [], removeList: [], @@ -197,13 +297,17 @@ export default function Affiliations() { useEffect(() => { setIsLoading(true); - const regex = new RegExp(removeDiacritics(filteredAffiliationName)); - const filteredAffiliationsTmp = affiliations.filter( - (affiliation) => regex.test( - `${affiliation.key.replace('[ source: ', '').replace(' ]', '')} ${affiliation.rors.map((_ror) => _ror.rorId).join(' ')}`, - ), - ); - setFilteredAffiliations(filteredAffiliationsTmp); + if (filteredAffiliationName !== '') { + const regex = new RegExp(removeDiacritics(filteredAffiliationName)); + const filteredAffiliationsTmp = affiliations.filter( + (affiliation) => regex.test( + `${affiliation.key.replace('[ source: ', '').replace(' ]', '')} ${affiliation.rors.map((_ror) => _ror.rorId).join(' ')}`, + ), + ); + setFilteredAffiliations(filteredAffiliationsTmp); + } else { + setFilteredAffiliations(affiliations); + } setIsLoading(false); }, [affiliations, filteredAffiliationName]); @@ -224,142 +328,79 @@ export default function Affiliations() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ror]); - const toggleRemovedRor = (affiliationId, rorId) => { - const updatedAffiliations = affiliations.map((affiliation) => { - if (affiliation.id === affiliationId) { - return { - ...affiliation, - removeList: affiliation.removeList.includes(rorId) - ? affiliation.removeList.filter((item) => item !== rorId) - : [...affiliation.removeList, rorId], - }; - } - return affiliation; - }); - setAffiliations(updatedAffiliations); - }; - - const removeRorMultiple = () => { - const selectedRorIds = rorsToRemove.filter((_ror) => _ror.removed).map((_ror) => _ror.rorId); - const affiliationsTmp = affiliations.map((affiliation) => { - if (affiliation.selected) { - return { - ...affiliation, - addList: affiliation.addList.filter((item) => !selectedRorIds.includes(item.rorId)), - removeList: [...new Set([...affiliation.removeList, ...selectedRorIds])].filter((item) => affiliation.rors.map((_ror) => _ror.rorId).includes(item)), - selected: false, - }; - } - return affiliation; - }); - setAffiliations(affiliationsTmp); - setRorsToRemove([]); - setIsRemoveModalOpen(false); - }; - - const removeRorFromAddList = (affiliationId, rorId) => { - const updatedAffiliations = affiliations.map((affiliation) => { - if (affiliation.id === affiliationId) { - if (affiliation.addList.find((item) => item.rorId === rorId)) { - return { ...affiliation, addList: affiliation.addList.filter((item) => item.rorId !== rorId) }; - } - } - return affiliation; - }); - - setAffiliations(updatedAffiliations); - }; - useEffect(() => { if (rorMessageType !== 'valid') { setCleanRor({}); } }, [rorMessageType]); - const addRor = () => { - const updatedAffiliations = affiliations.map((affiliation) => { - if (affiliation.selected - && !affiliation.addList.some((item) => item.rorId === cleanRor.rorId) - && !affiliation.rors.some((item) => item.rorId === cleanRor.rorId) - ) { - return { - ...affiliation, - addList: [...affiliation.addList, cleanRor], - selected: false, - }; - } - return { - ...affiliation, - selected: false, - }; - }); - setAffiliations(updatedAffiliations); - setRor(''); - setCleanRor({}); - setIsAddModalOpen(false); - }; - - const getCleanRor = async () => { - setIsLoadingRor(true); - const cleanRorData = await getRorData(ror); - setCleanRor(cleanRorData[0]); - setIsLoadingRor(false); - }; - - const setSelectAffiliations = (affiliationIds) => { - const updatedAffiliations = affiliations.map((affiliation) => { - if (affiliationIds.includes(affiliation.id)) { - return { ...affiliation, selected: !affiliation.selected }; - } - return affiliation; - }); - setAffiliations(updatedAffiliations); - }; - - const getUniqueRors = () => { - const selectedRors = {}; - const selectedAffiliations = filteredAffiliations.filter((affiliation) => affiliation.selected); - - // initial RORs - selectedAffiliations.forEach((affiliation) => { - affiliation.rors.forEach((_ror) => { - if (affiliation.removeList.includes(_ror.rorId)) return; - if (!selectedRors[_ror.rorId]) { - selectedRors[_ror.rorId] = { ..._ror, count: 0 }; - } - selectedRors[_ror.rorId].count += 1; - }); - }); - - // added RORs - selectedAffiliations.forEach((affiliation) => { - affiliation.addList.forEach((_ror) => { - if (!selectedRors[_ror.rorId]) { - selectedRors[_ror.rorId] = { ..._ror, count: 0 }; - } - selectedRors[_ror.rorId].count += 1; - }); - }); - - const rorArray = Object.keys(selectedRors).map((rorId) => ({ - rorId, - rorName: selectedRors[rorId].rorName, - rorCountry: selectedRors[rorId].rorCountry, - count: selectedRors[rorId].count, - removed: false, - })); - return rorArray; - }; - - const resetCorrections = () => { - const affiliationsTmp = affiliations.map((affiliation) => ({ - ...affiliation, - addList: [], - removeList: [], - rorToCorrect: affiliation.rors, - })); - setAffiliations(affiliationsTmp); - }; + useEffect(() => { + if (filteredAffiliations.length > 0) { + introJs().setOptions({ + dontShowAgain: true, + dontShowAgainCookie: 'introjs-dontShowAgain-results', + showStepNumbers: true, + steps: [ + { + element: document.querySelector('.step-search-summary'), + intro: 'Here is your search', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-search-go-back'), + intro: 'Click here to modify your search', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-action-add-remove-ror'), + intro: 'Click here to add or remove ROR from selected affiliations', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-action-export'), + intro: 'Click here to export corrections, choose between CSV and JSONL format', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-action-feedback'), + intro: 'Click here to send feedback to OpenAlex', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliations-select'), + intro: 'Select all affiliations', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliations-search'), + intro: 'Search through affiliations names', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliations-sort'), + intro: 'Open menu to filter affiliations by country and sort them', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliations-colors'), + intro: 'Explanation about the colors of ROR', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliation-checkbox'), + intro: 'Select affiliation one by one', + title: 'Tutorial', + }, + { + element: document.querySelector('.step-affiliation-badge'), + intro: '