import { createElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import GridLayout, { WidthProvider } from "react-grid-layout"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; import { api } from "../api"; import { useConfirm } from "../confirm"; import { useToast } from "../toast"; import Icon from "../components/Icon"; import { KARTEN, KARTEN_TYPEN } from "../dashboard/cards"; const Grid = WidthProvider(GridLayout); export default function Dashboard() { const { t } = useTranslation(); const confirm = useConfirm(); const toast = useToast(); const [items, setItems] = useState(null); const [edit, setEdit] = useState(false); const [addOpen, setAddOpen] = useState(false); function normalize(it) { const def = KARTEN[it.type] || {}; return { i: it.i || it.type, minW: def.minW || 2, minH: def.minH || 2, ...it }; } async function load() { try { const res = await api.dashboardLayout(); setItems(res.layout.filter((it) => KARTEN[it.type]).map(normalize)); } catch (e) { toast(e.message, "error"); setItems([]); } } useEffect(() => { load(); }, []); function merge(layout) { const byId = Object.fromEntries((items || []).map((it) => [it.i, it])); return layout.map((l) => ({ ...byId[l.i], x: l.x, y: l.y, w: l.w, h: l.h })); } async function persist(next) { setItems(next); try { await api.saveDashboardLayout(next.map((it) => ({ i: it.i, type: it.type, x: it.x, y: it.y, w: it.w, h: it.h, props: it.props, }))); } catch (e) { toast(e.message, "error"); } } // Eine Karten-Option (z. B. Aufschlüsselung Gruppen/Kategorien) setzen + sichern. function setCardProp(i, key, value) { persist(items.map((it) => (it.i === i ? { ...it, props: { ...(it.props || {}), [key]: value } } : it))); } function addCard(type) { const def = KARTEN[type]; const maxY = items.reduce((m, it) => Math.max(m, it.y + it.h), 0); setAddOpen(false); persist([...items, normalize({ i: type, type, x: 0, y: maxY, w: def.w, h: def.h })]); } function removeCard(i) { persist(items.filter((it) => it.i !== i)); } async function reset() { if (!(await confirm({ title: t("dashboard.reset"), message: t("dashboard.resetConfirm"), confirmLabel: t("dashboard.reset") }))) return; try { const res = await api.resetDashboardLayout(); setItems(res.layout.filter((it) => KARTEN[it.type]).map(normalize)); setEdit(false); } catch (e) { toast(e.message, "error"); } } if (!items) return
{t("common.loading")}
; const present = new Set(items.map((it) => it.type)); const missing = KARTEN_TYPEN.filter((tp) => !present.has(tp)); return (

{t("dashboard.title")}

{edit && missing.length > 0 && (
{addOpen && (
{missing.map((tp) => ( ))}
)}
)} {edit && ( )}
{items.length === 0 ? (

{t("dashboard.emptyCards")}

) : ( setItems(merge(l))} onDragStop={(l) => persist(merge(l))} onResizeStop={(l) => persist(merge(l))} useCSSTransforms > {items.map((it) => { const def = KARTEN[it.type]; return (
{def ? t(def.titleKey) : it.type} {edit && ( )}
{edit && def?.options?.length > 0 && (
{def.options.map((opt) => { const cur = it.props?.[opt.key] ?? opt.def; return (
{t(opt.labelKey)}
{opt.choices.map((ch) => ( ))}
); })}
)} {def ? createElement(def.Comp, { key: JSON.stringify(it.props || {}), props: it.props }) : null}
); })}
)}
); }