diff --git a/src/App.js b/src/App.js index 5c4ef9e..60f50c1 100644 --- a/src/App.js +++ b/src/App.js @@ -168,6 +168,174 @@ function App() { [handleUnauthorized, normalizeAdminSettings] ); + const authorizedFetch = useCallback( + async (url, options = {}, tokenOverride) => { + const activeToken = tokenOverride || session?.token; + if (!activeToken) { + throw new Error('Keine aktive Session'); + } + const headers = { + Authorization: `Bearer ${activeToken}`, + ...(options.headers || {}) + }; + const response = await fetch(url, { ...options, headers }); + if (response.status === 401) { + handleUnauthorized(); + throw new Error('Nicht autorisiert'); + } + return response; + }, + [handleUnauthorized, session?.token] + ); + + const fetchConfig = useCallback( + async (tokenOverride, { silent = false } = {}) => { + const tokenToUse = tokenOverride || session?.token; + if (!tokenToUse) { + return; + } + if (!silent) { + setStatus(''); + } + setLoading(true); + setError(''); + try { + const response = await authorizedFetch('/api/config', {}, tokenToUse); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const data = await response.json(); + setConfig(Array.isArray(data) ? data : []); + if (!silent) { + setStatus('Konfiguration aktualisiert.'); + setTimeout(() => setStatus(''), 3000); + } + } catch (err) { + setError(`Fehler beim Laden der Konfiguration: ${err.message}`); + } finally { + setLoading(false); + } + }, + [session?.token, authorizedFetch] + ); + + const fetchStoresList = useCallback(async () => { + if (!session?.token) { + return; + } + setStatus(''); + setError(''); + try { + const response = await authorizedFetch('/api/stores'); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const data = await response.json(); + setStores(Array.isArray(data) ? data : []); + setStatus('Betriebe aktualisiert.'); + setTimeout(() => setStatus(''), 3000); + } catch (err) { + setError(`Fehler beim Laden der Betriebe: ${err.message}`); + } + }, [session?.token, authorizedFetch]); + + const syncStoresWithProgress = useCallback( + async ({ block = false, reason = 'manual', startJob = true, reuseOverlay = false } = {}) => { + if (!session?.token) { + return; + } + if (!reuseOverlay) { + startSyncProgress('Betriebe werden geprüft...', 5, block); + } else { + updateSyncProgress('Betriebe werden geprüft...', 35); + } + try { + let jobStarted = false; + const triggerRefresh = async () => { + const response = await authorizedFetch('/api/stores/refresh', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ force: true, reason }) + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + await response.json(); + jobStarted = true; + }; + + if (startJob) { + await triggerRefresh(); + } + + let completed = false; + while (!completed) { + const statusResp = await authorizedFetch('/api/stores/refresh/status'); + if (!statusResp.ok) { + throw new Error(`HTTP ${statusResp.status}`); + } + const statusData = await statusResp.json(); + const job = statusData.job; + if (job?.status === 'running') { + const total = job.total || 0; + const processed = job.processed || 0; + const percent = total > 0 ? Math.min(95, 10 + Math.round((processed / total) * 80)) : undefined; + const message = job.currentStore + ? `Prüfe ${job.currentStore} (${processed}/${total || '?'})` + : 'Betriebe werden geprüft...'; + updateSyncProgress(message, percent); + } else if (!job) { + if (statusData.storesFresh) { + updateSyncProgress('Betriebe aktuell.', 90); + completed = true; + } else if (!jobStarted) { + await triggerRefresh(); + await delay(500); + } else { + updateSyncProgress('Warte auf Rückmeldung...', undefined); + } + } else if (job.status === 'done') { + updateSyncProgress('Synchronisierung abgeschlossen', 95); + completed = true; + } else if (job.status === 'error') { + throw new Error(job.error || 'Unbekannter Fehler beim Prüfen der Betriebe.'); + } + if (!completed) { + await delay(1000); + } + } + + await fetchStoresList(); + await fetchConfig(undefined, { silent: true }); + setStatus('Betriebe aktualisiert.'); + setTimeout(() => setStatus(''), 3000); + } catch (err) { + setError(`Aktualisieren der Betriebe fehlgeschlagen: ${err.message}`); + } finally { + if (!reuseOverlay) { + finishSyncProgress(); + } + } + }, + [ + session?.token, + authorizedFetch, + startSyncProgress, + updateSyncProgress, + finishSyncProgress, + delay, + fetchStoresList, + fetchConfig, + setError, + setStatus + ] + ); + + const refreshStoresAndConfig = useCallback( + ({ block = false } = {}) => syncStoresWithProgress({ block, reason: 'manual', startJob: true }), + [syncStoresWithProgress] + ); + useEffect(() => { let ticker; let cancelled = false; @@ -219,26 +387,6 @@ function App() { syncStoresWithProgress ]); - const authorizedFetch = useCallback( - async (url, options = {}, tokenOverride) => { - const activeToken = tokenOverride || session?.token; - if (!activeToken) { - throw new Error('Keine aktive Session'); - } - const headers = { - Authorization: `Bearer ${activeToken}`, - ...(options.headers || {}) - }; - const response = await fetch(url, { ...options, headers }); - if (response.status === 401) { - handleUnauthorized(); - throw new Error('Nicht autorisiert'); - } - return response; - }, - [handleUnauthorized, session?.token] - ); - useEffect(() => { if (!session?.token || !session.isAdmin) { setAdminSettings(null);