feat: reports cost tracking, staff hourly rate, product form refactor
- Backend reports: fix cost/profit calculation to handle null unit_cost with has_gap flag; expose total_cost in product & workday summary - StaffTab: add hourly_rate field in payroll section (admin-only) - ProductFormModal: major refactor of form layout and structure - ProductsTab: minor tweaks aligned with form changes - Report pages (Today, WorkDaySummary, CategoryPerformance, ProductPerformance, RevenueTrends): UI improvements and cost data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -263,7 +263,7 @@ const inputStyle = {
|
||||
fontSize: 13, outline: 'none', color: '#111827', background: '#fff', boxSizing: 'border-box',
|
||||
}
|
||||
|
||||
const EMPTY_FORM = { username: '', full_name: '', nickname: '', mobile_phone: '', email: '', note: '', role: 'waiter', pin: '' }
|
||||
const EMPTY_FORM = { username: '', full_name: '', nickname: '', mobile_phone: '', email: '', note: '', role: 'waiter', pin: '', hourly_rate: '' }
|
||||
|
||||
// ── Main page ─────────────────────────────────────────────────────────────────
|
||||
export default function WaitersPage() {
|
||||
@@ -424,6 +424,7 @@ export default function WaitersPage() {
|
||||
nickname: newForm.nickname || null, mobile_phone: newForm.mobile_phone || null,
|
||||
email: newForm.email || null, note: newForm.note || null,
|
||||
pin: newForm.pin, role: newForm.role, is_active: true,
|
||||
hourly_rate: newForm.hourly_rate !== '' ? parseFloat(newForm.hourly_rate) : null,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
@@ -584,8 +585,30 @@ function AddWaiterModal({ form, setForm, avatarFile, avatarPreview, setAvatarFil
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 3: PIN */}
|
||||
<div style={{ padding: '20px 22px', display: 'flex', flexDirection: 'column', gap: 12, background: '#fafafa' }}>
|
||||
{/* Column 3: Payroll + PIN */}
|
||||
<div style={{ padding: '20px 22px', display: 'flex', flexDirection: 'column', gap: 16, background: '#fafafa' }}>
|
||||
<div>
|
||||
<SectionLabel>Μισθοδοσία</SectionLabel>
|
||||
<FormField label="Ωριαία αμοιβή (€/ώρα)" desc="Μόνο για διαχειριστές. Δεν εμφανίζεται στον εργαζόμενο.">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<input
|
||||
type="number" step="0.50" min="0"
|
||||
style={{ ...inputStyle, width: 110 }}
|
||||
placeholder="π.χ. 5.50"
|
||||
value={form.hourly_rate ?? ''}
|
||||
onChange={e => f('hourly_rate', e.target.value)}
|
||||
/>
|
||||
{form.hourly_rate && parseFloat(form.hourly_rate) > 0 && (
|
||||
<span style={{ fontSize: 11.5, color: '#16a34a', fontWeight: 500 }}>
|
||||
€{parseFloat(form.hourly_rate).toFixed(2)}/ώρα
|
||||
</span>
|
||||
)}
|
||||
{!form.hourly_rate && (
|
||||
<span style={{ fontSize: 11, color: '#9ca3af' }}>Δεν παρακολουθείται</span>
|
||||
)}
|
||||
</div>
|
||||
</FormField>
|
||||
</div>
|
||||
<SectionLabel>Κωδικός PIN</SectionLabel>
|
||||
<p style={{ margin: 0, fontSize: 12, color: '#6b7280', lineHeight: 1.5 }}>
|
||||
Ο 4ψήφιος κωδικός που θα χρησιμοποιεί ο εργαζόμενος για να ξεκλειδώσει την εφαρμογή. Μπορεί να αλλάξει οποτεδήποτε.
|
||||
@@ -725,11 +748,14 @@ function EditWaiterModal({ waiter, form, setForm, avatarInputRef, isPending, isU
|
||||
<div>
|
||||
<SectionLabel>Πληροφορίες</SectionLabel>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<InfoBlock label="Ημερομηνία δημιουργίας"
|
||||
value={waiter.created_at ? new Date(waiter.created_at).toLocaleDateString('el-GR') : null} />
|
||||
<InfoBlock label="Κατάσταση"
|
||||
value={waiter.is_active ? 'Ενεργός' : 'Αποκλεισμένος'}
|
||||
valueColor={waiter.is_active ? '#16a34a' : '#ef4444'} />
|
||||
{/* Row 1: Date + Status side by side */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
|
||||
<InfoBlock label="Εγγραφή"
|
||||
value={waiter.created_at ? new Date(waiter.created_at).toLocaleDateString('el-GR') : null} />
|
||||
<InfoBlock label="Κατάσταση"
|
||||
value={waiter.is_active ? 'Ενεργός' : 'Αποκλεισμένος'}
|
||||
valueColor={waiter.is_active ? '#16a34a' : '#ef4444'} />
|
||||
</div>
|
||||
<InfoBlock label="Ρόλος"
|
||||
value={waiter.role === 'waiter' ? 'Σερβιτόρος' : 'Διαχειριστής'} />
|
||||
{waiter.role === 'waiter' && (
|
||||
|
||||
Reference in New Issue
Block a user