diff --git a/src/App.css b/src/App.css index 374edcb..8658d2b 100644 --- a/src/App.css +++ b/src/App.css @@ -117,3 +117,22 @@ pre { width: 100%; } } + +@keyframes dashboard-row-glow { + 0% { + background-color: rgba(59, 130, 246, 0.25); + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.45); + } + 60% { + background-color: rgba(59, 130, 246, 0.15); + box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.15); + } + 100% { + background-color: transparent; + box-shadow: 0 0 0 0 transparent; + } +} + +.dashboard-row-highlight { + animation: dashboard-row-glow 2.5s ease forwards; +} diff --git a/src/App.js b/src/App.js index 84d5b18..6b298d2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { startOfDay } from 'date-fns'; import './App.css'; @@ -40,6 +40,10 @@ function App() { const [activeRangePicker, setActiveRangePicker] = useState(null); const [notificationPanelOpen, setNotificationPanelOpen] = useState(false); const [focusedStoreId, setFocusedStoreId] = useState(null); + const configRef = useRef(config); + useEffect(() => { + configRef.current = config; + }, [config]); const minSelectableDate = useMemo(() => startOfDay(new Date()), []); const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']; @@ -681,47 +685,49 @@ function App() { }; }, [isDirty]); - const persistConfigUpdate = async (updater, successMessage, { autoCommit = false } = {}) => { - if (!session?.token) { - return; - } - let nextConfigState; - setConfig((prev) => { - nextConfigState = typeof updater === 'function' ? updater(prev) : updater; - return nextConfigState; - }); - if (!nextConfigState) { - return; - } - if (!autoCommit) { - setIsDirty(true); - } - setStatus('Speichere...'); - setError(''); - try { - const response = await authorizedFetch('/api/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(nextConfigState) - }); - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); + const persistConfigUpdate = useCallback( + async (updater, successMessage, { autoCommit = false } = {}) => { + const baseConfig = configRef.current; + const nextConfigState = typeof updater === 'function' ? updater(baseConfig) : updater; + if (!nextConfigState) { + return; } - const result = await response.json(); - if (!result.success) { - throw new Error(result.error || 'Unbekannter Fehler beim Speichern'); + setConfig(nextConfigState); + if (!session?.token) { + setIsDirty(true); + return; } - const message = successMessage || 'Konfiguration gespeichert.'; - setStatus(message); - setTimeout(() => setStatus(''), 3000); - setIsDirty(false); - } catch (err) { - setError(`Fehler beim Speichern: ${err.message}`); - if (autoCommit) { + if (!autoCommit) { setIsDirty(true); } - } - }; + setStatus('Speichere...'); + setError(''); + try { + const response = await authorizedFetch('/api/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(nextConfigState) + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const result = await response.json(); + if (!result.success) { + throw new Error(result.error || 'Unbekannter Fehler beim Speichern'); + } + const message = successMessage || 'Konfiguration gespeichert.'; + setStatus(message); + setTimeout(() => setStatus(''), 3000); + setIsDirty(false); + } catch (err) { + setError(`Fehler beim Speichern: ${err.message}`); + if (autoCommit) { + setIsDirty(true); + } + } + }, + [authorizedFetch, session?.token] + ); const deleteEntry = async (entryId) => { const confirmed = await askConfirmation({ diff --git a/src/components/DashboardView.js b/src/components/DashboardView.js index 7295066..315f02b 100644 --- a/src/components/DashboardView.js +++ b/src/components/DashboardView.js @@ -43,17 +43,14 @@ const DashboardView = ({ return; } row.scrollIntoView({ behavior: 'smooth', block: 'center' }); - row.classList.add('ring-4', 'ring-blue-400', 'bg-blue-50', 'transition'); - row.style.transition = 'background-color 0.8s ease, box-shadow 0.8s ease'; + row.classList.add('dashboard-row-highlight', 'ring-4', 'ring-blue-400'); const timeout = setTimeout(() => { - row.classList.remove('ring-4', 'ring-blue-400'); - row.style.backgroundColor = 'transparent'; - setTimeout(onClearFocus, 600); - }, 2200); + row.classList.remove('dashboard-row-highlight', 'ring-4', 'ring-blue-400'); + onClearFocus(); + }, 2500); return () => { clearTimeout(timeout); - row.classList.remove('ring-4', 'ring-blue-400'); - row.style.backgroundColor = 'transparent'; + row.classList.remove('dashboard-row-highlight', 'ring-4', 'ring-blue-400'); }; }, [focusedStoreId, onClearFocus]); const {