This commit is contained in:
root
2025-11-09 16:13:45 +01:00
parent 9925b3be8f
commit 795319c0f2

View File

@@ -25,24 +25,39 @@ function App() {
const [availableCollapsed, setAvailableCollapsed] = useState(true); const [availableCollapsed, setAvailableCollapsed] = useState(true);
const [adminSettings, setAdminSettings] = useState(null); const [adminSettings, setAdminSettings] = useState(null);
const [adminSettingsLoading, setAdminSettingsLoading] = useState(false); const [adminSettingsLoading, setAdminSettingsLoading] = useState(false);
const [syncProgress, setSyncProgress] = useState({ active: false, percent: 0, message: '', block: false }); const [syncProgress, setSyncProgress] = useState({
active: false,
percent: 0,
message: '',
block: false,
etaSeconds: null
});
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']; const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
const delay = useCallback((ms) => new Promise((resolve) => setTimeout(resolve, ms)), []); const delay = useCallback((ms) => new Promise((resolve) => setTimeout(resolve, ms)), []);
const startSyncProgress = useCallback((message, percent, block = false) => { const startSyncProgress = useCallback((message, percent, block = false) => {
setSyncProgress({ active: true, percent, message, block }); setSyncProgress({ active: true, percent, message, block, etaSeconds: null });
}, []); }, []);
const updateSyncProgress = useCallback((message, percent) => { const updateSyncProgress = useCallback((message, percent, extra = {}) => {
setSyncProgress((prev) => { setSyncProgress((prev) => {
if (!prev.active) { if (!prev.active) {
return prev; return prev;
} }
let nextPercent = prev.percent ?? 0;
if (typeof percent === 'number' && Number.isFinite(percent)) {
const bounded = Math.min(100, Math.max(percent, 0));
nextPercent = Math.max(bounded, nextPercent);
}
return { return {
...prev, ...prev,
message: message || prev.message, message: message ?? prev.message,
percent: Math.min(100, Math.max(percent, prev.percent)) percent: nextPercent,
etaSeconds:
Object.prototype.hasOwnProperty.call(extra, 'etaSeconds') && extra.etaSeconds !== undefined
? extra.etaSeconds
: prev.etaSeconds ?? null
}; };
}); });
}, []); }, []);
@@ -52,10 +67,10 @@ function App() {
if (!prev.active) { if (!prev.active) {
return prev; return prev;
} }
return { ...prev, percent: 100 }; return { ...prev, percent: 100, etaSeconds: null };
}); });
setTimeout(() => { setTimeout(() => {
setSyncProgress({ active: false, percent: 0, message: '', block: false }); setSyncProgress({ active: false, percent: 0, message: '', block: false, etaSeconds: null });
}, 400); }, 400);
}, []); }, []);
@@ -347,6 +362,7 @@ function App() {
} }
try { try {
let jobStarted = false; let jobStarted = false;
const jobStartedAt = Date.now();
const triggerRefresh = async () => { const triggerRefresh = async () => {
const response = await authorizedFetch('/api/stores/refresh', { const response = await authorizedFetch('/api/stores/refresh', {
method: 'POST', method: 'POST',
@@ -376,22 +392,31 @@ function App() {
const total = job.total || 0; const total = job.total || 0;
const processed = job.processed || 0; const processed = job.processed || 0;
const percent = total > 0 ? Math.min(95, 10 + Math.round((processed / total) * 80)) : undefined; const percent = total > 0 ? Math.min(95, 10 + Math.round((processed / total) * 80)) : undefined;
let etaSeconds = null;
if (total > 0 && processed > 0) {
const elapsedSeconds = Math.max(1, (Date.now() - jobStartedAt) / 1000);
const rate = processed / elapsedSeconds;
if (rate > 0) {
const remaining = Math.max(0, total - processed);
etaSeconds = Math.round(remaining / rate);
}
}
const message = job.currentStore const message = job.currentStore
? `Prüfe ${job.currentStore} (${processed}/${total || '?'})` ? `Prüfe ${job.currentStore} (${processed}/${total || '?'})`
: 'Betriebe werden geprüft...'; : 'Betriebe werden geprüft...';
updateSyncProgress(message, percent); updateSyncProgress(message, percent, { etaSeconds });
} else if (!job) { } else if (!job) {
if (statusData.storesFresh) { if (statusData.storesFresh) {
updateSyncProgress('Betriebe aktuell.', 90); updateSyncProgress('Betriebe aktuell.', 90, { etaSeconds: null });
completed = true; completed = true;
} else if (!jobStarted) { } else if (!jobStarted) {
await triggerRefresh(); await triggerRefresh();
await delay(500); await delay(500);
} else { } else {
updateSyncProgress('Warte auf Rückmeldung...', undefined); updateSyncProgress('Warte auf Rückmeldung...', undefined, { etaSeconds: null });
} }
} else if (job.status === 'done') { } else if (job.status === 'done') {
updateSyncProgress('Synchronisierung abgeschlossen', 95); updateSyncProgress('Synchronisierung abgeschlossen', 95, { etaSeconds: null });
completed = true; completed = true;
} else if (job.status === 'error') { } else if (job.status === 'error') {
throw new Error(job.error || 'Unbekannter Fehler beim Prüfen der Betriebe.'); throw new Error(job.error || 'Unbekannter Fehler beim Prüfen der Betriebe.');
@@ -1452,12 +1477,27 @@ function AdminAccessMessage() {
); );
} }
function formatEta(seconds) {
if (seconds == null || seconds === Infinity) {
return null;
}
const clamped = Math.max(0, seconds);
const mins = Math.floor(clamped / 60);
const secs = clamped % 60;
if (mins > 0) {
return `${mins}m ${secs.toString().padStart(2, '0')}s`;
}
return `${secs}s`;
}
function StoreSyncOverlay({ state }) { function StoreSyncOverlay({ state }) {
if (!state?.active) { if (!state?.active) {
return null; return null;
} }
const percent = Math.round(state.percent || 0); const percent = Math.round(state.percent || 0);
const backgroundColor = state.block ? 'rgba(255,255,255,0.95)' : 'rgba(15,23,42,0.4)'; const backgroundColor = state.block ? 'rgba(255,255,255,0.95)' : 'rgba(15,23,42,0.4)';
const etaLabel = formatEta(state.etaSeconds);
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center px-4" style={{ backgroundColor }}> <div className="fixed inset-0 z-50 flex items-center justify-center px-4" style={{ backgroundColor }}>
<div className="bg-white shadow-2xl rounded-lg p-6 w-full max-w-md"> <div className="bg-white shadow-2xl rounded-lg p-6 w-full max-w-md">
@@ -1470,7 +1510,7 @@ function StoreSyncOverlay({ state }) {
</div> </div>
<div className="flex items-center justify-between mt-2 text-sm text-gray-500"> <div className="flex items-center justify-between mt-2 text-sm text-gray-500">
<span>{percent}%</span> <span>{percent}%</span>
<span>Bitte warten...</span> <span>{etaLabel ? `ETA ~ ${etaLabel}` : 'Bitte warten...'}</span>
</div> </div>
<p className="text-xs text-gray-400 mt-2"> <p className="text-xs text-gray-400 mt-2">
Die Verzögerung schützt vor Rate-Limits während die Betriebe geprüft werden. Die Verzögerung schützt vor Rate-Limits während die Betriebe geprüft werden.