Files
dickendock/frontend/src/tools/bestellungen.jsx

504 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useMemo } from 'react';
import { api, S } from '../lib.js';
import { SearchIcon, PlusIcon, XIcon, EditIcon, TrashIcon, ChevronIcon } from '../icons.jsx';
const STATUS = {
warteliste: { label:'Warteliste', color:'#ffe66d', bg:'rgba(255,230,109,0.12)' },
in_arbeit: { label:'In Arbeit', color:'#4ecdc4', bg:'rgba(78,205,196,0.12)' },
fertig: { label:'Fertig', color:'#6bcb77', bg:'rgba(107,203,119,0.12)' },
};
const STATUS_KEYS = ['warteliste','in_arbeit','fertig'];
const PREISE = [
{ key:'freundschaft', emoji:'💖', label:'Freundschaft', c:'#ff6b9d' },
{ key:'normal', emoji:'🤝', label:'Normal', c:'#4ecdc4' },
{ key:'auftrag', emoji:'💼', label:'Auftrag', c:'#ffe66d' },
];
function StatusBadge({ status, onClick, small }) {
const s = STATUS[status] || STATUS.warteliste;
return (
<button onClick={onClick} style={{
background:s.bg, border:`1px solid ${s.color}44`,
borderRadius:20, padding:small?'3px 10px':'5px 14px',
color:s.color, fontFamily:'monospace', fontSize:small?10:11,
cursor:onClick?'pointer':'default', whiteSpace:'nowrap',
}}>{s.label}</button>
);
}
// ── Bestellung Detail ─────────────────────────────────────────────────────────
function BestellungDetail({ id, toast, onBack }) {
const [order, setOrder] = useState(null);
const [loading, setLoading] = useState(true);
const [archiv, setArchiv] = useState([]);
const [showPicker, setShowPicker] = useState(false);
const [pickerSearch,setPickerSearch]= useState('');
const [editMode, setEditMode] = useState(!id);
const [form, setForm] = useState({ name:'', bemerkung:'', custom_price:'', status:'warteliste' });
useEffect(() => {
api('/tools/kalkulator3d').then(setArchiv).catch(()=>{});
if (id) {
api(`/tools/bestellungen/${id}`)
.then(o => { setOrder(o); setForm({ name:o.name, bemerkung:o.bemerkung, custom_price:o.custom_price??'', status:o.status }); })
.catch(e => toast(e.message,'error'))
.finally(() => setLoading(false));
} else { setLoading(false); }
}, [id]);
const totals = useMemo(() => {
const items = order?.items || [];
return {
freundschaft: items.reduce((s,i) => s + i.preis_freundschaft * i.stueckzahl, 0),
normal: items.reduce((s,i) => s + i.preis_normal * i.stueckzahl, 0),
auftrag: items.reduce((s,i) => s + i.preis_auftrag * i.stueckzahl, 0),
custom_sum: items.reduce((s,i) => s + (i.custom_price != null ? i.custom_price * i.stueckzahl : 0), 0),
has_any_custom: items.some(i => i.custom_price != null),
};
}, [order]);
const saveOrder = async () => {
if (!form.name.trim()) { toast('Name erforderlich','error'); return; }
try {
const payload = { ...form, custom_price: form.custom_price!=='' ? parseFloat(form.custom_price) : null };
if (id) {
const u = await api(`/tools/bestellungen/${id}`,{method:'PUT',body:payload});
setOrder(u); setEditMode(false); toast('Gespeichert');
} else {
const created = await api('/tools/bestellungen',{body:payload});
toast('Bestellung angelegt');
onBack(created.id);
}
} catch(e) { toast(e.message,'error'); }
};
const addItem = async calcId => {
try { const u = await api(`/tools/bestellungen/${id}/items`,{body:{calculation_id:calcId,stueckzahl:1}}); setOrder(u); setShowPicker(false); toast('Hinzugefügt'); }
catch(e) { toast(e.message,'error'); }
};
const updateItem = async (itemId, patch) => {
try { const u = await api(`/tools/bestellungen/${id}/items/${itemId}`,{method:'PUT',body:patch}); setOrder(u); }
catch(e) { toast(e.message,'error'); }
};
const removeItem = async itemId => {
try { const u = await api(`/tools/bestellungen/${id}/items/${itemId}`,{method:'DELETE'}); setOrder(u); toast('Entfernt'); }
catch(e) { toast(e.message,'error'); }
};
const cycleOrderStatus = async () => {
const next = STATUS_KEYS[(STATUS_KEYS.indexOf(order.status)+1) % STATUS_KEYS.length];
try { const u = await api(`/tools/bestellungen/${id}`,{method:'PUT',body:{...order,status:next}}); setOrder(u); }
catch(e) { toast(e.message,'error'); }
};
const cycleItemStatus = item => {
const next = STATUS_KEYS[(STATUS_KEYS.indexOf(item.status||'warteliste')+1) % STATUS_KEYS.length];
updateItem(item.id, { status: next });
};
const filteredArchiv = pickerSearch
? archiv.filter(a => a.name.toLowerCase().includes(pickerSearch.toLowerCase()))
: archiv;
if (loading) return <div style={{padding:24,color:'rgba(255,255,255,0.3)',fontFamily:'monospace'}}>Lädt</div>;
return (
<div style={{ padding:'14px 14px 90px', maxWidth:860 }}>
{/* Header */}
<div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:16 }}>
<button onClick={() => onBack(null)} style={{ background:'transparent', border:'none', cursor:'pointer', padding:4 }}>
<ChevronIcon size={20} color="rgba(255,255,255,0.5)" dir="left"/>
</button>
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:18, flex:1, margin:0 }}>
{id ? (editMode?'Bearbeiten':order?.name) : 'Neue Bestellung'}
</h1>
{order && !editMode && <StatusBadge status={order.status} onClick={cycleOrderStatus}/>}
{order && !editMode && (
<button onClick={()=>setEditMode(true)} style={{background:'transparent',border:'none',cursor:'pointer',padding:4}}>
<EditIcon size={18} color="rgba(255,255,255,0.5)"/>
</button>
)}
</div>
{/* Formular */}
{(editMode || !id) && (
<div style={{...S.card,marginBottom:12}}>
<div style={{marginBottom:10}}>
<label style={{...S.head,display:'block',marginBottom:4}}>NAME / PERSON</label>
<input value={form.name} onChange={e=>setForm(f=>({...f,name:e.target.value}))}
placeholder="z.B. Max Mustermann" style={{...S.inp,fontSize:15}}/>
</div>
<div style={{marginBottom:10}}>
<label style={{...S.head,display:'block',marginBottom:4}}>BEMERKUNG</label>
<textarea value={form.bemerkung} onChange={e=>setForm(f=>({...f,bemerkung:e.target.value}))}
placeholder="Hinweise, Lieferung…" rows={2}
style={{...S.inp,resize:'vertical',lineHeight:1.6,fontSize:14,padding:'9px 12px'}}/>
</div>
<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10,marginBottom:12}}>
<div>
<label style={{...S.head,display:'block',marginBottom:4}}>STATUS</label>
<select value={form.status} onChange={e=>setForm(f=>({...f,status:e.target.value}))} style={{...S.inp,fontSize:14}}>
{Object.entries(STATUS).map(([k,v])=><option key={k} value={k} style={{background:'#1a1a1e'}}>{v.label}</option>)}
</select>
</div>
<div>
<label style={{...S.head,display:'block',marginBottom:4}}>GESAMT-FESTPREIS ()</label>
<input type="number" value={form.custom_price} onChange={e=>setForm(f=>({...f,custom_price:e.target.value}))}
placeholder="Optional" step="0.01" style={{...S.inp,fontSize:15}}/>
</div>
</div>
<div style={{display:'flex',gap:8}}>
<button onClick={saveOrder} style={{...S.btn('#4ecdc4'),flex:1,textAlign:'center',padding:'10px 0'}}>
{id?'✓ Speichern':'+ Bestellung anlegen'}
</button>
{id && <button onClick={()=>setEditMode(false)} style={{...S.btn('#ff6b9d'),padding:'10px 14px'}}>Abbrechen</button>}
</div>
</div>
)}
{order && !editMode && (
<>
{order.bemerkung && (
<div style={{...S.card,marginBottom:10,padding:'10px 14px'}}>
<div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:12,lineHeight:1.6}}>{order.bemerkung}</div>
</div>
)}
{/* Positionen */}
<div style={{...S.card,marginBottom:10}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:12}}>
<div style={S.head}>POSITIONEN</div>
<button onClick={()=>{setShowPicker(true);setPickerSearch('');}}
style={{...S.btn('#4ecdc4',true),display:'flex',alignItems:'center',gap:5}}>
<PlusIcon size={13} color="#4ecdc4"/> Hinzufügen
</button>
</div>
{order.items.length===0 && (
<div style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:12,padding:'6px 0'}}>
Noch keine Positionen.
</div>
)}
{order.items.map(item => (
<div key={item.id} style={{borderBottom:'1px solid rgba(255,255,255,0.05)',paddingBottom:12,marginBottom:12}}>
{/* Name + Status + Löschen */}
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
<span style={{color:'#fff',fontFamily:'monospace',fontSize:13,flex:1}}>{item.calc_name}</span>
<div style={{display:'flex',gap:6,alignItems:'center'}}>
<StatusBadge status={item.status||'warteliste'} onClick={()=>cycleItemStatus(item)} small/>
<button onClick={()=>removeItem(item.id)} style={{background:'transparent',border:'none',cursor:'pointer',padding:2}}>
<XIcon size={14} color="rgba(255,255,255,0.3)"/>
</button>
</div>
</div>
{/* Stückzahl gesamt + Festpreis */}
<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginBottom:10}}>
<div>
<div style={{...S.head,marginBottom:4}}>STÜCKZAHL GESAMT</div>
<div style={{display:'flex',alignItems:'center',background:'rgba(255,255,255,0.05)',borderRadius:7,border:'1px solid rgba(255,255,255,0.1)',overflow:'hidden'}}>
<button onClick={()=>item.stueckzahl>1&&updateItem(item.id,{stueckzahl:item.stueckzahl-1})}
style={{padding:'6px 10px',background:'transparent',border:'none',color:'rgba(255,255,255,0.6)',fontSize:18,cursor:'pointer'}}></button>
<span style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,flex:1,textAlign:'center'}}>{item.stueckzahl}</span>
<button onClick={()=>updateItem(item.id,{stueckzahl:item.stueckzahl+1})}
style={{padding:'6px 10px',background:'transparent',border:'none',color:'rgba(255,255,255,0.6)',fontSize:18,cursor:'pointer'}}>+</button>
</div>
</div>
<div>
<div style={{...S.head,marginBottom:4}}>FESTPREIS/STK ()</div>
<input type="number" step="0.01" min="0"
defaultValue={item.custom_price??''}
placeholder=""
onBlur={e=>{
const v = e.target.value==='' ? null : parseFloat(e.target.value);
updateItem(item.id, {custom_price:v});
}}
style={{...S.inp,fontSize:14,padding:'8px 10px'}}/>
</div>
</div>
{/* Status pro Stück */}
<div style={{marginBottom:10}}>
<div style={{...S.head,marginBottom:6}}>STATUS PRO STÜCK</div>
<div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:6}}>
{[
['warteliste','Warteliste','#ffe66d', item.qty_warteliste||0],
['in_arbeit', 'In Arbeit', '#4ecdc4', item.qty_in_arbeit||0],
['fertig', 'Fertig', '#6bcb77', item.qty_fertig||0],
].map(([key, label, color, qty]) => {
const others = item.stueckzahl - qty;
return (
<div key={key} style={{background:`${color}10`,border:`1px solid ${color}30`,borderRadius:8,padding:'8px 6px',textAlign:'center'}}>
<div style={{color:'rgba(255,255,255,0.35)',fontSize:9,fontFamily:'monospace',marginBottom:6}}>{label.toUpperCase()}</div>
<div style={{display:'flex',alignItems:'center',justifyContent:'center',gap:4}}>
<button
onClick={()=>qty>0&&updateItem(item.id,{[`qty_${key}`]:qty-1})}
style={{width:22,height:22,borderRadius:5,background:'rgba(0,0,0,0.3)',border:'none',color:qty>0?color:'rgba(255,255,255,0.15)',fontSize:16,cursor:qty>0?'pointer':'default',lineHeight:1}}></button>
<span style={{color,fontFamily:"'Space Mono',monospace",fontSize:16,fontWeight:700,minWidth:20,textAlign:'center'}}>{qty}</span>
<button
onClick={()=>{const sum=(item.qty_warteliste||0)+(item.qty_in_arbeit||0)+(item.qty_fertig||0);sum<item.stueckzahl&&updateItem(item.id,{[`qty_${key}`]:qty+1})}}
style={{width:22,height:22,borderRadius:5,background:'rgba(0,0,0,0.3)',border:'none',color:color,fontSize:16,cursor:'pointer',lineHeight:1}}>+</button>
</div>
</div>
);
})}
</div>
{/* Verbleibend ohne Status */}
{(() => { const assigned=(item.qty_warteliste||0)+(item.qty_in_arbeit||0)+(item.qty_fertig||0); return assigned<item.stueckzahl ? (
<div style={{marginTop:6,color:'rgba(255,230,109,0.7)',fontFamily:'monospace',fontSize:10,textAlign:'center'}}>
{item.stueckzahl-assigned} Stück noch keinem Status zugewiesen
</div>
) : null; })()}
</div>
{/* Preiszeile */}
<div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
{PREISE.map(p=>(
<div key={p.key} style={{textAlign:'center',minWidth:60}}>
<div style={{fontSize:10}}>{p.emoji}</div>
<div style={{color:p.c,fontFamily:"'Space Mono',monospace",fontSize:11,fontWeight:700}}>
{(item[`preis_${p.key}`]*item.stueckzahl).toFixed(2)}
</div>
</div>
))}
{item.custom_price!=null && (
<div style={{textAlign:'center',minWidth:60,marginLeft:'auto'}}>
<div style={{color:'rgba(255,255,255,0.3)',fontSize:9,fontFamily:'monospace'}}>FEST</div>
<div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:12,fontWeight:700}}>
{(item.custom_price*item.stueckzahl).toFixed(2)}
</div>
</div>
)}
</div>
</div>
))}
</div>
{/* Summen */}
{order.items.length > 0 && (
<div style={{...S.card,marginBottom:10}}>
<div style={S.head}>GESAMTSUMME</div>
<div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:8,marginBottom:totals.has_any_custom||order.custom_price?12:0}}>
{PREISE.map(p=>(
<div key={p.key} style={{background:`${p.c}10`,border:`1px solid ${p.c}30`,borderRadius:9,padding:'10px 8px',textAlign:'center'}}>
<div style={{fontSize:16,marginBottom:2}}>{p.emoji}</div>
<div style={{color:'rgba(255,255,255,0.35)',fontSize:9,fontFamily:'monospace',marginBottom:3}}>{p.label}</div>
<div style={{color:p.c,fontFamily:"'Space Mono',monospace",fontSize:16,fontWeight:700}}>
{totals[p.key].toFixed(2)}
</div>
</div>
))}
</div>
{totals.has_any_custom && (
<div style={{padding:'10px 14px',background:'rgba(255,255,255,0.04)',border:'1px solid rgba(255,255,255,0.1)',borderRadius:8,display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:12}}> Positions-Festpreise</span>
<span style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:18,fontWeight:700}}>
{totals.custom_sum.toFixed(2)}
</span>
</div>
)}
{order.custom_price != null && (
<div style={{padding:'10px 14px',background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.15)',borderRadius:8,display:'flex',justifyContent:'space-between',alignItems:'center'}}>
<span style={{color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:12}}>Gesamt-Festpreis</span>
<span style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:20,fontWeight:700}}>
{parseFloat(order.custom_price).toFixed(2)}
</span>
</div>
)}
</div>
)}
{/* Archiv-Picker */}
{showPicker && (
<div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.8)',zIndex:5000,
display:'flex',alignItems:'flex-end',justifyContent:'center'}}
onClick={e=>e.target===e.currentTarget&&setShowPicker(false)}>
<div style={{background:'#1a1a1e',borderRadius:'16px 16px 0 0',width:'100%',maxWidth:600,
maxHeight:'75vh',display:'flex',flexDirection:'column',border:'1px solid rgba(255,255,255,0.1)'}}>
<div style={{padding:'16px 16px 10px'}}>
<div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 14px'}}/>
<div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,fontWeight:700,marginBottom:10}}>Archiv-Eintrag wählen</div>
<div style={{position:'relative'}}>
<span style={{position:'absolute',left:10,top:'50%',transform:'translateY(-50%)'}}>
<SearchIcon size={14} color="rgba(255,255,255,0.3)"/>
</span>
<input value={pickerSearch} onChange={e=>setPickerSearch(e.target.value)}
placeholder="Suchen…" style={{...S.inp,paddingLeft:32,fontSize:14}} autoFocus/>
</div>
</div>
<div style={{overflowY:'auto',flex:1,padding:'0 8px 8px'}}>
{filteredArchiv.map(a=>(
<button key={a.id} onClick={()=>addItem(a.id)} style={{
width:'100%',padding:'12px',background:'transparent',border:'none',
borderBottom:'1px solid rgba(255,255,255,0.05)',
display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',
}}>
<div style={{textAlign:'left'}}>
<div style={{color:'#fff',fontFamily:'monospace',fontSize:13}}>{a.name}</div>
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:2}}>{a.gramm}g · {a.stunden}h</div>
</div>
<div style={{display:'flex',gap:8}}>
{PREISE.map(p=>(
<div key={p.key} style={{textAlign:'right'}}>
<div style={{fontSize:10}}>{p.emoji}</div>
<div style={{color:p.c,fontFamily:"'Space Mono',monospace",fontSize:11}}>{a[`preis_${p.key}`].toFixed(2)}</div>
</div>
))}
</div>
</button>
))}
{filteredArchiv.length===0 && <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:12,padding:'12px 8px'}}>Keine Treffer.</div>}
</div>
<div style={{padding:'8px 16px 24px'}}>
<button onClick={()=>setShowPicker(false)} style={{...S.btn('#ff6b9d'),width:'100%',textAlign:'center',padding:'10px 0'}}>Schließen</button>
</div>
</div>
</div>
)}
</>
)}
</div>
);
}
// ── Übersicht ─────────────────────────────────────────────────────────────────
export default function Bestellungen({ toast, mobile }) {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
const [filterStatus,setFilter] = useState('alle');
const [openId, setOpenId] = useState(null);
const [delId, setDelId] = useState(null);
const [newMode, setNewMode] = useState(false);
const load = () => {
setLoading(true);
api('/tools/bestellungen').then(setOrders).catch(e=>toast(e.message,'error')).finally(()=>setLoading(false));
};
useEffect(()=>{ load(); },[]);
const del = async id => {
try { await api(`/tools/bestellungen/${id}`,{method:'DELETE'}); setOrders(p=>p.filter(o=>o.id!==id)); toast('Gelöscht'); }
catch(e) { toast(e.message,'error'); } finally { setDelId(null); }
};
const filtered = orders.filter(o => {
const ms = !search.trim() || o.name.toLowerCase().includes(search.toLowerCase());
const mf = filterStatus==='alle' || o.status===filterStatus;
return ms && mf;
});
if (openId !== null || newMode) {
return (
<BestellungDetail
id={newMode ? null : openId}
toast={toast}
onBack={savedId => { setNewMode(false); if (savedId) setOpenId(savedId); else { setOpenId(null); load(); } }}
/>
);
}
return (
<div style={{padding:mobile?'14px 14px 90px':'36px 44px',maxWidth:860}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:14}}>
<h1 style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:mobile?18:22,margin:0}}>Bestellungen</h1>
<button onClick={()=>{setNewMode(true);setOpenId(null);}} style={{...S.btn('#4ecdc4'),display:'flex',alignItems:'center',gap:6}}>
<PlusIcon size={14} color="#4ecdc4"/> Neu
</button>
</div>
{/* Suche */}
<div style={{position:'relative',marginBottom:10}}>
<span style={{position:'absolute',left:12,top:'50%',transform:'translateY(-50%)'}}>
<SearchIcon size={14} color="rgba(255,255,255,0.3)"/>
</span>
<input value={search} onChange={e=>setSearch(e.target.value)} placeholder="Name suchen…"
style={{...S.inp,paddingLeft:34,fontSize:15}}/>
{search && <button onClick={()=>setSearch('')} style={{position:'absolute',right:10,top:'50%',transform:'translateY(-50%)',background:'transparent',border:'none',color:'rgba(255,255,255,0.3)',cursor:'pointer',fontSize:16}}></button>}
</div>
{/* Status-Filter */}
<div style={{display:'flex',gap:6,marginBottom:14,flexWrap:'wrap'}}>
{[['alle','Alle','rgba(255,255,255,0.6)','rgba(255,255,255,0.1)'],...Object.entries(STATUS).map(([k,v])=>[k,v.label,v.color,v.bg])].map(([k,l,c,bg])=>(
<button key={k} onClick={()=>setFilter(k)} style={{
padding:'5px 12px',borderRadius:20,border:`1px solid ${filterStatus===k?c:'rgba(255,255,255,0.1)'}`,
background:filterStatus===k?bg:'transparent',
color:filterStatus===k?c:'rgba(255,255,255,0.4)',
fontFamily:'monospace',fontSize:11,cursor:'pointer',
}}>{l}</button>
))}
</div>
<p style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:10,marginBottom:12}}>
{filtered.length} von {orders.length} Bestellungen
</p>
{loading && <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace'}}>Lädt</div>}
{!loading && orders.length===0 && (
<div style={{...S.card,textAlign:'center',padding:40}}>
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:12}}>Noch keine Bestellungen.</div>
</div>
)}
{filtered.map(order => {
const done = order.qty_fertig_total || 0;
const total = order.qty_total || 0;
const customSum = order.custom_price_sum || 0;
const allDone = total > 0 && done === total;
return (
<div key={order.id} style={{...S.card,marginBottom:8,padding:'12px 14px'}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',marginBottom:6}}>
<button onClick={()=>setOpenId(order.id)} style={{background:'transparent',border:'none',cursor:'pointer',textAlign:'left',padding:0,flex:1}}>
<div style={{color:'#fff',fontFamily:'monospace',fontSize:14,fontWeight:700}}>{order.name}</div>
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
{new Date(order.created_at).toLocaleDateString('de-DE')}
</div>
</button>
<StatusBadge status={order.status} small/>
</div>
{/* Positions-Fortschritt + Festpreis-Summe */}
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
<div style={{display:'flex',alignItems:'center',gap:6}}>
<div style={{
fontSize:10,fontFamily:'monospace',
color: allDone?'#6bcb77':'rgba(255,255,255,0.4)',
background: allDone?'rgba(107,203,119,0.12)':'rgba(255,255,255,0.05)',
border:`1px solid ${allDone?'rgba(107,203,119,0.3)':'rgba(255,255,255,0.08)'}`,
borderRadius:12,padding:'2px 8px',
}}>
{done}/{total} fertig
</div>
</div>
{customSum > 0 && (
<div style={{color:'rgba(255,255,255,0.5)',fontFamily:"'Space Mono',monospace",fontSize:12}}>
<span style={{color:'#fff',fontWeight:700}}>{customSum.toFixed(2)}</span>
</div>
)}
</div>
<div style={{display:'flex',gap:6,justifyContent:'flex-end'}}>
<button onClick={()=>setOpenId(order.id)} style={{...S.btn('#4ecdc4',true),flex:1,textAlign:'center'}}>Öffnen</button>
{delId===order.id?(
<div style={{display:'flex',gap:5,alignItems:'center'}}>
<button onClick={()=>del(order.id)} style={S.btn('#ff6b9d',true)}> Ja</button>
<button onClick={()=>setDelId(null)} style={S.btn('rgba(255,255,255,0.4)',true)}>Nein</button>
</div>
):(
<button onClick={()=>setDelId(order.id)} style={S.btn('#ff6b9d',true)}>
<TrashIcon size={13} color="#ff6b9d"/>
</button>
)}
</div>
</div>
);
})}
</div>
);
}