fix: revenue aus order_items korrekt berechnet, custom Zeitraum (Monat/Jahr)

This commit is contained in:
2026-06-17 15:02:07 +02:00
parent 23f798e1d1
commit c1db0c1410
2 changed files with 164 additions and 60 deletions

View File

@@ -25,19 +25,51 @@ const fmt = v => `${(v||0).toFixed(2)} €`;
const fmtMonth = m => { if (!m) return ''; const [y,mo] = m.split('-'); return ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'][parseInt(mo)-1]+' '+y.slice(2); };
const PERIODS = [
{ id:'week', label:'Woche' },
{ id:'month', label:'Monat' },
{ id:'year', label:'Jahr' },
{ id:'all', label:'Gesamt'},
{ id:'week', label:'Diese Woche' },
{ id:'month', label:'Dieser Monat' },
{ id:'year', label:'Dieses Jahr' },
{ id:'custom', label:'Eigener Zeitraum' },
{ id:'all', label:'Gesamt'},
];
function periodRange(id) {
// Generiere Monat-Optionen (letzten 24 Monate)
function getMonthOptions() {
const opts = [];
const now = new Date();
for (let i = 0; i < 24; i++) {
const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
const val = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`;
const label = d.toLocaleDateString('de-DE', { month:'long', year:'numeric' });
opts.push({ val, label });
}
return opts;
}
// Generiere Jahr-Optionen
function getYearOptions() {
const opts = [];
const now = new Date().getFullYear();
for (let y = now; y >= now - 5; y--) opts.push(y);
return opts;
}
function periodRange(id, custom) {
const now = new Date();
const pad = n => String(n).padStart(2,'0');
const iso = d => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
if (id==='week') { const d=new Date(now); d.setDate(d.getDate()-6); return { from:iso(d), to:iso(now) }; }
if (id==='month') { const d=new Date(now.getFullYear(),now.getMonth(),1); return { from:iso(d), to:iso(now) }; }
if (id==='year') { return { from:`${now.getFullYear()}-01-01`, to:iso(now) }; }
if (id==='custom' && custom) {
if (custom.type==='month' && custom.month) {
const [y,m] = custom.month.split('-').map(Number);
const last = new Date(y, m, 0);
return { from:`${y}-${pad(m)}-01`, to:iso(last) };
}
if (custom.type==='year' && custom.year) {
return { from:`${custom.year}-01-01`, to:`${custom.year}-12-31` };
}
}
return {};
}
@@ -53,21 +85,22 @@ function KPI({ label, value, color, sub }) {
export default function Statistik({ mobile }) {
const [period, setPeriod] = useState('month');
const [custom, setCustom] = useState({ type:'month', month: new Date().toISOString().slice(0,7), year: new Date().getFullYear() });
const [data, setData] = useState(null);
const [loading,setLoading] = useState(false);
const [tab, setTab] = useState('overview'); // overview | expenses | orders
const [tab, setTab] = useState('overview');
const [expForm,setExpForm] = useState({ date: new Date().toISOString().slice(0,10), category:'Filament', description:'', amount:'' });
const [saving, setSaving] = useState(false);
const load = useCallback(() => {
setLoading(true);
const { from, to } = periodRange(period);
const { from, to } = periodRange(period, custom);
const q = from ? `?from=${from}&to=${to}` : '';
api('/tools/statistik/overview'+q)
.then(d => setData(d))
.catch(()=>{})
.finally(()=>setLoading(false));
}, [period]);
}, [period, custom]);
useEffect(() => { load(); }, [load]);
@@ -97,8 +130,8 @@ export default function Statistik({ mobile }) {
📊 3D-Druck Statistik
</h2>
{/* Zeitraum */}
<div style={{display:'flex',gap:4}}>
{PERIODS.map(p => (
<div style={{display:'flex',gap:4,flexWrap:'wrap',alignItems:'center'}}>
{PERIODS.filter(p=>p.id!=='custom').map(p => (
<button key={p.id} onClick={()=>setPeriod(p.id)} style={{
background: period===p.id ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.04)',
border: `1px solid ${period===p.id ? 'rgba(78,205,196,0.5)' : 'rgba(255,255,255,0.1)'}`,
@@ -107,6 +140,36 @@ export default function Statistik({ mobile }) {
fontFamily:'monospace', fontSize:11, cursor:'pointer',
}}>{p.label}</button>
))}
{/* Eigener Zeitraum */}
<div style={{display:'flex',gap:4,alignItems:'center'}}>
<select value={custom.type} onChange={e=>setCustom(p=>({...p,type:e.target.value}))}
onClick={()=>setPeriod('custom')}
style={{background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.12)',
borderRadius:8,padding:'5px 8px',color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:11,cursor:'pointer'}}>
<option value="month">Monat</option>
<option value="year">Jahr</option>
</select>
{custom.type==='month' && (
<select value={custom.month}
onChange={e=>{setCustom(p=>({...p,month:e.target.value}));setPeriod('custom');}}
style={{background: period==='custom'?'rgba(78,205,196,0.1)':'rgba(255,255,255,0.06)',
border:`1px solid ${period==='custom'?'rgba(78,205,196,0.4)':'rgba(255,255,255,0.12)'}`,
borderRadius:8,padding:'5px 8px',color:period==='custom'?'#4ecdc4':'rgba(255,255,255,0.6)',
fontFamily:'monospace',fontSize:11,cursor:'pointer'}}>
{getMonthOptions().map(o=><option key={o.val} value={o.val}>{o.label}</option>)}
</select>
)}
{custom.type==='year' && (
<select value={custom.year}
onChange={e=>{setCustom(p=>({...p,year:Number(e.target.value)}));setPeriod('custom');}}
style={{background: period==='custom'?'rgba(78,205,196,0.1)':'rgba(255,255,255,0.06)',
border:`1px solid ${period==='custom'?'rgba(78,205,196,0.4)':'rgba(255,255,255,0.12)'}`,
borderRadius:8,padding:'5px 8px',color:period==='custom'?'#4ecdc4':'rgba(255,255,255,0.6)',
fontFamily:'monospace',fontSize:11,cursor:'pointer'}}>
{getYearOptions().map(y=><option key={y} value={y}>{y}</option>)}
</select>
)}
</div>
</div>
</div>