// ─── Zubehör (accessories) — inventory management + overview ─── // Data model per accessory: // { id, name, cat, qty, price (number|null → "zugehörig"), forEquipmentId (string|null), // note, defectQty } // Rental storage: rental.accessoryItems = [{ accessoryId, name, quantity, given, returned }] const { useState: useStateZ, useEffect: useEffectZ } = React; // Category list is now user-managed (starts empty). This map only supplies a nice // default emoji when a category name happens to match — new names fall back to 📦. const ACC_CAT_EMOJI = { 'Kabel': '🔌', 'Ständer': '🎚️', 'Adapter': '🔗', 'Taschen': '🎒', 'Batterien': '🔋', 'Sonstiges': '📦' }; // How many of an accessory are currently out (active/reserved rentals), and how many free. function accStock(acc, rentals = [], excludeRentalId = null) { const total = Math.max(0, Number(acc.qty) || 0); const defect = Math.max(0, Number(acc.defectQty) || 0); let out = 0; for (const r of rentals) { if (excludeRentalId && r.id === excludeRentalId) continue; if (r.status !== 'aktiv' && r.status !== 'reserviert') continue; for (const a of (r.accessoryItems || [])) { if (a.accessoryId === acc.id && !a.returned) out += Math.max(0, Number(a.quantity) || 0); } } const available = Math.max(0, total - defect - out); return { total, defect, out, available }; } if (typeof window !== 'undefined') window.accStock = accStock; // ─── Add / edit accessory sheet ─────────────────────────────── function AccessorySheet({ open, onClose, initial, onSave, equipment = [], accCats = [], setAccCats }) { const t = useTheme(); const blank = { id: '', name: '', cat: '', qty: 1, price: '', forEquipmentId: '', note: '', defectQty: 0, emoji: '', photo: '' }; const [form, setForm] = useStateZ(blank); const [newCat, setNewCat] = useStateZ(''); const [uploading, setUploading] = useStateZ(false); const [addingCat, setAddingCat] = useStateZ(false); useEffectZ(() => { if (open) { setForm(initial ? { ...blank, ...initial, price: initial.price == null ? '' : String(initial.price) } : blank); setAddingCat(false); setNewCat(''); } }, [open, initial]); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const valid = form.name.trim(); const commitNewCat = () => { const c = newCat.trim(); if (!c) { setAddingCat(false); return; } if (setAccCats && !accCats.includes(c)) setAccCats(prev => [...prev, c]); set('cat', c); setNewCat(''); setAddingCat(false); }; const save = () => { const priceNum = String(form.price).trim() === '' ? null : Math.max(0, Number(form.price) || 0); onSave({ ...form, id: form.id || 'acc-' + Date.now().toString(36), name: form.name.trim(), qty: Math.max(0, Number(form.qty) || 0), defectQty: Math.max(0, Number(form.defectQty) || 0), price: priceNum, forEquipmentId: form.forEquipmentId || null, }); onClose(); }; const inp = { width: '100%', boxSizing: 'border-box', padding: '11px 13px', borderRadius: 11, border: `0.5px solid ${t.inputBorder}`, background: t.inputBg, fontSize: 15, color: t.text, outline: 'none', fontFamily: 'inherit' }; const Stepper = ({ value, onChange, min = 0 }) => (
onChange(Math.max(min, (Number(value) || 0) - 1))} scale={0.85}>
{Number(value) || 0}
onChange((Number(value) || 0) + 1)} scale={0.85}>
); return (
{initial ? 'Zubehör bearbeiten' : 'Neues Zubehör'}
{/* Icon — photo or emoji, editable like equipment */}
{form.photo && ( set('photo', '')} scale={0.85} style={{ position: 'absolute', top: -6, right: -6 }}>
)}
{ set('emoji', e.target.value); if (e.target.value && form.photo) set('photo', ''); }} placeholder="oder Emoji / Buchstabe …" maxLength={8} style={{ ...inp, flex: 1, fontFamily: 'inherit' }}/> {form.emoji && ( set('emoji', '')} scale={0.85}>
Zurück
)}
set('name', e.target.value)} placeholder="z.B. XLR-Kabel 5m" style={inp} autoFocus/>
{accCats.map(c => { const on = form.cat === c; return ( set('cat', c)} scale={0.95}>
{c}
); })} {addingCat ? (
setNewCat(e.target.value)} autoFocus onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); commitNewCat(); } }} placeholder="Neue Kategorie" style={{ ...inp, width: 150, padding: '7px 11px', fontSize: 13 }}/>
) : ( { setAddingCat(true); setNewCat(''); }} scale={0.9}>
Kategorie
)}
{accCats.length === 0 && !addingCat &&
Noch keine Kategorien — mit „✎ Kategorie“ eine anlegen.
}
Anzahl Bestand
set('qty', v)} min={0}/>
Davon defekt
set('defectQty', v)} min={0}/>
set('price', e.target.value)} inputMode="decimal" placeholder="leer lassen → nicht einzeln verleihbar" style={inp}/>
{String(form.price).trim() === '' ? 'Ohne Preis: gehört als Zubehör zu Geräten, wird nicht einzeln berechnet.' : 'Mit Preis: kann auch eigenständig verliehen werden.'}
set('forEquipmentId', '')} scale={0.95}>
— keins —
{equipment.map(eq => { const on = form.forEquipmentId === eq.id; return ( set('forEquipmentId', eq.id)} scale={0.95}>
{eq.name}
); })}