feat:
This commit is contained in:
64
src/App.js
64
src/App.js
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user