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);
|
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')) 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('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
|
// Migrationen order_items
|
||||||
// files migrations
|
// files migrations
|
||||||
|
|||||||
@@ -77,8 +77,16 @@ router.put('/:id', authenticate, (req, res) => {
|
|||||||
bezahlt_am = req.body.bezahlt ? new Date().toISOString() : null;
|
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=?')
|
// Abgeholt-Status mit Datum
|
||||||
.run(name, bemerkung, status, custom_price, bezahlt, bezahlt_am, req.params.id, uid(req));
|
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)));
|
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}})
|
api(`/tools/bestellungen/${id}`,{method:'PUT',body:{bezahlt:!order.bezahlt}})
|
||||||
.then(u=>{setOrder(u);toast(u.bezahlt?'Als bezahlt markiert':'Bezahlung aufgehoben');});
|
.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 (
|
return (
|
||||||
<div style={{ padding:'14px 14px 90px', maxWidth:860 }}>
|
<div style={{ padding:'14px 14px 90px', maxWidth:860 }}>
|
||||||
<ConfirmDialog/>
|
<ConfirmDialog/>
|
||||||
@@ -178,6 +184,27 @@ function BestellungDetail({ id, toast, onBack }) {
|
|||||||
🔒 Bearbeitung gesperrt.
|
🔒 Bearbeitung gesperrt.
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -598,6 +625,7 @@ export default function Bestellungen({ toast, mobile }) {
|
|||||||
const [openId, setOpenId] = useState(null);
|
const [openId, setOpenId] = useState(null);
|
||||||
const [newMode, setNewMode] = useState(false);
|
const [newMode, setNewMode] = useState(false);
|
||||||
const [archivItem, setArchivItem] = useState(null);
|
const [archivItem, setArchivItem] = useState(null);
|
||||||
|
const [archivOpen, setArchivOpen] = useState(false);
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -612,7 +640,11 @@ export default function Bestellungen({ toast, mobile }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sq = search.trim().toLowerCase();
|
const sq = search.trim().toLowerCase();
|
||||||
|
// Abgeschlossen = bezahlt UND abgeholt
|
||||||
|
const isAbgeschlossen = o => !!o.bezahlt && !!o.abgeholt;
|
||||||
|
|
||||||
const filtered = orders.filter(o => {
|
const filtered = orders.filter(o => {
|
||||||
|
if (isAbgeschlossen(o)) return false; // nie in aktiver Liste
|
||||||
const ms = !sq || o.name.toLowerCase().includes(sq)
|
const ms = !sq || o.name.toLowerCase().includes(sq)
|
||||||
|| (o.items && o.items.some(i => i.calc_name.toLowerCase().includes(sq)));
|
|| (o.items && o.items.some(i => i.calc_name.toLowerCase().includes(sq)));
|
||||||
const mf = filterStatus==='alle'
|
const mf = filterStatus==='alle'
|
||||||
@@ -623,6 +655,9 @@ export default function Bestellungen({ toast, mobile }) {
|
|||||||
return ms && mf;
|
return ms && mf;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const abgeschlossen = orders.filter(o => isAbgeschlossen(o)
|
||||||
|
&& (!sq || o.name.toLowerCase().includes(sq)));
|
||||||
|
|
||||||
if (openId !== null || newMode) {
|
if (openId !== null || newMode) {
|
||||||
return (
|
return (
|
||||||
<BestellungDetail
|
<BestellungDetail
|
||||||
@@ -805,6 +840,74 @@ export default function Bestellungen({ toast, mobile }) {
|
|||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user