feat: Abgeholt-Status + Archiv-Drawer für abgeschlossene Bestellungen
This commit is contained in:
@@ -155,6 +155,8 @@ if (!cols.includes('bemerkung')) db.exec("ALTER TABLE calculations ADD COLUMN be
|
||||
const ordCols = db.prepare("PRAGMA table_info(orders)").all().map(r => r.name);
|
||||
if (!ordCols.includes('bezahlt')) db.exec("ALTER TABLE orders ADD COLUMN bezahlt INTEGER NOT NULL DEFAULT 0");
|
||||
if (!ordCols.includes('bezahlt_am')) db.exec("ALTER TABLE orders ADD COLUMN bezahlt_am DATETIME");
|
||||
if (!ordCols.includes('abgeholt')) db.exec("ALTER TABLE orders ADD COLUMN abgeholt INTEGER NOT NULL DEFAULT 0");
|
||||
if (!ordCols.includes('abgeholt_am'))db.exec("ALTER TABLE orders ADD COLUMN abgeholt_am DATETIME");
|
||||
|
||||
// Migrationen order_items
|
||||
// files migrations
|
||||
|
||||
@@ -77,8 +77,16 @@ router.put('/:id', authenticate, (req, res) => {
|
||||
bezahlt_am = req.body.bezahlt ? new Date().toISOString() : null;
|
||||
}
|
||||
|
||||
db.prepare('UPDATE orders SET name=?,bemerkung=?,status=?,custom_price=?,bezahlt=?,bezahlt_am=?,updated_at=CURRENT_TIMESTAMP WHERE id=? AND user_id=?')
|
||||
.run(name, bemerkung, status, custom_price, bezahlt, bezahlt_am, req.params.id, uid(req));
|
||||
// Abgeholt-Status mit Datum
|
||||
let abgeholt = ex.abgeholt;
|
||||
let abgeholt_am = ex.abgeholt_am;
|
||||
if (req.body.abgeholt !== undefined) {
|
||||
abgeholt = req.body.abgeholt ? 1 : 0;
|
||||
abgeholt_am = req.body.abgeholt ? new Date().toISOString() : null;
|
||||
}
|
||||
|
||||
db.prepare('UPDATE orders SET name=?,bemerkung=?,status=?,custom_price=?,bezahlt=?,bezahlt_am=?,abgeholt=?,abgeholt_am=?,updated_at=CURRENT_TIMESTAMP WHERE id=? AND user_id=?')
|
||||
.run(name, bemerkung, status, custom_price, bezahlt, bezahlt_am, abgeholt, abgeholt_am, req.params.id, uid(req));
|
||||
res.json(loadOrder(req.params.id, uid(req)));
|
||||
});
|
||||
|
||||
|
||||
@@ -128,6 +128,12 @@ function BestellungDetail({ id, toast, onBack }) {
|
||||
api(`/tools/bestellungen/${id}`,{method:'PUT',body:{bezahlt:!order.bezahlt}})
|
||||
.then(u=>{setOrder(u);toast(u.bezahlt?'Als bezahlt markiert':'Bezahlung aufgehoben');});
|
||||
|
||||
const toggleAbgeholt = () =>
|
||||
api(`/tools/bestellungen/${id}`,{method:'PUT',body:{abgeholt:!order.abgeholt}})
|
||||
.then(u=>{setOrder(u);toast(u.abgeholt?'Als abgeholt markiert':'Abholung aufgehoben');});
|
||||
|
||||
const isAbgeschlossen = order && !!order.bezahlt && !!order.abgeholt;
|
||||
|
||||
return (
|
||||
<div style={{ padding:'14px 14px 90px', maxWidth:860 }}>
|
||||
<ConfirmDialog/>
|
||||
@@ -178,6 +184,27 @@ function BestellungDetail({ id, toast, onBack }) {
|
||||
🔒 Bearbeitung gesperrt.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Abgeholt – nur bei fertig oder bezahlt anzeigen */}
|
||||
{(order.status==='fertig' || !!order.bezahlt) && (
|
||||
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginTop:8,paddingTop:8,
|
||||
borderTop:'1px solid rgba(255,255,255,0.06)'}}>
|
||||
<button onClick={toggleAbgeholt} style={{
|
||||
background:!!order.abgeholt?'rgba(251,191,36,0.15)':'rgba(255,255,255,0.05)',
|
||||
border:`1px solid ${!!order.abgeholt?'rgba(251,191,36,0.5)':'rgba(255,255,255,0.12)'}`,
|
||||
borderRadius:20,padding:'5px 14px',
|
||||
color:!!order.abgeholt?'#fbbf24':'rgba(255,255,255,0.45)',
|
||||
fontFamily:'monospace',fontSize:11,cursor:'pointer',
|
||||
}}>
|
||||
{!!order.abgeholt?'📦 Abgeholt':'○ Als abgeholt markieren'}
|
||||
</button>
|
||||
{!!order.abgeholt && order.abgeholt_am && (
|
||||
<span style={{color:'#fbbf24',fontFamily:'monospace',fontSize:11}}>
|
||||
am {new Date(order.abgeholt_am).toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric'})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -598,6 +625,7 @@ export default function Bestellungen({ toast, mobile }) {
|
||||
const [openId, setOpenId] = useState(null);
|
||||
const [newMode, setNewMode] = useState(false);
|
||||
const [archivItem, setArchivItem] = useState(null);
|
||||
const [archivOpen, setArchivOpen] = useState(false);
|
||||
|
||||
const load = () => {
|
||||
setLoading(true);
|
||||
@@ -612,7 +640,11 @@ export default function Bestellungen({ toast, mobile }) {
|
||||
};
|
||||
|
||||
const sq = search.trim().toLowerCase();
|
||||
// Abgeschlossen = bezahlt UND abgeholt
|
||||
const isAbgeschlossen = o => !!o.bezahlt && !!o.abgeholt;
|
||||
|
||||
const filtered = orders.filter(o => {
|
||||
if (isAbgeschlossen(o)) return false; // nie in aktiver Liste
|
||||
const ms = !sq || o.name.toLowerCase().includes(sq)
|
||||
|| (o.items && o.items.some(i => i.calc_name.toLowerCase().includes(sq)));
|
||||
const mf = filterStatus==='alle'
|
||||
@@ -623,6 +655,9 @@ export default function Bestellungen({ toast, mobile }) {
|
||||
return ms && mf;
|
||||
});
|
||||
|
||||
const abgeschlossen = orders.filter(o => isAbgeschlossen(o)
|
||||
&& (!sq || o.name.toLowerCase().includes(sq)));
|
||||
|
||||
if (openId !== null || newMode) {
|
||||
return (
|
||||
<BestellungDetail
|
||||
@@ -805,6 +840,74 @@ export default function Bestellungen({ toast, mobile }) {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* ── Archiv-Drawer ───────────────────────────────────────────────── */}
|
||||
{abgeschlossen.length > 0 && (
|
||||
<div style={{marginTop:20}}>
|
||||
<button onClick={()=>setArchivOpen(v=>!v)} style={{
|
||||
width:'100%',background:'rgba(255,255,255,0.03)',
|
||||
border:'1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius:10,padding:'10px 14px',cursor:'pointer',
|
||||
display:'flex',alignItems:'center',justifyContent:'space-between',
|
||||
}}>
|
||||
<div style={{display:'flex',alignItems:'center',gap:8}}>
|
||||
<span style={{fontSize:16}}>✅</span>
|
||||
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:12}}>
|
||||
ABGESCHLOSSEN
|
||||
</span>
|
||||
<span style={{
|
||||
background:'rgba(74,222,128,0.15)',border:'1px solid rgba(74,222,128,0.3)',
|
||||
borderRadius:10,padding:'1px 8px',color:'#4ade80',fontFamily:'monospace',fontSize:10,
|
||||
}}>{abgeschlossen.length}</span>
|
||||
</div>
|
||||
<span style={{color:'rgba(255,255,255,0.3)',fontSize:11,
|
||||
transform:archivOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
|
||||
</button>
|
||||
|
||||
{archivOpen && (
|
||||
<div style={{marginTop:8}}>
|
||||
{abgeschlossen.map(order => {
|
||||
const customSum = order.custom_price_sum || 0;
|
||||
return (
|
||||
<div key={order.id} style={{...S.card,marginBottom:6,padding:'10px 14px',
|
||||
opacity:0.7,borderColor:'rgba(255,255,255,0.05)'}}>
|
||||
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:4}}>
|
||||
<button onClick={()=>setOpenId(order.id)}
|
||||
style={{background:'transparent',border:'none',cursor:'pointer',textAlign:'left',padding:0,flex:1}}>
|
||||
<div style={{color:'rgba(255,255,255,0.75)',fontFamily:'monospace',fontSize:13,fontWeight:700}}>
|
||||
{order.name}
|
||||
</div>
|
||||
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
|
||||
{new Date(order.created_at).toLocaleDateString('de-DE')}
|
||||
{order.abgeholt_am && ` · abgeholt ${new Date(order.abgeholt_am).toLocaleDateString('de-DE')}`}
|
||||
</div>
|
||||
</button>
|
||||
<div style={{display:'flex',gap:5,alignItems:'center',flexShrink:0}}>
|
||||
<span style={{background:'rgba(251,191,36,0.12)',border:'1px solid rgba(251,191,36,0.3)',
|
||||
borderRadius:20,padding:'2px 8px',color:'#fbbf24',fontFamily:'monospace',fontSize:9}}>📦</span>
|
||||
<span style={{background:BEZAHLT.bg,border:`1px solid ${BEZAHLT.color}44`,
|
||||
borderRadius:20,padding:'2px 8px',color:BEZAHLT.color,fontFamily:'monospace',fontSize:9}}>💜</span>
|
||||
{customSum > 0 && (
|
||||
<span style={{color:'rgba(255,255,255,0.4)',fontFamily:"'Space Mono',monospace",fontSize:11}}>
|
||||
{customSum.toFixed(2)}€
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{display:'flex',gap:6,justifyContent:'flex-end',marginTop:6}}>
|
||||
<button onClick={()=>setOpenId(order.id)}
|
||||
style={{...S.btn('#4ecdc4',true),flex:1,textAlign:'center'}}>Öffnen</button>
|
||||
<button onClick={()=>del(order.id)} style={S.btn('#ff6b9d',true)}>
|
||||
<TrashIcon size={13} color="#ff6b9d"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user