diff --git a/queen-v2/src/components/lightOrchestrator/LoopPanel/component.js b/queen-v2/src/components/lightOrchestrator/LoopPanel/component.js index 50a5a559..fb91674a 100644 --- a/queen-v2/src/components/lightOrchestrator/LoopPanel/component.js +++ b/queen-v2/src/components/lightOrchestrator/LoopPanel/component.js @@ -1,14 +1,12 @@ -import { memo, useEffect, useState } from 'react'; +import { memo } from 'react'; import { useStyles } from './component.style'; import { Panel } from './panel'; -const LoopPanelNotMemo = ({ loopVariables = [], getData, pager, goToPage }) => { +const LoopPanelNotMemo = ({ loopVariables = [], allData, pager, goToPage }) => { const noLoopVariables = loopVariables.length === 0 || loopVariables[0] === undefined; const classes = useStyles(); - const [datas, setDatas] = useState(null); - const { page: currentPage, subPage: currentSubPage, @@ -16,17 +14,12 @@ const LoopPanelNotMemo = ({ loopVariables = [], getData, pager, goToPage }) => { lastReachedPage, } = pager; - useEffect(() => { - if (!noLoopVariables) setDatas(getData()); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [noLoopVariables, loopVariables]); - if (noLoopVariables) return null; // use page to select loopVariables depth const depth = 0; const targetVariable = loopVariables[depth]; - const targetData = datas?.COLLECTED[targetVariable]; + const targetData = allData?.COLLECTED[targetVariable]; const COLLECTED = targetData?.COLLECTED; if (COLLECTED && (COLLECTED.length === 0 || COLLECTED[0] === null)) return null; diff --git a/queen-v2/src/components/lightOrchestrator/lightOrchestrator.js b/queen-v2/src/components/lightOrchestrator/lightOrchestrator.js index c8c71b7d..3f6a469f 100644 --- a/queen-v2/src/components/lightOrchestrator/lightOrchestrator.js +++ b/queen-v2/src/components/lightOrchestrator/lightOrchestrator.js @@ -1,11 +1,12 @@ import { useLunatic } from '@inseefr/lunatic'; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useEffect, useMemo, useRef, useState } from 'react'; import ButtonContinue from './buttons/continue/index'; import D from 'i18n'; import { componentHasResponse } from 'utils/components/deduceState'; import { QUEEN_URL } from 'utils/constants'; +import { useConstCallback } from 'utils/hook/useConstCallback'; import { LoopPanel } from './LoopPanel'; import { ComponentDisplayer } from './componentDisplayer'; import Header from './header'; @@ -36,6 +37,7 @@ function LightOrchestrator({ missing = true, shortcut = true, autoSuggesterLoading, + allData, filterDescription, onChange = onLogChange, onDataChange = noDataChange, @@ -47,7 +49,7 @@ function LightOrchestrator({ const classes = useStyles(); const lunaticStateRef = useRef(); - const lightCustomHandleChange = useCallback(valueChange => { + const lightCustomHandleChange = useConstCallback(valueChange => { if (lunaticStateRef === undefined) return; const { getComponents, goNextPage } = lunaticStateRef.current; const currentComponent = getComponents()?.[0]; @@ -60,13 +62,13 @@ function LightOrchestrator({ ) { goNextPage(); } - }, []); + }); - const missingStrategy = useCallback(() => { + const missingStrategy = useConstCallback(() => { if (lunaticStateRef === undefined) return; const { goNextPage } = lunaticStateRef.current; goNextPage(); - }, []); + }); // TODO restore when lunatic handle object in missingButtons properties // const dontKnowButton = ; @@ -89,6 +91,7 @@ function LightOrchestrator({ missingShortcut, dontKnowButton, refusedButton, + trackChanges: true, workersBasePath: `${QUEEN_URL}/workers`, }); @@ -106,7 +109,8 @@ function LightOrchestrator({ // getErrors, // getModalErrors, // getCurrentErrors, - getData, + // getData, + // getChangedData, loopVariables = [], Provider, pageTag, @@ -118,30 +122,30 @@ function LightOrchestrator({ useEffect(() => { const savingTask = async () => { if (lunaticStateRef.current === undefined) return; - const { getData: freshGetData, pageTag, pager } = lunaticStateRef.current; + const { getChangedData: freshGetChangedData, pageTag, pager } = lunaticStateRef.current; if (previousPageTag.current === undefined) { previousPageTag.current = pageTag; return; } if (pageTag !== previousPageTag.current) { previousPageTag.current = pageTag; - const allData = freshGetData(); - onDataChange(allData.COLLECTED); - save(undefined, allData, pager.lastReachedPage); + const partialData = freshGetChangedData(true); + onDataChange(partialData); + save(undefined, partialData, pager.lastReachedPage); } }; savingTask(); }, [save, pager, onDataChange]); - const memoQuit = useCallback(() => { - const { getData: freshGetData, pager: freshPager } = lunaticStateRef.current; - quit(freshPager, freshGetData); - }, [quit]); + const memoQuit = useConstCallback(() => { + const { getChangedData: freshGetChangedData, pager: freshPager } = lunaticStateRef.current; + quit(freshPager, freshGetChangedData); + }); - const memoDefinitiveQuit = useCallback(() => { - const { getData: freshGetData, pager: freshPager } = lunaticStateRef.current; - definitiveQuit(freshPager, freshGetData); - }, [definitiveQuit]); + const memoDefinitiveQuit = useConstCallback(() => { + const { getChangedData: freshGetChangedData, pager: freshPager } = lunaticStateRef.current; + definitiveQuit(freshPager, freshGetChangedData); + }); const [components, setComponents] = useState([]); @@ -156,23 +160,20 @@ function LightOrchestrator({ // const modalErrors = getModalErrors(); // const currentErrors = typeof getCurrentErrors === 'function' ? getCurrentErrors() : []; - const trueGoToPage = useCallback( - targetPage => { - if (typeof targetPage === 'string') { - goToPage({ page: targetPage }); - } else { - const { page, iteration, subPage } = targetPage; - goToPage({ page: page, iteration: iteration, subPage: subPage }); - } - }, - [goToPage] - ); + const trueGoToPage = useConstCallback(targetPage => { + if (typeof targetPage === 'string') { + goToPage({ page: targetPage }); + } else { + const { page, iteration, subPage } = targetPage; + goToPage({ page: page, iteration: iteration, subPage: subPage }); + } + }); - const goToLastReachedPage = useCallback(() => { + const goToLastReachedPage = useConstCallback(() => { if (lunaticStateRef.current === undefined) return; const { pager } = lunaticStateRef.current; trueGoToPage(pager.lastReachedPage); - }, [trueGoToPage]); + }); const firstComponent = useMemo(() => [...components]?.[0], [components]); const hasResponse = componentHasResponse(firstComponent); @@ -228,7 +229,7 @@ function LightOrchestrator({ diff --git a/queen-v2/src/components/orchestratorManager/orchestratorManager.js b/queen-v2/src/components/orchestratorManager/orchestratorManager.js index 541218c5..5fbaf99a 100644 --- a/queen-v2/src/components/orchestratorManager/orchestratorManager.js +++ b/queen-v2/src/components/orchestratorManager/orchestratorManager.js @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { ORCHESTRATOR_COLLECT, ORCHESTRATOR_READONLY, READ_ONLY } from 'utils/constants'; import { EventsManager, INIT_ORCHESTRATOR_EVENT, INIT_SESSION_EVENT } from 'utils/events'; @@ -11,12 +11,14 @@ import Error from 'components/shared/Error'; import NotFound from 'components/shared/not-found'; import Preloader from 'components/shared/preloader'; import { sendCloseEvent } from 'utils/communication'; +import { useConstCallback } from 'utils/hook/useConstCallback'; import surveyUnitIdbService from 'utils/indexedbb/services/surveyUnit-idb-service'; -import { checkQuestionnaire } from 'utils/questionnaire'; +import { checkQuestionnaire, getFullData, removeNullCollectedData } from 'utils/questionnaire'; export const OrchestratorManager = () => { const { standalone, apiUrl } = useContext(AppContext); const { readonly: readonlyParam, idQ, idSU } = useParams(); + const [surveyUnitData, setSurveyUnitData] = useState(null); const history = useHistory(); const readonly = readonlyParam === READ_ONLY; @@ -34,7 +36,6 @@ export const OrchestratorManager = () => { const { surveyUnit, questionnaire, loadingMessage, errorMessage } = useAPIRemoteData(idSU, idQ); const stateData = surveyUnit?.stateData; - const initialData = surveyUnit?.data; const { oidcUser } = useAuth(); const isAuthenticated = !!oidcUser?.profile; @@ -46,7 +47,6 @@ export const OrchestratorManager = () => { const { putUeData /* postParadata */ } = useAPI(); const [getState, changeState, onDataChange] = useQuestionnaireState( surveyUnit?.id, - initialData, stateData?.state ); @@ -66,6 +66,7 @@ export const OrchestratorManager = () => { const { valid, error: questionnaireError } = checkQuestionnaire(questionnaire); if (valid) { setSource(questionnaire); + setSurveyUnitData(surveyUnit.data); setInit(true); LOGGER.log(INIT_ORCHESTRATOR_EVENT); } else { @@ -81,84 +82,76 @@ export const OrchestratorManager = () => { /** take a survey-unit as parameter, then save it in IDB, then save paradatas in IDB * If in standalone mode : make API calls to persist data in DB */ - const saveData = useCallback( - async unit => { - if (!readonly) { - const putSurveyUnit = async unit => { - const { id, ...other } = unit; - await putUeData(id, other); - }; - - await surveyUnitIdbService.addOrUpdateSU(unit); - - /** - * Disable temporaly paradata - * - * const paradatas = LOGGER.getEventsToSend(); - */ - // TODO : make a true update of paradatas : currently adding additional completed arrays => SHOULD save one and only one array - // await paradataIdbService.update(paradatas); - if (standalone) { - // TODO managing errors - await putSurveyUnit(unit); - // await postParadata(paradatas); - } + const saveData = useConstCallback(async unit => { + if (!readonly) { + const putSurveyUnit = async unit => { + const { id, ...other } = unit; + await putUeData(id, other); + }; + + await surveyUnitIdbService.addOrUpdateSU(unit); + + /** + * Disable temporaly paradata + * + * const paradatas = LOGGER.getEventsToSend(); + */ + // TODO : make a true update of paradatas : currently adding additional completed arrays => SHOULD save one and only one array + // await paradataIdbService.update(paradatas); + if (standalone) { + // TODO managing errors + await putSurveyUnit(unit); + // await postParadata(paradatas); } - }, - [putUeData, readonly, standalone] - ); - - const saveQueen = useCallback( - async (newState, newData, lastReachedPage) => { - const currentState = getState(); - saveData({ - comment: {}, - ...surveyUnit, - stateData: { - state: newState ?? currentState, - date: new Date().getTime(), - currentPage: lastReachedPage, - }, - data: newData ?? surveyUnit.data, - }); - }, - [getState, saveData, surveyUnit] - ); - - const closeOrchestrator = useCallback(() => { + } + }); + + const savePartialQueen = useConstCallback(async (newState, newPartialData, lastReachedPage) => { + const currentState = getState(); + + const newData = getFullData(surveyUnitData, removeNullCollectedData(newPartialData)); + setSurveyUnitData(newData); + saveData({ + comment: {}, + ...surveyUnit, + stateData: { + state: newState ?? currentState, + date: new Date().getTime(), + currentPage: lastReachedPage, + }, + data: newData ?? surveyUnitData, + }); + }); + + const closeOrchestrator = useConstCallback(() => { if (standalone) { history.push('/'); } else { sendCloseEvent(surveyUnit.id); } - }, [history, standalone, surveyUnit?.id]); - - const quit = useCallback( - async (pager, getData) => { - const { page, maxPage, lastReachedPage } = pager; - const isLastPage = page === maxPage; - const newData = getData(); - if (isLastPage) { - // TODO : make algo to calculate COMPLETED event - changeState(COMPLETED); - changeState(VALIDATED); - await saveQueen(VALIDATED, newData, lastReachedPage); - } else await saveQueen(undefined, newData, lastReachedPage); - closeOrchestrator(); - }, - [changeState, closeOrchestrator, saveQueen] - ); - - const definitiveQuit = useCallback( - async (pager, getData) => { - const { lastReachedPage } = pager; - const newData = getData(); + }); + + const quit = useConstCallback(async (pager, getChangedData) => { + const { page, maxPage, lastReachedPage } = pager; + const isLastPage = page === maxPage; + const newData = getChangedData(true); + if (isLastPage) { + // TODO : make algo to calculate COMPLETED event + changeState(COMPLETED); changeState(VALIDATED); - await saveQueen(VALIDATED, newData, lastReachedPage); - closeOrchestrator(); - }, - [changeState, closeOrchestrator, saveQueen] - ); + await savePartialQueen(VALIDATED, newData, lastReachedPage); + } else await savePartialQueen(undefined, newData, lastReachedPage); + closeOrchestrator(); + }); + + const definitiveQuit = useConstCallback(async (pager, getChangedData) => { + const { lastReachedPage } = pager; + const newData = getChangedData(true); + changeState(VALIDATED); + await savePartialQueen(VALIDATED, newData, lastReachedPage); + closeOrchestrator(); + }); + return ( <> {![READ_ONLY, undefined].includes(readonlyParam) && } @@ -169,6 +162,7 @@ export const OrchestratorManager = () => { surveyUnit={surveyUnit} source={source} getReferentiel={getReferentiel} + allData={surveyUnitData} autoSuggesterLoading={true} standalone={standalone} readonly={readonly} @@ -177,7 +171,7 @@ export const OrchestratorManager = () => { missing={true} shortcut={true} filterDescription={false} - save={saveQueen} + save={savePartialQueen} onDataChange={onDataChange} close={closeOrchestrator} quit={quit} diff --git a/queen-v2/src/components/visualizer/visualizer.js b/queen-v2/src/components/visualizer/visualizer.js index cb31f1b9..a440e18d 100644 --- a/queen-v2/src/components/visualizer/visualizer.js +++ b/queen-v2/src/components/visualizer/visualizer.js @@ -1,6 +1,11 @@ -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useGetReferentiel, useRemoteData, useVisuQuery } from 'utils/hook'; -import { checkQuestionnaire, downloadDataAsJson } from 'utils/questionnaire'; +import { + checkQuestionnaire, + downloadDataAsJson, + getFullData, + removeNullCollectedData, +} from 'utils/questionnaire'; import { AppContext } from 'components/app'; import LightOrchestrator from 'components/lightOrchestrator'; @@ -8,11 +13,13 @@ import Error from 'components/shared/Error'; import Preloader from 'components/shared/preloader'; import { useHistory } from 'react-router'; import { useQuestionnaireState } from 'utils/hook/questionnaire'; +import { useConstCallback } from 'utils/hook/useConstCallback'; import surveyUnitIdbService from 'utils/indexedbb/services/surveyUnit-idb-service'; import QuestionnaireForm from './questionnaireForm'; const Visualizer = () => { const { apiUrl, standalone } = useContext(AppContext); + const [surveyUnitData, setSurveyUnitData] = useState(null); const [surveyUnit, setSurveyUnit] = useState(undefined); const [error, setError] = useState(null); @@ -30,7 +37,6 @@ const Visualizer = () => { const [getState, , onDataChange] = useQuestionnaireState( surveyUnit?.id, - suData?.data, suData?.stateData?.state ); @@ -55,6 +61,7 @@ const Visualizer = () => { const { valid, error: questionnaireError } = checkQuestionnaire(questionnaire); if (valid) { setSource(questionnaire); + setSurveyUnitData(suData?.data || {}); } else { setError(questionnaireError); } @@ -65,49 +72,39 @@ const Visualizer = () => { if (errorMessage) setError(errorMessage); }, [errorMessage]); - // const save = useCallback(async (unit, newData) => { - // console.log(unit, newData); - // await surveyUnitIdbService.addOrUpdateSU({ - // ...unit, - // data: newData, - // }); - // }, []); - - const save = useCallback( - async (newState, newData, lastReachedPage) => { - const currentState = getState(); - const unit = { - ...surveyUnit, - stateData: { - state: newState ?? currentState, - date: new Date().getTime(), - currentPage: lastReachedPage, - }, - data: newData ?? surveyUnit?.data, - }; - await surveyUnitIdbService.addOrUpdateSU(unit); - }, - [getState, surveyUnit] - ); - const closeAndDownloadData = useCallback( - async (pager, getData) => { - const { lastReachedPage } = pager; - const newData = getData(); - const unit = { - ...surveyUnit, - stateData: { - state: getState(), - date: new Date().getTime(), - currentPage: lastReachedPage, - }, - data: newData ?? surveyUnit?.data, - }; - - downloadDataAsJson(unit, 'data'); - history.push('/'); - }, - [getState, history, surveyUnit] - ); + const savePartial = useConstCallback(async (newState, newPartialData, lastReachedPage) => { + const currentState = getState(); + + const newData = getFullData(surveyUnitData, removeNullCollectedData(newPartialData)); + setSurveyUnitData(newData); + const unit = { + ...surveyUnit, + stateData: { + state: newState ?? currentState, + date: new Date().getTime(), + currentPage: lastReachedPage, + }, + data: newData ?? surveyUnit.data, + }; + await surveyUnitIdbService.addOrUpdateSU(unit); + }); + + const closeAndDownloadData = useConstCallback(async (pager, getChangedData) => { + const { lastReachedPage } = pager; + const newData = getFullData(surveyUnitData, removeNullCollectedData(getChangedData(true))); + const unit = { + ...surveyUnit, + stateData: { + state: getState(), + date: new Date().getTime(), + currentPage: lastReachedPage, + }, + data: newData ?? surveyUnit?.data, + }; + + downloadDataAsJson(unit, 'data'); + history.push('/'); + }); return ( <> @@ -119,11 +116,12 @@ const Visualizer = () => { source={source} autoSuggesterLoading={true} getReferentiel={getReferentielForVizu} + allData={surveyUnitData} standalone={standalone} readonly={readonly} pagination={true} missing={true} - save={save} + save={savePartial} onDataChange={onDataChange} filterDescription={false} quit={closeAndDownloadData} diff --git a/queen-v2/src/utils/hook/questionnaire.js b/queen-v2/src/utils/hook/questionnaire.js index 980b20b9..266eeb24 100644 --- a/queen-v2/src/utils/hook/questionnaire.js +++ b/queen-v2/src/utils/hook/questionnaire.js @@ -1,52 +1,37 @@ -import { sendCompletedEvent, sendStartedEvent, sendValidatedEvent } from 'utils/communication'; import { useCallback, useRef } from 'react'; +import { sendCompletedEvent, sendStartedEvent, sendValidatedEvent } from 'utils/communication'; +import { useConstCallback } from './useConstCallback'; export const NOT_STARTED = null; export const INIT = 'INIT'; export const COMPLETED = 'COMPLETED'; export const VALIDATED = 'VALIDATED'; -export const useQuestionnaireState = (idSurveyUnit, initialData, initialState = NOT_STARTED) => { - console.log('useQuestionnaireState', { idSurveyUnit, initialData, initialState }); +export const useQuestionnaireState = (idSurveyUnit, initialState = NOT_STARTED) => { const stateRef = useRef(initialState); const getState = useCallback(() => stateRef.current, []); - const initialDataRef = useRef(initialData); - // Send an event when questionnaire's state has changed (started, completed, validated) - const changeState = useCallback( - newState => { - console.log('change state to ', newState); - if (newState === INIT) sendStartedEvent(idSurveyUnit); - if (newState === COMPLETED) sendCompletedEvent(idSurveyUnit); - if (newState === VALIDATED) sendValidatedEvent(idSurveyUnit); - stateRef.current = newState; - }, - [idSurveyUnit] - ); - const onDataChange = useCallback( - newData => { - // initialisation des data de référence - if (initialDataRef.current === undefined) { - console.log('persisting initial data'); - initialDataRef.current = JSON.stringify(newData); - } + const changeState = useConstCallback(newState => { + if (newState === INIT) sendStartedEvent(idSurveyUnit); + if (newState === COMPLETED) sendCompletedEvent(idSurveyUnit); + if (newState === VALIDATED) sendValidatedEvent(idSurveyUnit); + stateRef.current = newState; + }); + const onDataChange = useConstCallback((newData = {}) => { + const { COLLECTED = {} } = newData; + const hasDataChanged = Object.keys(COLLECTED).length > 0; - if (stateRef.current === NOT_STARTED) { - changeState(INIT); - } else if ( - stateRef.current === VALIDATED && - initialDataRef.current !== JSON.stringify(newData) - ) { - // state VALIDATED et données entrantes !== données initiales - changeState(INIT); - } else { - // here we do nothing - console.log({ newData, state: stateRef.current }); - } - }, - [changeState] - ); + if (stateRef.current === NOT_STARTED) { + changeState(INIT); + } else if (stateRef.current === VALIDATED && hasDataChanged) { + // state VALIDATED et données entrantes !== données initiales + changeState(INIT); + } else { + // here we do nothing + console.log({ newData, state: stateRef.current }); + } + }); // Analyse collected variables to update state (only to STARTED state)