/dashboard/by-category bekommt depth: rollt jede Artikel-Kategorie auf ihren Vorfahren der gewuenschten Stufe hoch (1 = oberste Ebene), ohne depth zaehlt jede zugeordnete Kategorie einzeln (feinste). Neue Karten-Konfig select Kategorie-Tiefe (Feinste / oberste / bis 2. / bis 3. Stufe). KartenConfig zeigt jetzt auch bei select den Hinweis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3.4 KiB
JavaScript
94 lines
3.4 KiB
JavaScript
import Icon from "../components/Icon";
|
||
import { ZEITRAEUME } from "./cards";
|
||
import { locationOptions } from "../locationPath";
|
||
|
||
/**
|
||
* Konfig-Dialog einer Karte – wie in HomeAssistant: ein eigener Dialog mit den
|
||
* Optionen der Karte, statt Dropdowns im Kartenkopf. Welche Felder erscheinen,
|
||
* bestimmt `karte.config`. Die Werte landen in den Karten-Props (bzw. `tage`
|
||
* beim Zeitraum) und werden mit dem Layout gespeichert.
|
||
*/
|
||
export default function KartenConfig({
|
||
karte, props: karteProps = {}, tage,
|
||
produkte = [], locations = [], onProps, onTage, onClose,
|
||
}) {
|
||
const felder = karte?.config || [];
|
||
|
||
function feld(f) {
|
||
if (f.type === "product") {
|
||
return (
|
||
<label key={f.key}>
|
||
{f.label}
|
||
<select value={karteProps[f.key] ?? ""}
|
||
onChange={(e) => onProps({ [f.key]: e.target.value ? Number(e.target.value) : undefined })}>
|
||
<option value="">– Artikel wählen –</option>
|
||
{produkte.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||
</select>
|
||
</label>
|
||
);
|
||
}
|
||
if (f.type === "location") {
|
||
return (
|
||
<label key={f.key}>
|
||
{f.label}{f.hint ? <span className="muted small"> ({f.hint})</span> : null}
|
||
<select value={karteProps[f.key] ?? ""}
|
||
onChange={(e) => onProps({ [f.key]: e.target.value || undefined })}>
|
||
<option value="">– alle Orte –</option>
|
||
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||
</select>
|
||
</label>
|
||
);
|
||
}
|
||
if (f.type === "zeitraum") {
|
||
return (
|
||
<label key="zeitraum">
|
||
{f.label}
|
||
<select value={tage ?? karte.zeitraum} onChange={(e) => onTage(Number(e.target.value))}>
|
||
{ZEITRAEUME.map((z) => <option key={z.tage} value={z.tage}>{z.label}</option>)}
|
||
</select>
|
||
</label>
|
||
);
|
||
}
|
||
if (f.type === "select") {
|
||
return (
|
||
<label key={f.key}>
|
||
{f.label}{f.hint ? <span className="muted small"> ({f.hint})</span> : null}
|
||
<select value={karteProps[f.key] ?? f.default ?? ""}
|
||
onChange={(e) => onProps({ [f.key]: e.target.value })}>
|
||
{(f.optionen || []).map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
|
||
</select>
|
||
</label>
|
||
);
|
||
}
|
||
if (f.type === "boolean") {
|
||
return (
|
||
<label key={f.key} className="check-inline">
|
||
<input type="checkbox" checked={karteProps[f.key] ?? f.default ?? false}
|
||
onChange={(e) => onProps({ [f.key]: e.target.checked })} />
|
||
<span>{f.label}</span>
|
||
</label>
|
||
);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
|
||
<div className="modal" role="dialog" aria-modal="true">
|
||
<div className="modal-head">
|
||
<Icon name="settings" size={18} />
|
||
<h2>{karte?.titel} einstellen</h2>
|
||
</div>
|
||
<div className="modal-body" style={{ display: "grid", gap: "var(--sp-3)" }}>
|
||
{felder.length === 0
|
||
? <p className="muted">Diese Karte hat keine Einstellungen.</p>
|
||
: felder.map(feld)}
|
||
</div>
|
||
<div className="btn-pair">
|
||
<button type="button" className="btn primary" onClick={onClose}>Fertig</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|