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 [adminSettings, setAdminSettings] = useState(null);
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 delay = useCallback((ms) => new Promise((resolve) => setTimeout(resolve, ms)), []);
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) => {
if (!prev.active) {
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 {
...prev,
message: message || prev.message,
percent: Math.min(100, Math.max(percent, prev.percent))
message: message ?? prev.message,
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) {
return prev;
}
return { ...prev, percent: 100 };
return { ...prev, percent: 100, etaSeconds: null };
});
setTimeout(() => {
setSyncProgress({ active: false, percent: 0, message: '', block: false });
setSyncProgress({ active: false, percent: 0, message: '', block: false, etaSeconds: null });
}, 400);
}, []);
@@ -347,6 +362,7 @@ function App() {
}
try {
let jobStarted = false;
const jobStartedAt = Date.now();
const triggerRefresh = async () => {
const response = await authorizedFetch('/api/stores/refresh', {
method: 'POST',
@@ -376,22 +392,31 @@ function App() {
const total = job.total || 0;
const processed = job.processed || 0;
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
? `Prüfe ${job.currentStore} (${processed}/${total || '?'})`
: 'Betriebe werden geprüft...';
updateSyncProgress(message, percent);
updateSyncProgress(message, percent, { etaSeconds });
} else if (!job) {
if (statusData.storesFresh) {
updateSyncProgress('Betriebe aktuell.', 90);
updateSyncProgress('Betriebe aktuell.', 90, { etaSeconds: null });
completed = true;
} else if (!jobStarted) {
await triggerRefresh();
await delay(500);
} else {
updateSyncProgress('Warte auf Rückmeldung...', undefined);
updateSyncProgress('Warte auf Rückmeldung...', undefined, { etaSeconds: null });
}
} else if (job.status === 'done') {
updateSyncProgress('Synchronisierung abgeschlossen', 95);
updateSyncProgress('Synchronisierung abgeschlossen', 95, { etaSeconds: null });
completed = true;
} else if (job.status === 'error') {
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 }) {
if (!state?.active) {
return null;
}
const percent = Math.round(state.percent || 0);
const backgroundColor = state.block ? 'rgba(255,255,255,0.95)' : 'rgba(15,23,42,0.4)';
const etaLabel = formatEta(state.etaSeconds);
return (
<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">
@@ -1470,7 +1510,7 @@ function StoreSyncOverlay({ state }) {
</div>
<div className="flex items-center justify-between mt-2 text-sm text-gray-500">
<span>{percent}%</span>
<span>Bitte warten...</span>
<span>{etaLabel ? `ETA ~ ${etaLabel}` : 'Bitte warten...'}</span>
</div>
<p className="text-xs text-gray-400 mt-2">
Die Verzögerung schützt vor Rate-Limits während die Betriebe geprüft werden.