aktueller Stand
This commit is contained in:
143
src/App.js
143
src/App.js
@@ -25,9 +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 weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
|
||||
|
||||
const startSyncProgress = useCallback((message, percent, block = false) => {
|
||||
setSyncProgress({ active: true, percent, message, block });
|
||||
}, []);
|
||||
|
||||
const updateSyncProgress = useCallback((message, percent) => {
|
||||
setSyncProgress((prev) => {
|
||||
if (!prev.active) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
message: message || prev.message,
|
||||
percent: Math.min(100, Math.max(percent, prev.percent))
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const finishSyncProgress = useCallback(() => {
|
||||
setSyncProgress((prev) => {
|
||||
if (!prev.active) {
|
||||
return prev;
|
||||
}
|
||||
return { ...prev, percent: 100 };
|
||||
});
|
||||
setTimeout(() => {
|
||||
setSyncProgress({ active: false, percent: 0, message: '', block: false });
|
||||
}, 400);
|
||||
}, []);
|
||||
|
||||
const normalizeAdminSettings = useCallback((raw) => {
|
||||
if (!raw) {
|
||||
return null;
|
||||
@@ -38,6 +68,7 @@ function App() {
|
||||
randomDelayMaxSeconds: raw.randomDelayMaxSeconds ?? '',
|
||||
initialDelayMinSeconds: raw.initialDelayMinSeconds ?? '',
|
||||
initialDelayMaxSeconds: raw.initialDelayMaxSeconds ?? '',
|
||||
storePickupCheckDelayMs: raw.storePickupCheckDelayMs ?? '',
|
||||
ignoredSlots: Array.isArray(raw.ignoredSlots)
|
||||
? raw.ignoredSlots.map((slot) => ({
|
||||
storeId: slot?.storeId ? String(slot.storeId) : '',
|
||||
@@ -70,9 +101,15 @@ function App() {
|
||||
}, [resetSessionState]);
|
||||
|
||||
const bootstrapSession = useCallback(
|
||||
async (token) => {
|
||||
async (token, { withProgress = false } = {}) => {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError('');
|
||||
if (withProgress) {
|
||||
startSyncProgress('Session wird aufgebaut...', 10, true);
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/auth/session', {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
@@ -86,6 +123,9 @@ function App() {
|
||||
}
|
||||
const data = await response.json();
|
||||
setSession({ token, profile: data.profile, isAdmin: data.isAdmin });
|
||||
if (withProgress) {
|
||||
updateSyncProgress('Betriebe werden geprüft...', 45);
|
||||
}
|
||||
setStores(Array.isArray(data.stores) ? data.stores : []);
|
||||
setAdminSettings(data.isAdmin ? normalizeAdminSettings(data.adminSettings) : null);
|
||||
|
||||
@@ -100,14 +140,23 @@ function App() {
|
||||
throw new Error(`HTTP ${configResponse.status}`);
|
||||
}
|
||||
const configData = await configResponse.json();
|
||||
if (withProgress) {
|
||||
updateSyncProgress('Konfiguration wird geladen...', 75);
|
||||
}
|
||||
setConfig(Array.isArray(configData) ? configData : []);
|
||||
if (withProgress) {
|
||||
updateSyncProgress('Synchronisierung abgeschlossen', 95);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(`Session konnte nicht wiederhergestellt werden: ${err.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (withProgress) {
|
||||
finishSyncProgress();
|
||||
}
|
||||
}
|
||||
},
|
||||
[handleUnauthorized, normalizeAdminSettings]
|
||||
[handleUnauthorized, normalizeAdminSettings, startSyncProgress, updateSyncProgress, finishSyncProgress]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -200,10 +249,7 @@ function App() {
|
||||
} catch (storageError) {
|
||||
console.warn('Konnte Token nicht speichern:', storageError);
|
||||
}
|
||||
setSession({ token: data.token, profile: data.profile, isAdmin: data.isAdmin });
|
||||
setConfig(Array.isArray(data.config) ? data.config : []);
|
||||
setStores(Array.isArray(data.stores) ? data.stores : []);
|
||||
setAdminSettings(data.isAdmin ? normalizeAdminSettings(data.adminSettings) : null);
|
||||
await bootstrapSession(data.token, { withProgress: true });
|
||||
setStatus('Anmeldung erfolgreich. Konfiguration geladen.');
|
||||
setTimeout(() => setStatus(''), 3000);
|
||||
} catch (err) {
|
||||
@@ -276,6 +322,24 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshStoresAndConfig = useCallback(
|
||||
async ({ block = false } = {}) => {
|
||||
if (!session?.token) {
|
||||
return;
|
||||
}
|
||||
startSyncProgress('Betriebe werden geprüft...', 15, block);
|
||||
try {
|
||||
await fetchStoresList();
|
||||
updateSyncProgress('Konfiguration wird aktualisiert...', 70);
|
||||
await fetchConfig(undefined, { silent: true });
|
||||
updateSyncProgress('Synchronisierung abgeschlossen', 95);
|
||||
} finally {
|
||||
finishSyncProgress();
|
||||
}
|
||||
},
|
||||
[session?.token, fetchStoresList, fetchConfig, startSyncProgress, updateSyncProgress, finishSyncProgress]
|
||||
);
|
||||
|
||||
const saveConfig = async () => {
|
||||
if (!session?.token) {
|
||||
return;
|
||||
@@ -571,6 +635,7 @@ function App() {
|
||||
randomDelayMaxSeconds: toNumber(adminSettings.randomDelayMaxSeconds),
|
||||
initialDelayMinSeconds: toNumber(adminSettings.initialDelayMinSeconds),
|
||||
initialDelayMaxSeconds: toNumber(adminSettings.initialDelayMaxSeconds),
|
||||
storePickupCheckDelayMs: toNumber(adminSettings.storePickupCheckDelayMs),
|
||||
ignoredSlots: (adminSettings.ignoredSlots || []).map((slot) => ({
|
||||
storeId: slot.storeId || '',
|
||||
description: slot.description || ''
|
||||
@@ -672,7 +737,7 @@ function App() {
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={fetchStoresList}
|
||||
onClick={() => refreshStoresAndConfig({ block: false })}
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors"
|
||||
>
|
||||
Betriebe aktualisieren
|
||||
@@ -721,10 +786,11 @@ function App() {
|
||||
statusClass = 'text-gray-500';
|
||||
} else if (needsRestore) {
|
||||
statusLabel = 'Ausgeblendet – erneut hinzufügen';
|
||||
statusClass = 'text-amber-600';
|
||||
}
|
||||
if (blockedByNoPickups) {
|
||||
statusLabel = 'Keine Pickups – automatisch verborgen';
|
||||
statusClass = 'text-amber-600';
|
||||
statusClass = 'text-red-600';
|
||||
}
|
||||
return (
|
||||
<button
|
||||
@@ -1104,6 +1170,18 @@ function App() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Verzögerung Store-Prüfung (ms)</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={adminSettings.storePickupCheckDelayMs}
|
||||
onChange={(e) => handleAdminSettingChange('storePickupCheckDelayMs', e.target.value, true)}
|
||||
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||||
placeholder="z. B. 400"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Hilft Rate-Limits beim Abfragen der Pickups zu vermeiden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-purple-200 rounded-lg bg-white p-4">
|
||||
@@ -1166,16 +1244,19 @@ function App() {
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<div className="min-h-screen bg-gray-100 py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<NavigationTabs isAdmin={session?.isAdmin} />
|
||||
<Routes>
|
||||
<Route path="/" element={dashboardContent} />
|
||||
<Route path="/admin" element={adminPageContent} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
<>
|
||||
<div className="min-h-screen bg-gray-100 py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<NavigationTabs isAdmin={session?.isAdmin} />
|
||||
<Routes>
|
||||
<Route path="/" element={dashboardContent} />
|
||||
<Route path="/admin" element={adminPageContent} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StoreSyncOverlay state={syncProgress} />
|
||||
</>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
@@ -1222,4 +1303,32 @@ function AdminAccessMessage() {
|
||||
);
|
||||
}
|
||||
|
||||
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)';
|
||||
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">
|
||||
<p className="text-gray-800 font-semibold mb-3">{state.message || 'Synchronisiere...'}</p>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
|
||||
<div
|
||||
className="h-3 bg-blue-600 transition-all duration-300 ease-out"
|
||||
style={{ width: `${percent}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2 text-sm text-gray-500">
|
||||
<span>{percent}%</span>
|
||||
<span>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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user