import { useCallback, useEffect, useMemo, useState } from 'react'; import { createColumnHelper, flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'; import { haversineDistanceKm } from '../utils/distance'; import { inferLocationLabel } from '../utils/locationLabel'; import NotificationPanel from './NotificationPanel'; const REGION_STORAGE_KEY = 'storeWatchRegionSelection'; const WATCH_TABLE_STATE_KEY = 'storeWatchTableState'; const PANEL_STORAGE_KEY = 'storeWatchPanels'; const PANEL_IDS = ['stores', 'watch']; function createDefaultPanelState() { return { order: [...PANEL_IDS], collapsed: PANEL_IDS.reduce( (acc, panelId) => ({ ...acc, [panelId]: false }), {} ) }; } function formatWatchStatusLabel(status) { if (status === 1) { return 'Suchend'; } if (status === 0) { return 'Nicht suchend'; } return 'Unbekannt'; } const columnHelper = createColumnHelper(); const DEFAULT_TABLE_STATE = { sorting: [{ id: 'distanceKm', desc: false }], columnFilters: [{ id: 'membership', value: 'false' }] }; function normalizePanelLayout(state) { const fallback = createDefaultPanelState(); if (!state || typeof state !== 'object') { return fallback; } const rawOrder = Array.isArray(state.order) ? state.order : []; const normalizedOrder = rawOrder.filter((panelId) => PANEL_IDS.includes(panelId)); const dedupedOrder = [...new Set([...normalizedOrder, ...PANEL_IDS])]; const collapsed = { ...fallback.collapsed }; PANEL_IDS.forEach((panelId) => { collapsed[panelId] = Boolean(state?.collapsed?.[panelId]); }); return { order: dedupedOrder, collapsed }; } function readPanelLayout() { if (typeof window === 'undefined') { return createDefaultPanelState(); } try { const raw = window.localStorage.getItem(PANEL_STORAGE_KEY); if (!raw) { return createDefaultPanelState(); } const parsed = JSON.parse(raw); return normalizePanelLayout(parsed); } catch { return createDefaultPanelState(); } } function persistPanelLayout(state) { if (typeof window === 'undefined') { return; } try { const normalized = normalizePanelLayout(state); window.localStorage.setItem(PANEL_STORAGE_KEY, JSON.stringify(normalized)); } catch { /* ignore */ } } const ColumnTextFilter = ({ column, placeholder }) => { if (!column.getCanFilter()) { return null; } return ( column.setFilterValue(event.target.value)} placeholder={placeholder} className="mt-1 w-full rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500" /> ); }; const ColumnSelectFilter = ({ column, options }) => { if (!column.getCanFilter()) { return null; } return ( ); }; function readWatchTableState() { if (typeof window === 'undefined') { return DEFAULT_TABLE_STATE; } try { const raw = window.localStorage.getItem(WATCH_TABLE_STATE_KEY); if (!raw) { return DEFAULT_TABLE_STATE; } const parsed = JSON.parse(raw); return { sorting: Array.isArray(parsed.sorting) && parsed.sorting.length > 0 ? parsed.sorting : DEFAULT_TABLE_STATE.sorting, columnFilters: Array.isArray(parsed.columnFilters) && parsed.columnFilters.length > 0 ? parsed.columnFilters : DEFAULT_TABLE_STATE.columnFilters }; } catch { return DEFAULT_TABLE_STATE; } } function persistWatchTableState(state) { if (typeof window === 'undefined') { return; } try { window.localStorage.setItem(WATCH_TABLE_STATE_KEY, JSON.stringify(state)); } catch { /* ignore */ } } const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation, locationLoading = false, locationError = '', notificationPanelOpen = false, onToggleNotificationPanel = () => {}, notificationProps, isAdmin = false }) => { const [regions, setRegions] = useState([]); const [selectedRegionId, setSelectedRegionId] = useState(() => { if (typeof window === 'undefined') { return 'all'; } try { return window.localStorage.getItem(REGION_STORAGE_KEY) || 'all'; } catch { return 'all'; } }); const [storesByRegion, setStoresByRegion] = useState({}); const [watchList, setWatchList] = useState([]); const [regionLoading, setRegionLoading] = useState(false); const [storesLoading, setStoresLoading] = useState(false); const [subscriptionsLoading, setSubscriptionsLoading] = useState(false); const [status, setStatus] = useState(''); const [error, setError] = useState(''); const [dirty, setDirty] = useState(false); const [saving, setSaving] = useState(false); const [adhocChecking, setAdhocChecking] = useState(false); const [lastAdhocCheck, setLastAdhocCheck] = useState(null); const initialTableState = useMemo(() => readWatchTableState(), []); const [sorting, setSorting] = useState(initialTableState.sorting); const [columnFilters, setColumnFilters] = useState(initialTableState.columnFilters); const [panelLayout, setPanelLayout] = useState(() => readPanelLayout()); const normalizedPanelLayout = useMemo(() => normalizePanelLayout(panelLayout), [panelLayout]); const panelOrder = normalizedPanelLayout.order; const aggregatedRegionStores = useMemo(() => { const list = []; Object.values(storesByRegion).forEach((entry) => { if (Array.isArray(entry?.stores)) { entry.stores.forEach((store) => { if (store) { list.push(store); } }); } }); return list; }, [storesByRegion]); const proximityLabel = useMemo(() => { if (!userLocation) { return null; } return inferLocationLabel(userLocation, aggregatedRegionStores); }, [userLocation, aggregatedRegionStores]); const displayLocation = useMemo(() => { if (!userLocation) { return null; } if (userLocation.label) { return userLocation; } if (proximityLabel) { return { ...userLocation, label: proximityLabel.label, labelDistanceKm: proximityLabel.distanceKm }; } return userLocation; }, [userLocation, proximityLabel]); useEffect(() => { if (typeof window === 'undefined') { return; } try { window.localStorage.setItem(REGION_STORAGE_KEY, selectedRegionId || 'all'); } catch { /* ignore */ } }, [selectedRegionId]); useEffect(() => { persistWatchTableState({ sorting, columnFilters }); }, [sorting, columnFilters]); useEffect(() => { persistPanelLayout(panelLayout); }, [panelLayout]); const togglePanelCollapsed = useCallback((panelId) => { if (!PANEL_IDS.includes(panelId)) { return; } setPanelLayout((prev) => { const normalized = normalizePanelLayout(prev); return { ...normalized, collapsed: { ...normalized.collapsed, [panelId]: !normalized.collapsed[panelId] } }; }); }, []); const movePanel = useCallback((panelId, direction) => { if (!PANEL_IDS.includes(panelId) || !direction) { return; } setPanelLayout((prev) => { const normalized = normalizePanelLayout(prev); const order = [...normalized.order]; const currentIndex = order.indexOf(panelId); const delta = direction === 'up' ? -1 : 1; const nextIndex = currentIndex + delta; if (currentIndex === -1 || nextIndex < 0 || nextIndex >= order.length) { return normalized; } [order[currentIndex], order[nextIndex]] = [order[nextIndex], order[currentIndex]]; return { ...normalized, order }; }); }, []); const watchedIds = useMemo( () => new Set(watchList.map((entry) => String(entry.storeId))), [watchList] ); const selectedRegion = useMemo(() => { if (selectedRegionId === 'all') { return null; } return regions.find((region) => String(region.id) === String(selectedRegionId)) || null; }, [regions, selectedRegionId]); const activeRegionLabel = selectedRegionId === 'all' ? 'allen Regionen' : selectedRegion?.name || 'dieser Region'; const storesPanelTitle = selectedRegionId === 'all' ? 'Betriebe' : `Betriebe in ${activeRegionLabel}`; const selectedStatusMeta = useMemo(() => { if (selectedRegionId === 'all') { const metas = regions .map((region) => storesByRegion[String(region.id)]?.statusMeta) .filter(Boolean); if (metas.length === 0) { return null; } const aggregated = metas.reduce( (acc, meta) => ({ total: acc.total + (meta.total || 0), refreshed: acc.refreshed + (meta.refreshed || 0), fromCache: acc.fromCache + (meta.fromCache || 0), missing: acc.missing + (meta.missing || 0), generatedAt: Math.max(acc.generatedAt, meta.generatedAt || 0) }), { total: 0, refreshed: 0, fromCache: 0, missing: 0, generatedAt: 0 } ); return aggregated; } return storesByRegion[String(selectedRegionId)]?.statusMeta || null; }, [selectedRegionId, storesByRegion, regions]); const lastUpdatedAt = useMemo(() => { if (selectedRegionId === 'all') { const timestamps = regions .map((region) => storesByRegion[String(region.id)]?.fetchedAt) .filter(Boolean); if (timestamps.length === 0) { return null; } return Math.max(...timestamps); } return storesByRegion[String(selectedRegionId)]?.fetchedAt || null; }, [regions, selectedRegionId, storesByRegion]); const currentStores = useMemo(() => { if (selectedRegionId === 'all') { const combined = new Map(); regions.forEach((region) => { const entry = storesByRegion[String(region.id)]; if (entry?.stores) { entry.stores.forEach((store) => { if (store?.id) { combined.set(String(store.id), store); } }); } }); return Array.from(combined.values()); } const regionEntry = storesByRegion[String(selectedRegionId)]; if (!regionEntry || !Array.isArray(regionEntry.stores)) { return []; } return regionEntry.stores; }, [regions, storesByRegion, selectedRegionId]); const regionStores = useMemo( () => currentStores.filter((store) => Number(store.cooperationStatus) === 5), [currentStores] ); const statusSummary = useMemo(() => { if (!selectedStatusMeta) { return 'Team-Status noch nicht geladen.'; } const parts = [ `${selectedStatusMeta.refreshed || 0} aktualisiert`, `${selectedStatusMeta.fromCache || 0} aus Cache` ]; if (selectedStatusMeta.missing) { parts.push(`${selectedStatusMeta.missing} ohne Daten`); } const timestamp = selectedStatusMeta.generatedAt ? new Date(selectedStatusMeta.generatedAt).toLocaleString('de-DE') : null; return `Team-Status: ${parts.join(', ')}${timestamp ? ` (Stand ${timestamp})` : ''}`; }, [selectedStatusMeta]); const membershipMap = useMemo(() => { const map = new Map(); (knownStores || []).forEach((store) => { if (store?.id) { map.set(String(store.id), store); } }); return map; }, [knownStores]); const handleToggleStore = useCallback( (store, checked) => { setWatchList((prev) => { const storeId = String(store.id || store.storeId); const existing = prev.find((entry) => entry.storeId === storeId); if (checked) { if (store.isOpen || existing) { return prev; } setDirty(true); const regionName = store.region?.name || selectedRegion?.name || existing?.regionName || ''; return [ ...prev, { storeId, storeName: store.name || store.storeName || `Store ${storeId}`, regionId: String(store.region?.id || selectedRegionId || existing?.regionId || ''), regionName, lastTeamSearchStatus: existing?.lastTeamSearchStatus ?? null } ]; } if (!existing) { return prev; } setDirty(true); return prev.filter((entry) => entry.storeId !== storeId); }); }, [selectedRegion, selectedRegionId] ); const handleRemoveWatch = useCallback((storeId) => { setWatchList((prev) => { const next = prev.filter((entry) => entry.storeId !== storeId); if (next.length !== prev.length) { setDirty(true); } return next; }); }, []); const tableData = useMemo( () => regionStores.map((store) => { const membership = membershipMap.has(String(store.id)); const lat = Number(store.location?.lat); const lon = Number(store.location?.lon); const statusValue = store.teamSearchStatus === null || store.teamSearchStatus === undefined ? null : Number(store.teamSearchStatus); const distance = userLocation && Number.isFinite(lat) && Number.isFinite(lon) ? haversineDistanceKm(userLocation.lat, userLocation.lon, lat, lon) : null; const isOpen = statusValue === 1; return { ...store, membership, distanceKm: distance, teamStatusUpdatedAt: store.teamStatusUpdatedAt || null, teamSearchStatus: statusValue, isOpen }; }), [regionStores, membershipMap, userLocation] ); const columns = useMemo( () => [ columnHelper.accessor('name', { header: ({ column }) => (
), cell: ({ row }) => (

{row.original.name}

#{row.original.id}

), sortingFn: 'alphanumeric', enableColumnFilter: true, filterFn: 'includesString' }), columnHelper.accessor((row) => row.city || '', { id: 'city', header: ({ column }) => (
), cell: ({ row }) => (

{row.original.city || 'unbekannt'}

{row.original.street || ''}

{(() => { const hasUserLocation = userLocation && Number.isFinite(userLocation.lat) && Number.isFinite(userLocation.lon); const storeLat = Number(row.original.location?.lat); const storeLon = Number(row.original.location?.lon); const hasStoreLocation = Number.isFinite(storeLat) && Number.isFinite(storeLon); if (!hasUserLocation || !hasStoreLocation) { return null; } const origin = encodeURIComponent(`${userLocation.lat},${userLocation.lon}`); const storeLabel = row.original.name || row.original.city || `Store ${row.original.id}`; const destination = encodeURIComponent(`${storeLabel}@${storeLat},${storeLon}`); const mapsUrl = `https://www.google.com/maps/dir/?api=1&origin=${origin}&destination=${destination}&travelmode=driving`; return ( Route in Google Maps ); })()}
), sortingFn: 'alphanumeric', filterFn: 'includesString' }), columnHelper.accessor('createdAt', { header: ({ column }) => ( ), cell: ({ getValue }) => { const value = getValue(); const label = value ? new Date(value).toLocaleDateString('de-DE', { year: 'numeric', month: '2-digit', day: '2-digit' }) : '–'; return
{label}
; }, sortingFn: (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); return new Date(a || 0).getTime() - new Date(b || 0).getTime(); } }), columnHelper.accessor('isOpen', { header: ({ column }) => (
), cell: ({ row, getValue }) => { const value = getValue(); const updatedAt = row.original.teamStatusUpdatedAt ? new Date(row.original.teamStatusUpdatedAt).toLocaleDateString('de-DE') : null; if (value === null) { return ; } return (
{value ? 'Ja' : 'Nein'} {updatedAt &&

{updatedAt}

}
); }, filterFn: (row, columnId, value) => { if (value === undefined) { return true; } const boolValue = value === 'true'; return row.getValue(columnId) === boolValue; }, sortingFn: (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); return Number(b) - Number(a); } }), columnHelper.accessor('membership', { header: ({ column }) => (
), cell: ({ getValue }) => { const value = getValue(); return ( {value ? 'Ja' : 'Nein'} ); }, filterFn: (row, columnId, value) => { if (value === undefined) { return true; } const boolValue = value === 'true'; return row.getValue(columnId) === boolValue; }, sortingFn: (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); return Number(b) - Number(a); } }), columnHelper.accessor('distanceKm', { header: ({ column }) => (
{!userLocation &&

Standort erforderlich

}
), cell: ({ getValue }) => { const value = getValue(); if (!value && value !== 0) { return ; } return {value.toFixed(2)} km; }, sortingFn: (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); if (a === null || a === undefined) { return 1; } if (b === null || b === undefined) { return -1; } return a - b; }, enableColumnFilter: false, enableSorting: !!userLocation }), columnHelper.display({ id: 'watch', header: () => Überwachen, cell: ({ row }) => { const store = row.original; const checked = watchedIds.has(String(store.id)); const disabled = store.isOpen; return (
handleToggleStore(store, event.target.checked)} disabled={disabled} title={ disabled ? 'Store ist bereits offen – Überwachung nicht verfügbar.' : undefined } />
); } }) ], [handleToggleStore, watchedIds, userLocation] ); const table = useReactTable({ data: tableData, columns, state: { sorting, columnFilters }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel() }); const loadRegions = useCallback(async () => { if (!authorizedFetch) { return; } setRegionLoading(true); setError(''); try { const response = await authorizedFetch('/api/store-watch/regions'); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); const normalized = Array.isArray(data.regions) ? data.regions : []; setRegions(normalized); setSelectedRegionId((prev) => { if (!prev) { return 'all'; } if (prev === 'all') { return prev; } const exists = normalized.some((region) => String(region.id) === String(prev)); if (!exists) { return normalized.length > 0 ? String(normalized[0].id) : 'all'; } return prev; }); } catch (err) { setError(`Regionen konnten nicht geladen werden: ${err.message}`); } finally { setRegionLoading(false); } }, [authorizedFetch]); const loadSubscriptions = useCallback(async () => { if (!authorizedFetch) { return; } setSubscriptionsLoading(true); setError(''); try { const response = await authorizedFetch('/api/store-watch/subscriptions'); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); const normalized = Array.isArray(data.stores) ? data.stores : []; setWatchList(normalized); setDirty(false); } catch (err) { setError(`Überwachte Betriebe konnten nicht geladen werden: ${err.message}`); } finally { setSubscriptionsLoading(false); } }, [authorizedFetch]); const fetchStoresForRegion = useCallback( async (regionId, { force, silent, forceStatus } = {}) => { if (!authorizedFetch || !regionId) { return; } if (!force && !forceStatus && storesByRegion[String(regionId)]) { return; } if (!silent) { setStoresLoading(true); } setError(''); try { const params = new URLSearchParams(); if (force) { params.append('force', '1'); } if (forceStatus) { params.append('forceStatus', '1'); } const qs = params.toString(); const endpoint = `/api/store-watch/regions/${regionId}/stores${qs ? `?${qs}` : ''}`; const response = await authorizedFetch(endpoint); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); setStoresByRegion((prev) => ({ ...prev, [String(regionId)]: { total: Number(data.total) || 0, stores: Array.isArray(data.stores) ? data.stores : [], fetchedAt: Date.now(), statusMeta: data.statusMeta || null } })); } catch (err) { setError(`Betriebe konnten nicht geladen werden: ${err.message}`); } finally { if (!silent) { setStoresLoading(false); } } }, [authorizedFetch, storesByRegion] ); const fetchAllRegions = useCallback( async ({ force, forceStatus } = {}) => { if (!authorizedFetch || regions.length === 0) { return; } const targets = regions.filter( (region) => force || forceStatus || !storesByRegion[String(region.id)] ); if (targets.length === 0) { return; } setStoresLoading(true); setError(''); try { for (const region of targets) { await fetchStoresForRegion(region.id, { force, silent: true, forceStatus }); } } catch (err) { setError(`Betriebe konnten nicht geladen werden: ${err.message}`); } finally { setStoresLoading(false); } }, [authorizedFetch, regions, storesByRegion, fetchStoresForRegion] ); useEffect(() => { loadRegions(); loadSubscriptions(); }, [loadRegions, loadSubscriptions]); useEffect(() => { if (!selectedRegionId) { return; } if (selectedRegionId === 'all') { fetchAllRegions({ force: false }); } else { fetchStoresForRegion(selectedRegionId); } }, [selectedRegionId, fetchStoresForRegion, fetchAllRegions]); const handleSave = useCallback(async () => { if (!authorizedFetch || saving || !dirty) { return; } setSaving(true); setStatus(''); setError(''); try { const response = await authorizedFetch('/api/store-watch/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ stores: watchList }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); setWatchList(Array.isArray(data.stores) ? data.stores : []); setDirty(false); setStatus('Überwachung gespeichert.'); setTimeout(() => setStatus(''), 4000); } catch (err) { setError(`Speichern fehlgeschlagen: ${err.message}`); } finally { setSaving(false); } }, [authorizedFetch, dirty, saving, watchList]); const handleReset = useCallback(() => { loadSubscriptions(); }, [loadSubscriptions]); const handleStatusRefresh = useCallback(() => { if (selectedRegionId === 'all') { fetchAllRegions({ force: true, forceStatus: true }); } else if (selectedRegionId) { fetchStoresForRegion(selectedRegionId, { forceStatus: true }); } }, [selectedRegionId, fetchAllRegions, fetchStoresForRegion]); const handleAdhocWatchCheck = useCallback(async () => { if (!authorizedFetch || adhocChecking || watchList.length === 0) { return; } setAdhocChecking(true); setError(''); try { const response = await authorizedFetch('/api/store-watch/check', { method: 'POST' }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); const summary = Array.isArray(data.stores) ? data.stores : []; setLastAdhocCheck({ checkedAt: Date.now(), stores: summary }); setStatus('Ad-hoc-Prüfung abgeschlossen. Zusammenfassung versendet.'); setTimeout(() => setStatus(''), 4000); await loadSubscriptions(); } catch (err) { setError(`Ad-hoc-Prüfung fehlgeschlagen: ${err.message}`); } finally { setAdhocChecking(false); } }, [authorizedFetch, adhocChecking, watchList.length, loadSubscriptions]); const panelTitles = { stores: storesPanelTitle, watch: `Überwachte Betriebe (${watchList.length})` }; const renderPanelRightContent = (panelId) => { if (panelId === 'stores') { if (!lastUpdatedAt) { return null; } return ( Aktualisiert: {new Date(lastUpdatedAt).toLocaleTimeString('de-DE')} ); } if (panelId === 'watch') { return (
); } return null; }; const renderPanelContent = (panelId) => { if (panelId === 'stores') { return ( <> {storesLoading &&

Lade Betriebe...

} {!storesLoading && table.getRowModel().rows.length === 0 && (

Keine Betriebe gefunden. Prüfe Filter oder sortiere anders.

)} {!storesLoading && table.getRowModel().rows.length > 0 && (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))} {table.getRowModel().rows.length === 0 && ( )}
{flexRender(header.column.columnDef.header, header.getContext())}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
Keine Betriebe entsprechen den aktuellen Filtern.
)} ); } if (panelId === 'watch') { return ( <> {subscriptionsLoading &&

Lade aktuelle Auswahl...

} {!subscriptionsLoading && watchList.length === 0 && (

Noch keine Betriebe ausgewählt.

)} {watchList.length > 0 && (
{watchList.map((entry) => (

{entry.storeName}

#{entry.storeId} — {entry.regionName || 'Region unbekannt'}

Letzter Status:{' '} {formatWatchStatusLabel(entry.lastTeamSearchStatus)}{' '} {entry.lastStatusCheckAt ? `(geprüft am ${new Date(entry.lastStatusCheckAt).toLocaleString('de-DE')})` : '(noch nicht geprüft)'}

))}
)} {lastAdhocCheck?.stores?.length > 0 && (

Letzte Ad-hoc-Prüfung:{' '} {new Date(lastAdhocCheck.checkedAt).toLocaleString('de-DE')}

)} ); } return null; }; if (!authorizedFetch) { return (

Keine Session aktiv.

); } return (

Betriebs-Monitoring

Wähle Betriebe aus, die bei offenem Team-Status automatisch gemeldet werden sollen.

{notificationProps?.loading && Lade…}
{notificationPanelOpen && notificationProps && ( )} {(error || status) && (
{error && (
{error}
)} {status && (
{status}
)}
)} {!userLocation && !locationLoading && (

Standortdaten für die Entfernungssortierung stammen automatisch aus deinem Foodsharing-Profil. Ergänze dort deine Koordinaten, falls sie fehlen.

)}
{isAdmin && (
)}
Es werden nur Regionen angezeigt, in denen du Mitglied mit Klassifikation 1 bist. {statusSummary}
{panelOrder.map((panelId) => { const collapsed = Boolean(normalizedPanelLayout.collapsed?.[panelId]); const title = panelTitles[panelId] || panelId; const panelIndex = panelOrder.indexOf(panelId); const canMoveUp = panelIndex > 0; const canMoveDown = panelIndex < panelOrder.length - 1; const rightContent = renderPanelRightContent(panelId); const showRightColumn = Boolean(rightContent) || (collapsed && panelOrder.length > 1); return (
{showRightColumn && (
{rightContent} {collapsed && panelOrder.length > 1 && (
Reihenfolge:
)}
)}
{!collapsed &&
{renderPanelContent(panelId)}
} {collapsed && (
Bereich ist eingeklappt. Über die Pfeile kann die Reihenfolge angepasst werden.
)}
); })} {dirty && (

Es gibt ungespeicherte Änderungen. Bitte "Speichern" klicken.

)}
); }; export default StoreWatchPage;