// Equipment management screen — list / add / edit / delete const { useState, useEffect, useRef } = React; // ─── Row: tap → preview, long-press → menu ─── // Grapheme-aware: returns the last visible character (keeps multi-codepoint emoji intact). function lastGrapheme(str) { const s = String(str || ''); if (!s) return ''; try { if (typeof Intl !== 'undefined' && Intl.Segmenter) { const segs = [...new Intl.Segmenter().segment(s)]; return segs.length ? segs[segs.length - 1].segment : ''; } } catch (e) {} const cp = [...s]; return cp[cp.length - 1] || ''; } function EqRow({ item, t, rentals, onOpen, onEdit, onDelete, view = 'card', showIcons = true }) { const startX = React.useRef(0); const startY = React.useRef(0); const moved = React.useRef(false); const longTimer = React.useRef(null); const longFired = React.useRef(false); const rowRef = React.useRef(null); const [menu, setMenu] = React.useState(null); // { x, y } | null (long-press) const [dd, setDd] = React.useState(false); // three-dot dropdown const { total, verfuegbar: verf } = eqStock(item, rentals); const low = verf === 0 || (total > 0 && verf <= Math.max(1, Math.floor(total * 0.2))); const cancelLong = () => { if (longTimer.current) { clearTimeout(longTimer.current); longTimer.current = null; } }; const onPointerDown = (ev) => { if (ev.pointerType === 'mouse' && ev.button !== 0) return; startX.current = ev.clientX; startY.current = ev.clientY; moved.current = false; longFired.current = false; cancelLong(); longTimer.current = setTimeout(() => { longFired.current = true; const rect = rowRef.current && rowRef.current.getBoundingClientRect(); if (rect) setMenu({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }); }, 480); try { ev.currentTarget.setPointerCapture(ev.pointerId); } catch {} }; const onPointerMove = (ev) => { const adx = ev.clientX - startX.current; const ady = ev.clientY - startY.current; if (!moved.current && (Math.abs(adx) > 5 || Math.abs(ady) > 5)) { moved.current = true; cancelLong(); } }; const settle = () => { cancelLong(); if (longFired.current) return; // menu shown, no tap if (moved.current) return; // user scrolled, no tap onOpen(); }; const compact = view === 'compact' || view === 'table'; return (
{showIcons && } {compact ? ( <>
{item.name}
{item.cat}
{verf}/{total}
frei
) : ( <>
{item.name}
{item.cat}
{(item.accessories || []).length > 0 && (
+ {item.accessories.length} Zubehör
)} {(item.maint || []).filter(m => !m.done).map(m => { const mm = maintMeta(m.type); const label = m.type === 'tuev' ? `TÜV ${fmtDateDE(m.date).slice(0, 6)}` : mm.label; return (
{mm.icon} {label}
); })}
{verf}/{total}
verfügbar
e.stopPropagation()} onPointerUp={(e) => e.stopPropagation()} onClick={(e) => { e.stopPropagation(); setDd(m => !m); }} style={{ width: 28, height: 28, borderRadius: 14, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0 }}>
)}
{dd && ( <>
setDd(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }}/>
{ setDd(false); onEdit(); }} scale={0.98}>
Bearbeiten
{ setDd(false); onDelete(); }} scale={0.98}>
Löschen
)} {menu && setMenu(null)} onEdit={() => { setMenu(null); onEdit(); }} onDelete={() => { setMenu(null); onDelete(); }}/>}
); } // Floating two-action menu shown after a long-press on a row. function EqContextMenu({ pos, t, onClose, onEdit, onDelete }) { // Clamp menu within viewport. const W = 220, H = 110; const vw = typeof window !== 'undefined' ? window.innerWidth : 360; const vh = typeof window !== 'undefined' ? window.innerHeight : 720; const left = Math.max(12, Math.min(vw - W - 12, pos.x - W / 2)); const top = Math.max(12, Math.min(vh - H - 12, pos.y - H / 2)); return (
e.stopPropagation()} style={{ position: 'fixed', inset: 0, zIndex: 200, background: 'rgba(0,0,0,0.25)', backdropFilter: 'blur(2px)', WebkitBackdropFilter: 'blur(2px)' }}/>
Bearbeiten
Löschen
); } // ─── Read-only preview sheet — opens when tapping a row ─── function EqPreviewSheet({ open, onClose, item, rentals, setEquipment, onEdit, onDelete }) { const t = useTheme(); if (!item) return
; const { total, verfuegbar: verf, vermietet, reparatur } = eqStock(item, rentals); const patch = (changes) => setEquipment(prev => prev.map(p => p.id === item.id ? { ...p, ...changes } : p)); const setQty = (q) => { const next = Math.max(1, Number(q) || 1); // Don't let qty drop below what's currently rented out const min = vermietet + reparatur; patch({ qty: Math.max(min, next) }); }; const setPrice = (v) => patch({ price: v === '' ? '' : Math.max(0, Number(v) || 0) }); const maxRepair = Math.max(0, total - vermietet); const setRepair = (n) => patch({ repairQty: Math.max(0, Math.min(maxRepair, Number(n) || 0)) }); const Cell = ({ label, value, strong, accent }) => (
{value}
{label}
); const StepBtn = ({ sign, onClick, disabled }) => ( !disabled && onClick()} scale={0.85} disabled={disabled}>
0 ? t.solidBg : t.chip, display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: disabled ? 0.4 : 1 }}> 0 ? 'M12 5v14M5 12h14' : 'M5 12h14'} color={sign > 0 ? t.solidFg : t.text} size={15} sw={2.4}/>
); return (
{/* Hero */}
{item.name}
{item.cat}{item.sub ? ' · ' + item.sub : ''}{item.kind ? ` · ${item.kind}` : ''}
{/* Verfügbarkeit — Übersicht */}
{/* Schnell anpassen — Tagespreis · Anzahl · Reparatur ohne vollen Bearbeiten-Modus */}
{/* Schnell anpassen — als Liste (Label links, Stepper rechts) */}
Anzahl im Inventar
setQty((item.qty || 1) - 1)}/>
{item.qty || 1}
setQty((item.qty || 1) + 1)}/>
Für Reparatur
setRepair(reparatur - 1)}/>
{reparatur}
= maxRepair} onClick={() => setRepair(reparatur + 1)}/>
Tagespreis €
setPrice((Number(item.price) || 0) - 5)}/> setPrice(e.target.value)} onBlur={(e) => setPrice(String(Math.max(0, Number(e.target.value) || 0)))} style={{ width: 64, textAlign: 'center', padding: '6px 4px', borderRadius: 10, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 17, fontWeight: 700, color: t.text, fontVariantNumeric: 'tabular-nums', outline: 'none', fontFamily: 'inherit', MozAppearance: 'textfield' }}/> setPrice((Number(item.price) || 0) + 5)}/>
{/* Wartung — direkt editierbar */}
Wartung & Reparatur
patch({ maint: v })}/>
{/* Zubehör — read only */} {(item.accessories || []).length > 0 && (
Zubehör · gehört dazu
{item.accessories.map(s => (
{s}
))}
)} {/* Actions */}
Bearbeiten
Löschen
); } const EQ_KIND_OPTIONS = ['Tontechnik', 'Anhänger', 'Kabel']; // Suggest accessories based on equipment type / name keywords. Returns deduped list of strings. function suggestAccessories(eq) { if (!eq) return []; const key = ((eq.name || '') + ' ' + (eq.cat || '') + ' ' + (eq.kind || '')).toLowerCase(); const out = []; if (/mikr|mic|sm5|ew\d|sennheis|shure|funkmik|ges/.test(key)) { out.push('XLR-Kabel 5m', 'Mikrofonständer'); } if (/sm5|gesang/.test(key)) out.push('Popschutz'); if (/funk/.test(key)) out.push('Ersatz-Batterien (AA ×4)'); if (/lautsprech|aktivlaut|jbl|prx|eon|pa|box/.test(key)) { out.push('Boxenständer', 'Stromkabel 5m', 'Speakon-Kabel'); } if (/misch|mixer|mg10|yamaha|pult/.test(key)) { out.push('USB-Kabel', 'Klinke-Klinke 3m'); } if (/anhäng|trailer/.test(key)) { out.push('Spanngurte (4×)', 'Sicherungsnetz', 'Adapter 7→13-polig'); } if (/kabel|verläng|trommel/.test(key)) out.push('Kabeltrommel 25m'); return [...new Set(out)]; } // ─── Utilization stats card (Auslastung) ────────────────────── // ─── Top Performance card — % of total rentals per item ────── // Replaces the old generic utilization chart. Shows which products // drive the most rentals as a clear percentage of total bookings. function UtilizationCard({ equipment, onSelect }) { const t = useTheme(); const [open, setOpen] = useState(false); if (equipment.length === 0) return null; const totalUses = equipment.reduce((s, e) => s + (Number(e.uses) || 0), 0) || 1; const ranked = [...equipment] .map(e => ({ ...e, pct: Math.round(((Number(e.uses) || 0) / totalUses) * 100) })) .sort((a, b) => b.pct - a.pct); const topPct = ranked[0] ? ranked[0].pct : 0; const visible = open ? ranked : ranked.slice(0, 3); const barColor = (p) => p >= 25 ? t.green : p >= 12 ? t.accent : p >= 5 ? t.orange : t.textTer; const Bar = ({ pct, color }) => (
); return (
setOpen(o => !o)} scale={0.99}>
Beliebteste Produkte
Spitzenreiter:
{ranked[0] ? ranked[0].name : '—'}
{topPct}%
{visible.map((e, i) => ( onSelect && onSelect(e)} scale={0.99}>
#{i + 1}
{e.name}
{e.pct}%
{e.uses || 0} ×
))}
{!open && ranked.length > 3 && ( setOpen(true)} scale={0.99} style={{ marginTop: 10 }}>
Alle {ranked.length} Geräte anzeigen ›
)}
); } // ─── Accessory editor (used inside the equipment sheet) ─── function AccessoryEditor({ value = [], onChange, suggestions = [] }) { const t = useTheme(); const [text, setText] = useState(''); const items = value || []; const open = suggestions.filter(s => !items.includes(s)); const add = (v) => { const s = (v || text).trim(); if (!s) return; if (items.includes(s)) { setText(''); return; } onChange([...items, s]); setText(''); }; const remove = (s) => onChange(items.filter(x => x !== s)); const addAll = () => onChange([...items, ...open]); const inpStyle = { flex: 1, minWidth: 0, width: 0, padding: '10px 12px', borderRadius: 10, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 14, color: t.text, outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box', }; return (
{/* Selected accessories */} {items.length > 0 && (
{items.map(s => (
{s} remove(s)} scale={0.85}>
))}
)} {/* Add custom */}
setText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); add(); } }} placeholder="z.B. DI-Box, Adapter, Trolley…" style={inpStyle}/> add()} scale={0.94}>
+ Zubehör
{/* Suggestions */} {open.length > 0 && (
💡 Auto-Vorschläge
Alle übernehmen
{open.map(s => ( add(s)} scale={0.95}>
+ {s}
))}
)}
); } // ─── Maintenance / repair editor (used inside the equipment sheet) ─── function MaintEditor({ value = [], onChange }) { const t = useTheme(); const [adding, setAdding] = useState(false); const blank = { type: 'check', title: '', date: todayISO(), note: '' }; const [draft, setDraft] = useState(blank); const dInp = { width: '100%', padding: '10px 12px', borderRadius: 10, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 14, color: t.text, outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box', }; const add = () => { if (!draft.title.trim()) return; onChange([...value, { ...draft, id: 'm-' + Date.now(), done: false }]); setDraft(blank); setAdding(false); }; const toggle = (id) => onChange(value.map(m => m.id === id ? { ...m, done: !m.done } : m)); const remove = (id) => onChange(value.filter(m => m.id !== id)); return (
{value.map(m => { const mm = maintMeta(m.type); return (
toggle(m.id)} scale={0.85}>
{m.done && }
{mm.icon} {m.title}
{mm.label} · {fmtDateDE(m.date)}{m.note ? ' · ' + m.note : ''}
remove(m.id)} scale={0.8}>
); })}
{adding ? (
{MAINT_TYPES.map(ty => { const mm = maintMeta(ty); const sel = draft.type === ty; return ( setDraft(d => ({ ...d, type: ty }))} scale={0.94}>
{mm.icon} {mm.label}
); })}
setDraft(d => ({ ...d, title: e.target.value }))} placeholder="z.B. Nächster TÜV / HU" style={dInp}/>
setDraft(d => ({ ...d, date: e.target.value }))} style={dInp}/> setDraft(d => ({ ...d, note: e.target.value }))} placeholder="Notiz" style={dInp}/>
Hinzufügen
{ setDraft(blank); setAdding(false); }} scale={0.96}>
Abbrechen
) : ( setAdding(true)} scale={0.98} style={{ marginTop: 8 }}>
Wartung / Defekt / TÜV erfassen
)}
); } // ─── Edit / Add equipment sheet ───────────────────────────── function EquipmentSheet({ open, onClose, initial, onSave, categories, setCategories, rentals = [] }) { const t = useTheme(); const isEdit = !!initial; const blank = { id: '', name: '', cat: '', sub: '', kind: '', repairQty: 0, price: 0, qty: 1, uses: 0, ic: 'speaker', emoji: '', photo: '', until: '', maint: [], accessories: [] }; const [form, setForm] = useState(blank); const [uploading, setUploading] = useState(false); const [newCat, setNewCat] = useState(''); const [addingCat, setAddingCat] = useState(false); const fileRef = useRef(null); useEffect(() => { if (open) { setForm(initial ? { ...blank, ...initial } : blank); setAddingCat(false); setNewCat(''); } }, [open, initial]); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const commitNewCat = () => { const c = newCat.trim(); if (!c) { setAddingCat(false); return; } if (setCategories && !categories.includes(c)) setCategories(prev => [...prev, c]); set('kind', c); setNewCat(''); setAddingCat(false); }; const valid = form.name.trim() && form.cat.trim(); // Reusable +/- stepper (matches the Reparatur stepper). `editable` lets the // user tap the number and type an exact value, still nudgeable with +/-. const Stepper = ({ value, onChange, min = 0, step = 1, editable = false, suffix = '' }) => { const num = Number(value) || 0; const Btn = ({ sign, disabled }) => ( !disabled && onChange(String(Math.max(min, num + sign * step)))} scale={0.85} disabled={disabled}>
0 ? t.solidBg : t.chip, display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: disabled ? 0.4 : 1, }}> 0 ? 'M12 5v14M5 12h14' : 'M5 12h14'} color={sign > 0 ? t.solidFg : t.text} size={15} sw={2.4}/>
); return (
{editable ? ( onChange(e.target.value)} onBlur={(e) => onChange(String(Math.max(min, Number(e.target.value) || 0)))} style={{ width: 64, textAlign: 'center', padding: '7px 4px', borderRadius: 10, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 17, fontWeight: 700, color: t.text, fontVariantNumeric: 'tabular-nums', outline: 'none', fontFamily: 'inherit', MozAppearance: 'textfield', }}/> ) : (
{num}{suffix}
)}
); }; const save = () => { if (!valid) return; const out = { ...form, id: form.id || ('eq-' + Date.now()), price: Number(form.price) || 0, qty: Math.max(1, Number(form.qty) || 1), repairQty: Math.max(0, Math.min(Math.max(1, Number(form.qty) || 1), Number(form.repairQty) || 0)), uses: Number(form.uses) || 0, }; onSave(out); onClose(); }; return (
{isEdit ? 'Equipment bearbeiten' : 'Neues Equipment'}
{isEdit ? 'Felder anpassen' : 'Lege ein neues Mietobjekt an.'}
Abbrechen
{/* Icon — Foto oder Emoji, kompakt ganz oben (wie Zubehör) */}
{form.photo && ( set('photo', '')} scale={0.85} style={{ position: 'absolute', top: -6, right: -6 }}>
)}
{ const file = e.target.files && e.target.files[0]; if (!file) return; setUploading(true); try { const url = await compressImage(file); setForm(f => ({ ...f, photo: url })); } catch { /* ignore */ } setUploading(false); e.target.value = ''; }}/> fileRef.current && fileRef.current.click()} scale={0.97}>
{uploading ? 'Lädt…' : (form.photo ? 'Foto ändern' : 'Foto hochladen')}
setForm(f => ({ ...f, emoji: e.target.value, photo: e.target.value ? '' : f.photo }))} placeholder="oder Emoji / Buchstabe …" maxLength={8} style={{ ...inp(t), flex: 1, fontFamily: 'inherit' }}/> {form.emoji && ( setForm(f => ({ ...f, emoji: '' }))} scale={0.85}>
Zurück
)}
set('name', e.target.value)} placeholder="z.B. JBL PRX-815" style={inp(t)}/> set('cat', e.target.value)} placeholder="z.B. Aktivlautsprecher" style={inp(t)}/>
{categories.map(k => { const on = form.kind === k; return ( set('kind', k)} scale={0.95}>
{k}
); })} {addingCat ? (
setNewCat(e.target.value)} autoFocus onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); commitNewCat(); } }} placeholder="Neue Kategorie" style={{ ...inp(t), width: 150, padding: '7px 11px', fontSize: 13 }}/>
) : ( { setAddingCat(true); setNewCat(''); }} scale={0.9}>
Kategorie
)}
{categories.length === 0 && !addingCat && null}
Tagespreis €
set('price', v)} min={0} step={5} editable/>
Anzahl
set('qty', v)} min={1} step={1} editable/>
Einsätze
set('uses', v)} min={0} step={1} editable/>
In Reparatur
set('repairQty', Math.min(Number(v) || 0, Number(form.qty) || 1))} min={0} step={1} editable/>
{/* Verfügbarkeit & Reparatur werden in der Detail-/Info-Ansicht gepflegt, nicht im Bearbeiten-Modus. */} set('maint', v)}/> set('accessories', v)} suggestions={suggestAccessories(form)}/>
{isEdit ? 'Änderungen speichern' : 'Equipment hinzufügen'}
); } const inp = (t) => ({ width: '100%', padding: '12px 14px', borderRadius: 12, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 15, color: t.text, outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box', }); function FormGroup({ label, children, required, tight }) { const t = useTheme(); return (
{label}{required && *}
{children}
); } // ─── Categories sheet (re-implemented for this tab) ───────── function CategoriesSheet({ open, onClose, categories, setCategories, toast, equipment }) { const t = useTheme(); const [newName, setNewName] = useState(''); const [editingIdx, setEditingIdx] = useState(-1); const [editName, setEditName] = useState(''); const add = () => { const v = newName.trim(); if (!v) return; if (categories.includes(v)) { toast('Kategorie existiert bereits'); return; } setCategories([...categories, v]); setNewName(''); toast(`„${v}“ hinzugefügt`); }; const remove = (idx) => { const v = categories[idx]; setCategories(categories.filter((_, i) => i !== idx)); toast(`„${v}“ entfernt`); }; const startEdit = (idx) => { setEditingIdx(idx); setEditName(categories[idx]); }; const commitEdit = () => { const v = editName.trim(); if (!v || editingIdx < 0) { setEditingIdx(-1); return; } if (v === categories[editingIdx]) { setEditingIdx(-1); return; } const next = categories.slice(); next[editingIdx] = v; setCategories(next); setEditingIdx(-1); toast('Umbenannt'); }; return (
Kategorien
Hinzufügen, umbenennen, löschen.
Fertig
setNewName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') add(); }} placeholder="Neue Kategorie…" style={inp(t)}/>
+ Neu
{categories.map((c, i) => (
{editingIdx === i ? ( setEditName(e.target.value)} onBlur={commitEdit} onKeyDown={e => { if (e.key === 'Enter') commitEdit(); if (e.key === 'Escape') setEditingIdx(-1); }} style={{ flex: 1, padding: '8px 10px', borderRadius: 8, border: `1px solid ${t.accent}`, background: t.card, fontSize: 15, color: t.text, outline: 'none', fontFamily: 'inherit' }}/> ) : ( startEdit(i)} scale={0.98} style={{ flex: 1 }}>
{c}
)}
{equipment.filter(e => e.kind === c).length}
remove(i)} scale={0.85}>
))}
Tipp: Auf einen Namen tippen, um umzubenennen.
); } // ─── Main Equipment screen ───────────────────────────────── function ScreenEquipment({ equipment, setEquipment, accessories = [], setAccessories, accCats = [], setAccCats, categories, setCategories, rentals = [], setRentals, logistik, setLogistik, tabSettings = {}, setTabSettings, initialArea = 'equipment', toast, go }) { const t = useTheme(); const [area, setArea] = useState(initialArea); const [zubNonce, setZubNonce] = useState(0); useEffect(() => { setArea(initialArea); }, [initialArea]); const [filter, setFilter] = useState('Alle'); const [editing, setEditing] = useState(null); // equipment object or null const [sheetOpen, setSheetOpen] = useState(false); const [previewItem, setPreviewItem] = useState(null); const [previewOpen, setPreviewOpen] = useState(false); const [catsOpen, setCatsOpen] = useState(false); const [confirmId, setConfirmId] = useState(null); const [logistikSettingsOpen, setLogistikSettingsOpen] = useState(false); useEffect(() => { if (filter !== 'Alle' && !categories.includes(filter)) setFilter('Alle'); }, [categories, filter]); const filters = ['Alle', ...categories]; const filtered = filter === 'Alle' ? equipment : equipment.filter(e => e.kind === filter); const openNew = () => { setEditing(null); setSheetOpen(true); }; const openEdit = (eq) => { setEditing(eq); setSheetOpen(true); }; const openPreview = (eq) => { setPreviewItem(eq); setPreviewOpen(true); }; const editFromPreview = () => { setPreviewOpen(false); // Tiny delay so the preview sheet visually closes before the edit sheet appears. setTimeout(() => { setEditing(previewItem); setSheetOpen(true); }, 180); }; // Live-sync preview with latest equipment state (Anzahl/Wartung edits in preview). const livePreviewItem = previewItem ? (equipment.find(p => p.id === previewItem.id) || previewItem) : null; const onSave = (eq) => { setEquipment(prev => { const exists = prev.find(p => p.id === eq.id); if (exists) return prev.map(p => p.id === eq.id ? eq : p); return [eq, ...prev]; }); toast(editing ? 'Gespeichert' : `„${eq.name}“ hinzugefügt`); }; const doDelete = () => { const e = equipment.find(x => x.id === confirmId); setEquipment(prev => prev.filter(p => p.id !== confirmId)); toast(`„${e ? e.name : 'Equipment'}“ gelöscht`); setConfirmId(null); }; return (
{area === 'equipment' ? 'Equipment' : area === 'zubehoer' ? 'Zubehör' : 'Logistik'}
{area === 'equipment' ? (
) : area === 'zubehoer' ? ( setZubNonce(n => n + 1)} scale={0.9}>
) : null}
{/* Sub-area switch — underline tabs (AudioFlow-Stil) */}
{[ ['equipment', 'Equipment', 'M3 7l9-4 9 4v10l-9 4-9-4V7zM3 7l9 4 9-4M12 11v10'], ['zubehoer', 'Zubehör', 'M20.5 7.3l-8-4.5a1 1 0 00-1 0l-8 4.5A1 1 0 003 8.2v7.6a1 1 0 00.5.9l8 4.5a1 1 0 001 0l8-4.5a1 1 0 00.5-.9V8.2a1 1 0 00-.5-.9zM3.3 7.9l8.7 4.9 8.7-4.9M12 22v-9.2'], ['logistik', 'Logistik', 'M1 3h13v10H1zM14 8h4l3 3v2h-7zM5.5 18.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM19.5 18.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z'], ].map(([id, label, d]) => { const on = area === id; return ( setArea(id)} scale={0.97}>
{label}
); })}
{area === 'zubehoer' && ( )} {area === 'logistik' && ( setLogistikSettingsOpen(false)} /> )} {area === 'equipment' && <> {/* Utilization stats */} {/* Filter chips */}
{filters.map(label => ( setFilter(label)} scale={0.94}>
{label}
))}
{/* List */} {(() => { const eqCfg = tabSettings.equipment || {}; const eqView = eqCfg.view || 'card'; const eqIcons = eqCfg.showIcons !== false; const setEqCfg = (patch) => setTabSettings && setTabSettings((prev) => ({ ...prev, equipment: { ...(prev.equipment || {}), ...patch } })); const VIEWS = [['card', 'Karten'], ['compact', 'Kompakt'], ['table', 'Tabelle']]; return ( <>
{filtered.length === 0 ? (
Keine Objekte.
+ Erstes Objekt anlegen
) : filter === 'Alle' ? ( (() => { const groups = {}; for (const e of filtered) { const k = e.kind || 'Ohne Kategorie'; (groups[k] = groups[k] || []).push(e); } const groupOrder = (() => { const usedKinds = [...new Set(filtered.map(e => e.kind).filter(Boolean))]; const ordered = [...categories.filter(c => groups[c]), ...usedKinds.filter(k => !categories.includes(k) && groups[k])]; return [...ordered, ...(groups['Ohne Kategorie'] ? ['Ohne Kategorie'] : [])]; })(); return groupOrder.map(cat => (
{cat}
{groups[cat].map(e => ( openPreview(e)} onEdit={() => openEdit(e)} onDelete={() => setConfirmId(e.id)}/> ))}
)); })() ) : ( filtered.map(e => ( openPreview(e)} onEdit={() => openEdit(e)} onDelete={() => setConfirmId(e.id)}/> )) )}
); })()} } setPreviewOpen(false)} item={livePreviewItem} rentals={rentals} setEquipment={setEquipment} onEdit={editFromPreview} onDelete={() => { setPreviewOpen(false); if (livePreviewItem) setConfirmId(livePreviewItem.id); }}/> setSheetOpen(false)} initial={editing} onSave={onSave} categories={categories} setCategories={setCategories} rentals={rentals}/> setCatsOpen(false)} categories={categories} setCategories={setCategories} toast={toast} equipment={equipment}/> setConfirmId(null)} onConfirm={doDelete}/>
); } Object.assign(window, { ScreenEquipment });