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 [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.
|
||||||
|
|||||||
Reference in New Issue
Block a user