refactoring

This commit is contained in:
2025-11-10 13:43:13 +01:00
parent ea95188dd7
commit 7b3625ae3b
4 changed files with 286 additions and 220 deletions

View File

@@ -11,6 +11,7 @@ import useConfigManager from './hooks/useConfigManager';
import useStoreSync from './hooks/useStoreSync'; import useStoreSync from './hooks/useStoreSync';
import useDirtyNavigationGuard from './hooks/useDirtyNavigationGuard'; import useDirtyNavigationGuard from './hooks/useDirtyNavigationGuard';
import useSessionManager from './hooks/useSessionManager'; import useSessionManager from './hooks/useSessionManager';
import useAdminSettings from './hooks/useAdminSettings';
import NavigationTabs from './components/NavigationTabs'; import NavigationTabs from './components/NavigationTabs';
import LoginView from './components/LoginView'; import LoginView from './components/LoginView';
import DashboardView from './components/DashboardView'; import DashboardView from './components/DashboardView';
@@ -28,8 +29,6 @@ function App() {
const [status, setStatus] = useState(''); const [status, setStatus] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const [availableCollapsed, setAvailableCollapsed] = useState(true); const [availableCollapsed, setAvailableCollapsed] = useState(true);
const [adminSettings, setAdminSettings] = useState(null);
const [adminSettingsLoading, setAdminSettingsLoading] = useState(false);
const [initializing, setInitializing] = useState(false); const [initializing, setInitializing] = useState(false);
const [activeRangePicker, setActiveRangePicker] = useState(null); const [activeRangePicker, setActiveRangePicker] = useState(null);
const [confirmDialog, setConfirmDialog] = useState({ open: false, resolve: null }); const [confirmDialog, setConfirmDialog] = useState({ open: false, resolve: null });
@@ -86,39 +85,6 @@ function App() {
nudgeSyncProgress nudgeSyncProgress
} = useSyncProgress(); } = useSyncProgress();
const normalizeAdminSettings = useCallback((raw) => {
if (!raw) {
return null;
}
return {
scheduleCron: raw.scheduleCron || '',
randomDelayMinSeconds: raw.randomDelayMinSeconds ?? '',
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) : '',
description: slot?.description || ''
}))
: [],
notifications: {
ntfy: {
enabled: !!raw.notifications?.ntfy?.enabled,
serverUrl: raw.notifications?.ntfy?.serverUrl || 'https://ntfy.sh',
topicPrefix: raw.notifications?.ntfy?.topicPrefix || '',
username: raw.notifications?.ntfy?.username || '',
password: raw.notifications?.ntfy?.password || ''
},
telegram: {
enabled: !!raw.notifications?.telegram?.enabled,
botToken: raw.notifications?.telegram?.botToken || ''
}
}
};
}, []);
const { const {
session, session,
authorizedFetch, authorizedFetch,
@@ -129,7 +95,6 @@ function App() {
getStoredToken getStoredToken
} = useSessionManager({ } = useSessionManager({
normalizeConfigEntries, normalizeConfigEntries,
normalizeAdminSettings,
onUnauthorized: notifyUnauthorized, onUnauthorized: notifyUnauthorized,
setError, setError,
setLoading setLoading
@@ -169,6 +134,24 @@ function App() {
finishSyncProgress finishSyncProgress
}); });
const {
adminSettings,
adminSettingsLoading,
setAdminSettingsSnapshot,
clearAdminSettings,
handleAdminSettingChange,
handleAdminNotificationChange,
handleIgnoredSlotChange,
addIgnoredSlot,
removeIgnoredSlot,
saveAdminSettings
} = useAdminSettings({
session,
authorizedFetch,
setStatus,
setError
});
const { const {
requestNavigation, requestNavigation,
dialogState: dirtyDialogState, dialogState: dirtyDialogState,
@@ -187,11 +170,11 @@ function App() {
return {}; return {};
} }
setStores(Array.isArray(result.stores) ? result.stores : []); setStores(Array.isArray(result.stores) ? result.stores : []);
setAdminSettings(result.adminSettings ?? null); setAdminSettingsSnapshot(result.adminSettings ?? null);
setConfig(Array.isArray(result.config) ? result.config : []); setConfig(Array.isArray(result.config) ? result.config : []);
return result; return result;
}, },
[setStores, setAdminSettings, setConfig] [setStores, setAdminSettingsSnapshot, setConfig]
); );
const resetSessionState = useCallback(() => { const resetSessionState = useCallback(() => {
@@ -199,8 +182,7 @@ function App() {
setStores([]); setStores([]);
setStatus(''); setStatus('');
setError(''); setError('');
setAdminSettings(null); clearAdminSettings();
setAdminSettingsLoading(false);
setAvailableCollapsed(true); setAvailableCollapsed(true);
setInitializing(false); setInitializing(false);
}, [ }, [
@@ -208,8 +190,7 @@ function App() {
setStores, setStores,
setStatus, setStatus,
setError, setError,
setAdminSettings, clearAdminSettings,
setAdminSettingsLoading,
setAvailableCollapsed, setAvailableCollapsed,
setInitializing setInitializing
]); ]);
@@ -237,42 +218,6 @@ function App() {
sessionToken: session?.token sessionToken: session?.token
}); });
useEffect(() => {
if (!session?.token || !session.isAdmin) {
setAdminSettings(null);
setAdminSettingsLoading(false);
return;
}
let cancelled = false;
setAdminSettingsLoading(true);
(async () => {
try {
const response = await authorizedFetch('/api/admin/settings');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!cancelled) {
setAdminSettings(normalizeAdminSettings(data));
}
} catch (err) {
if (!cancelled) {
setError(`Admin-Einstellungen konnten nicht geladen werden: ${err.message}`);
}
} finally {
if (!cancelled) {
setAdminSettingsLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [session?.token, session?.isAdmin, authorizedFetch, normalizeAdminSettings]);
const handleLogin = async (event) => { const handleLogin = async (event) => {
event.preventDefault(); event.preventDefault();
setLoading(true); setLoading(true);
@@ -637,140 +582,6 @@ function App() {
setFocusedStoreId(storeId); setFocusedStoreId(storeId);
}; };
const handleAdminSettingChange = (field, value, isNumber = false) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
let nextValue = value;
if (isNumber) {
nextValue = value === '' ? '' : Number(value);
}
return {
...prev,
[field]: nextValue
};
});
};
const handleAdminNotificationChange = (channel, field, value) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
notifications: {
...(prev.notifications || {}),
[channel]: {
...(prev.notifications?.[channel] || {}),
[field]: value
}
}
};
});
};
const handleIgnoredSlotChange = (index, field, value) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
const slots = [...(prev.ignoredSlots || [])];
slots[index] = {
...slots[index],
[field]: field === 'storeId' ? value : value
};
return {
...prev,
ignoredSlots: slots
};
});
};
const addIgnoredSlot = () => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
ignoredSlots: [...(prev.ignoredSlots || []), { storeId: '', description: '' }]
};
});
};
const removeIgnoredSlot = (index) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
const slots = [...(prev.ignoredSlots || [])];
slots.splice(index, 1);
return {
...prev,
ignoredSlots: slots
};
});
};
const saveAdminSettings = async () => {
if (!session?.token || !session.isAdmin || !adminSettings) {
return;
}
setStatus('Admin-Einstellungen werden gespeichert...');
setError('');
const toNumber = (value) => {
if (value === '' || value === null || value === undefined) {
return undefined;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
};
try {
const payload = {
scheduleCron: adminSettings.scheduleCron,
randomDelayMinSeconds: toNumber(adminSettings.randomDelayMinSeconds),
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 || ''
})),
notifications: {
ntfy: {
enabled: !!adminSettings.notifications?.ntfy?.enabled,
serverUrl: adminSettings.notifications?.ntfy?.serverUrl || '',
topicPrefix: adminSettings.notifications?.ntfy?.topicPrefix || '',
username: adminSettings.notifications?.ntfy?.username || '',
password: adminSettings.notifications?.ntfy?.password || ''
},
telegram: {
enabled: !!adminSettings.notifications?.telegram?.enabled,
botToken: adminSettings.notifications?.telegram?.botToken || ''
}
}
};
const response = await authorizedFetch('/api/admin/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setAdminSettings(normalizeAdminSettings(data));
setStatus('Admin-Einstellungen gespeichert.');
setTimeout(() => setStatus(''), 3000);
} catch (err) {
setError(`Speichern der Admin-Einstellungen fehlgeschlagen: ${err.message}`);
}
};
if (!session?.token) { if (!session?.token) {
return ( return (
<> <>

View File

@@ -0,0 +1,189 @@
import { useCallback, useEffect, useState } from 'react';
import { normalizeAdminSettings, serializeAdminSettings } from '../utils/adminSettings';
const useAdminSettings = ({ session, authorizedFetch, setStatus, setError }) => {
const [adminSettings, setAdminSettings] = useState(null);
const [adminSettingsLoading, setAdminSettingsLoading] = useState(false);
const clearAdminSettings = useCallback(() => {
setAdminSettings(null);
setAdminSettingsLoading(false);
}, []);
const setAdminSettingsSnapshot = useCallback((snapshot) => {
setAdminSettings(snapshot ? normalizeAdminSettings(snapshot) : null);
}, []);
const loadAdminSettings = useCallback(async () => {
if (!session?.token || !session.isAdmin) {
clearAdminSettings();
return;
}
setAdminSettingsLoading(true);
try {
const response = await authorizedFetch('/api/admin/settings');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setAdminSettings(normalizeAdminSettings(data));
} catch (err) {
setError(`Admin-Einstellungen konnten nicht geladen werden: ${err.message}`);
} finally {
setAdminSettingsLoading(false);
}
}, [session?.token, session?.isAdmin, authorizedFetch, setError, clearAdminSettings]);
useEffect(() => {
if (!session?.token || !session.isAdmin) {
clearAdminSettings();
return;
}
let cancelled = false;
setAdminSettingsLoading(true);
(async () => {
try {
const response = await authorizedFetch('/api/admin/settings');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!cancelled) {
setAdminSettings(normalizeAdminSettings(data));
}
} catch (err) {
if (!cancelled) {
setError(`Admin-Einstellungen konnten nicht geladen werden: ${err.message}`);
}
} finally {
if (!cancelled) {
setAdminSettingsLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [session?.token, session?.isAdmin, authorizedFetch, setError, clearAdminSettings]);
const handleAdminSettingChange = useCallback((field, value, isNumber = false) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
let nextValue = value;
if (isNumber) {
nextValue = value === '' ? '' : Number(value);
}
return {
...prev,
[field]: nextValue
};
});
}, []);
const handleAdminNotificationChange = useCallback((channel, field, value) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
notifications: {
...(prev.notifications || {}),
[channel]: {
...(prev.notifications?.[channel] || {}),
[field]: value
}
}
};
});
}, []);
const handleIgnoredSlotChange = useCallback((index, field, value) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
const slots = [...(prev.ignoredSlots || [])];
slots[index] = {
...slots[index],
[field]: value
};
return {
...prev,
ignoredSlots: slots
};
});
}, []);
const addIgnoredSlot = useCallback(() => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
ignoredSlots: [...(prev.ignoredSlots || []), { storeId: '', description: '' }]
};
});
}, []);
const removeIgnoredSlot = useCallback((index) => {
setAdminSettings((prev) => {
if (!prev) {
return prev;
}
const slots = [...(prev.ignoredSlots || [])];
slots.splice(index, 1);
return {
...prev,
ignoredSlots: slots
};
});
}, []);
const saveAdminSettings = useCallback(async () => {
if (!session?.token || !session.isAdmin || !adminSettings) {
return;
}
setStatus('Admin-Einstellungen werden gespeichert...');
setError('');
try {
const payload = serializeAdminSettings(adminSettings);
const response = await authorizedFetch('/api/admin/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setAdminSettings(normalizeAdminSettings(data));
setStatus('Admin-Einstellungen gespeichert.');
setTimeout(() => setStatus(''), 3000);
} catch (err) {
setError(`Speichern der Admin-Einstellungen fehlgeschlagen: ${err.message}`);
}
}, [session?.token, session?.isAdmin, adminSettings, authorizedFetch, setStatus, setError]);
return {
adminSettings,
adminSettingsLoading,
setAdminSettingsSnapshot,
clearAdminSettings,
handleAdminSettingChange,
handleAdminNotificationChange,
handleIgnoredSlotChange,
addIgnoredSlot,
removeIgnoredSlot,
saveAdminSettings,
loadAdminSettings
};
};
export default useAdminSettings;

View File

@@ -1,14 +1,9 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { normalizeAdminSettings } from '../utils/adminSettings';
const TOKEN_STORAGE_KEY = 'pickupConfigToken'; const TOKEN_STORAGE_KEY = 'pickupConfigToken';
const useSessionManager = ({ const useSessionManager = ({ normalizeConfigEntries, onUnauthorized, setError, setLoading }) => {
normalizeConfigEntries,
normalizeAdminSettings,
onUnauthorized,
setError,
setLoading
}) => {
const [session, setSession] = useState(null); const [session, setSession] = useState(null);
const unauthorizedRef = useRef(onUnauthorized || (() => {})); const unauthorizedRef = useRef(onUnauthorized || (() => {}));
@@ -127,7 +122,7 @@ const useSessionManager = ({
setLoading(false); setLoading(false);
} }
}, },
[normalizeAdminSettings, normalizeConfigEntries, handleUnauthorized, setError, setLoading] [normalizeConfigEntries, handleUnauthorized, setError, setLoading]
); );
const performLogout = useCallback(async () => { const performLogout = useCallback(async () => {

View File

@@ -0,0 +1,71 @@
export const normalizeAdminSettings = (raw) => {
if (!raw) {
return null;
}
return {
scheduleCron: raw.scheduleCron || '',
randomDelayMinSeconds: raw.randomDelayMinSeconds ?? '',
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) : '',
description: slot?.description || ''
}))
: [],
notifications: {
ntfy: {
enabled: !!raw.notifications?.ntfy?.enabled,
serverUrl: raw.notifications?.ntfy?.serverUrl || 'https://ntfy.sh',
topicPrefix: raw.notifications?.ntfy?.topicPrefix || '',
username: raw.notifications?.ntfy?.username || '',
password: raw.notifications?.ntfy?.password || ''
},
telegram: {
enabled: !!raw.notifications?.telegram?.enabled,
botToken: raw.notifications?.telegram?.botToken || ''
}
}
};
};
const toNumberOrUndefined = (value) => {
if (value === '' || value === null || value === undefined) {
return undefined;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
};
export const serializeAdminSettings = (adminSettings) => {
if (!adminSettings) {
return null;
}
return {
scheduleCron: adminSettings.scheduleCron,
randomDelayMinSeconds: toNumberOrUndefined(adminSettings.randomDelayMinSeconds),
randomDelayMaxSeconds: toNumberOrUndefined(adminSettings.randomDelayMaxSeconds),
initialDelayMinSeconds: toNumberOrUndefined(adminSettings.initialDelayMinSeconds),
initialDelayMaxSeconds: toNumberOrUndefined(adminSettings.initialDelayMaxSeconds),
storePickupCheckDelayMs: toNumberOrUndefined(adminSettings.storePickupCheckDelayMs),
ignoredSlots: (adminSettings.ignoredSlots || []).map((slot) => ({
storeId: slot.storeId || '',
description: slot.description || ''
})),
notifications: {
ntfy: {
enabled: !!adminSettings.notifications?.ntfy?.enabled,
serverUrl: adminSettings.notifications?.ntfy?.serverUrl || '',
topicPrefix: adminSettings.notifications?.ntfy?.topicPrefix || '',
username: adminSettings.notifications?.ntfy?.username || '',
password: adminSettings.notifications?.ntfy?.password || ''
},
telegram: {
enabled: !!adminSettings.notifications?.telegram?.enabled,
botToken: adminSettings.notifications?.telegram?.botToken || ''
}
}
};
};