diff --git a/dashboard/src/components/DataLoader.js b/dashboard/src/components/DataLoader.js
index 534dafb451..c5aea9fbc6 100644
--- a/dashboard/src/components/DataLoader.js
+++ b/dashboard/src/components/DataLoader.js
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
-import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
+import { atom, useRecoilState, useSetRecoilState } from 'recoil';
import { toast } from 'react-toastify';
import { personsState } from '../recoil/persons';
@@ -37,6 +37,7 @@ const loaderTriggerState = atom({ key: 'loaderTriggerState', default: false });
const isLoadingState = atom({ key: 'isLoadingState', default: false });
const initialLoadState = atom({ key: 'isInitialLoadState', default: false });
const fullScreenState = atom({ key: 'fullScreenState', default: true });
+const checkDataConsistencyAtom = atom({ key: 'checkDataConsistencyAtom', default: 'idle' }); // 'idle' | 'checking' | 'unconsistent'
export const lastLoadState = atom({ key: 'lastLoadState', default: null, effects: [cacheEffect] });
export const initialLoadingTextState = 'En attente de chargement';
export const loadingTextState = atom({ key: 'loadingTextState', default: initialLoadingTextState });
@@ -65,7 +66,8 @@ export default function DataLoader() {
const [isLoading, setIsLoading] = useRecoilState(isLoadingState);
const [fullScreen, setFullScreen] = useRecoilState(fullScreenState);
const [loadingText, setLoadingText] = useRecoilState(loadingTextState);
- const initialLoad = useRecoilValue(initialLoadState);
+ const [checkDataConsistencyState, setDataConsistencyState] = useRecoilState(checkDataConsistencyAtom);
+ const [initialLoad, setInitialLoad] = useRecoilState(initialLoadState);
const [organisation, setOrganisation] = useRecoilState(organisationState);
const [loadList, setLoadList] = useState({ list: [], offset: 0 });
@@ -76,7 +78,7 @@ export default function DataLoader() {
useEffect(() => {
initLoader();
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [progress, total, loaderTrigger, loadList.list.length, isLoading]);
+ }, [lastLoad, progress, total, loaderTrigger, loadList.list.length, isLoading]);
useEffect(() => {
fetchData();
@@ -86,6 +88,10 @@ export default function DataLoader() {
updateProgress();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [progress, progressBuffer, loadList.list.length]);
+ useEffect(() => {
+ if (checkDataConsistencyState === 'checking') checkDataConsistency();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [checkDataConsistencyState]);
const organisationId = organisation?._id;
@@ -116,6 +122,7 @@ export default function DataLoader() {
.then(() => (initialLoad ? migrateData() : Promise.resolve()))
.then(() => getCacheItem(dashboardCurrentCacheKey))
.then((lastLoadValue) => {
+ console.log('lastLoadValue', lastLoadValue);
setLastLoad(lastLoadValue || 0);
API.get({
path: '/organisation/stats',
@@ -127,6 +134,7 @@ export default function DataLoader() {
withAllMedicalData: initialLoad,
},
}).then(({ data: stats }) => {
+ console.log('REFRESH stats', stats);
if (!stats) return;
const newList = [];
let itemsCount =
@@ -404,6 +412,170 @@ export default function DataLoader() {
}
}
+ function checkDataConsistency() {
+ setDataConsistencyState('idle'); // to not trigger effect loop
+ Promise.resolve()
+ .then(async () => {
+ /*
+ Refresh organisation (and user), to get the latest organisation fields
+ and the latest user roles
+ */
+ const userResponse = await API.get({ path: '/user/me' });
+ if (!userResponse.ok) return resetLoaderOnError();
+ setOrganisation(userResponse.user.organisation);
+ setUser(userResponse.user);
+ // Get date from server at the very beginning of the loader.
+ const serverDateResponse = await API.get({ path: '/now' });
+ serverDate.current = serverDateResponse.data;
+ })
+ .then(() => {
+ API.get({
+ path: '/organisation/stats',
+ query: {
+ organisation: organisationId,
+ after: 0,
+ withDeleted: false,
+ // Medical data is never saved in cache so we always have to download all at every page reload.
+ withAllMedicalData: true,
+ },
+ }).then(({ data: stats }) => {
+ console.log('stats', stats);
+ if (!stats) return;
+
+ let cacheIsInvalidated = false;
+
+ setLoadingText('Récupération des données dans le cache');
+ Promise.resolve()
+ .then(() => getCacheItemDefaultValue('person', []))
+ .then((cachedPersons) => {
+ console.log('SAME Persons AS PREVIOUS', cachedPersons.length, stats.persons, persons.length);
+ setPersons([...cachedPersons]);
+ if (stats.persons !== cachedPersons.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('group', []))
+ .then((cachedGroups) => {
+ console.log('SAME groups AS PREVIOUS', cachedGroups.length, stats.groups, groups.length);
+ setGroups([...cachedGroups]);
+ if (stats.groups !== cachedGroups.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('report', []))
+ .then((cachedReports) => {
+ console.log('SAME cachedReports AS PREVIOUS', cachedReports.length, stats.reports, reports.length);
+ setReports([...cachedReports]);
+ if (stats.reports !== cachedReports.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('passage', []))
+ .then((cachedPassages) => {
+ console.log('SAME cachedPassages AS PREVIOUS', cachedPassages.length, stats.passages, passages.length);
+ setPassages([...cachedPassages]);
+ if (stats.passages !== cachedPassages.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('rencontre', []))
+ .then((cachedRencontres) => {
+ console.log('SAME cachedRencontres AS PREVIOUS', cachedRencontres.length, stats.rencontres, rencontres.length);
+ setRencontres([...cachedRencontres]);
+ if (stats.rencontres !== cachedRencontres.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('action', []))
+ .then((cachedActions) => {
+ console.log('SAME cachedActions AS PREVIOUS', cachedActions.length, stats.actions, actions.length);
+ setActions([...cachedActions]);
+ if (stats.actions !== cachedActions.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('territory', []))
+ .then((cachedTerritories) => {
+ console.log('SAME cachedTerritories AS PREVIOUS', cachedTerritories.length, stats.territories, territories.length);
+ setTerritories([...cachedTerritories]);
+ if (stats.territories !== cachedTerritories.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('place', []))
+ .then((cachedPlaces) => {
+ console.log('SAME cachedPlaces AS PREVIOUS', cachedPlaces.length, stats.places, places.length);
+ setPlaces([...cachedPlaces]);
+ if (stats.places !== cachedPlaces.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('relPersonPlace', []))
+ .then((cachedRelsPersonPlace) => {
+ console.log('SAME cachedRelsPersonPlace AS PREVIOUS', cachedRelsPersonPlace.length, stats.relsPersonPlace, relsPersonPlace.length);
+ setRelsPersonPlace([...cachedRelsPersonPlace]);
+ if (stats.relsPersonPlace !== cachedRelsPersonPlace.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('territory-observation', []))
+ .then((cachedTerritoryObservations) => {
+ console.log(
+ 'SAME cachedTerritoryObservations AS PREVIOUS',
+ cachedTerritoryObservations.length,
+ stats.territoryObservations,
+ territoryObservations.length
+ );
+ setTerritoryObservations([...cachedTerritoryObservations]);
+ if (stats.territoryObservations !== cachedTerritoryObservations.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => getCacheItemDefaultValue('comment', []))
+ .then((cachedComments) => {
+ console.log('SAME cachedComments AS PREVIOUS', cachedComments.length, stats.comments, comments.length);
+ setComments([...cachedComments]);
+ if (stats.comments !== cachedComments.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(() => {
+ if (stats.consultations !== consultations.length) {
+ cacheIsInvalidated = true;
+ }
+ if (stats.treatments !== treatments.length) {
+ cacheIsInvalidated = true;
+ }
+ if (stats.medicalFiles !== medicalFiles.length) {
+ cacheIsInvalidated = true;
+ }
+ })
+ .then(async () => {
+ console.log('cacheIsInvalidated', cacheIsInvalidated);
+ if (cacheIsInvalidated) {
+ await clearCache().then(() => {
+ // startInitialLoad
+ setLastLoad(0);
+ setIsLoading(true);
+ setFullScreen(true);
+ setInitialLoad(true);
+ setLoaderTrigger(true);
+ setLoadingText('Chargement des données');
+ setDataConsistencyState('idle');
+ });
+ } else {
+ // refresh
+ setIsLoading(true);
+ setFullScreen(false);
+ setInitialLoad(false);
+ setLoaderTrigger(true);
+ setLoadingText('Mise à jour des données');
+ }
+ });
+ });
+ });
+ }
+
if (!isLoading) return ;
if (!total && !fullScreen) return null;
@@ -432,6 +604,7 @@ export function useDataLoader(options = { refreshOnMount: false }) {
const setInitialLoad = useSetRecoilState(initialLoadState);
const setLoadingText = useSetRecoilState(loadingTextState);
const setLastLoad = useSetRecoilState(lastLoadState);
+ const setDataConsistencyState = useSetRecoilState(checkDataConsistencyAtom);
useEffect(function refreshOnMountEffect() {
if (options.refreshOnMount && !isLoading) refresh();
@@ -445,7 +618,8 @@ export function useDataLoader(options = { refreshOnMount: false }) {
setLoaderTrigger(true);
setLoadingText('Mise à jour des données');
}
- function load() {
+
+ function startInitialLoad() {
setIsLoading(true);
setFullScreen(true);
setInitialLoad(true);
@@ -454,14 +628,19 @@ export function useDataLoader(options = { refreshOnMount: false }) {
}
async function resetCache() {
- await clearCache();
setLastLoad(0);
+ await clearCache();
+ }
+
+ async function checkDataConsistency() {
+ setDataConsistencyState('checking');
}
return {
refresh,
- load,
+ startInitialLoad,
resetCache,
+ checkDataConsistency,
isLoading: Boolean(isLoading),
isFullScreen: Boolean(fullScreen),
};
diff --git a/dashboard/src/components/PasswordInput.js b/dashboard/src/components/PasswordInput.js
index d50214c04d..641eb2e673 100644
--- a/dashboard/src/components/PasswordInput.js
+++ b/dashboard/src/components/PasswordInput.js
@@ -6,7 +6,7 @@ const PasswordInput = ({ className, showPassword, setShowPassword, type, ...prop
setShowPassword(!showPassword)} className="tw-absolute tw-right-4 tw-mb-auto" />
diff --git a/dashboard/src/components/header/index.js b/dashboard/src/components/header/index.js
index 997ecf0cab..339c55c98a 100644
--- a/dashboard/src/components/header/index.js
+++ b/dashboard/src/components/header/index.js
@@ -13,11 +13,40 @@ const Header = ({ title, refreshButton = false, style = {}, titleStyle = {}, cla
};
export const RefreshButton = ({ className }) => {
- const { refresh, isLoading } = useDataLoader();
+ const { refresh, isLoading, checkDataConsistency } = useDataLoader();
+ const [buttonTitle, setButtonTitle] = React.useState('Rafraichir');
+
+ React.useEffect(() => {
+ function enableCheckDataConsistency(e) {
+ // if option, then set title to check data consistency
+ if (e.key === 'Alt') setButtonTitle('NOUVEAU RAFRAICHIR');
+ }
+ function disableCheckDataConsistency(e) {
+ setButtonTitle('Rafraichir');
+ }
+
+ window.addEventListener('keydown', enableCheckDataConsistency);
+ window.addEventListener('keyup', disableCheckDataConsistency);
+ return () => {
+ window.removeEventListener('keydown', enableCheckDataConsistency);
+ window.removeEventListener('keyup', disableCheckDataConsistency);
+ };
+ }, []);
+
return (
- <>
- refresh()} disabled={isLoading} />
- >
+ {
+ if (buttonTitle === 'Rafraichir') {
+ refresh();
+ } else {
+ checkDataConsistency();
+ }
+ }}
+ disabled={isLoading}
+ />
);
};
diff --git a/dashboard/src/scenes/auth/signin.js b/dashboard/src/scenes/auth/signin.js
index 5f6087614a..e361aa8673 100644
--- a/dashboard/src/scenes/auth/signin.js
+++ b/dashboard/src/scenes/auth/signin.js
@@ -28,7 +28,7 @@ const SignIn = () => {
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(true);
const [authViaCookie, setAuthViaCookie] = useState(false);
- const { load: runDataLoader, isLoading, resetCache } = useDataLoader();
+ const { startInitialLoad, isLoading, resetCache } = useDataLoader();
const setToken = useSetRecoilState(authTokenState);
const [signinForm, setSigninForm] = useState({ email: '', password: '', orgEncryptionKey: DEFAULT_ORGANISATION_KEY || '' });
@@ -45,7 +45,7 @@ const SignIn = () => {
}
}, [history, organisation, isLoading, isDesktop]);
- const onSigninValidated = async () => runDataLoader();
+ const onSigninValidated = async () => startInitialLoad();
const onLogout = async () => {
await API.logout();