diff --git a/frontend/src/pages/Weekly.tsx b/frontend/src/pages/Weekly.tsx index 2a5bf49..ee47d10 100644 --- a/frontend/src/pages/Weekly.tsx +++ b/frontend/src/pages/Weekly.tsx @@ -3,24 +3,32 @@ import { ChevronLeft, ChevronRight, Download } from 'lucide-react' import { getActuals, getNetSales, getBudgets, downloadExport } from '../api' import type { DeptActuals, WageBudget } from '../types' -function startOfWeek(d: Date): Date { +function mondayOf(d: Date): string { const day = d.getDay() - const diff = (day === 0 ? -6 : 1 - day) // Mon = start const r = new Date(d) - r.setDate(d.getDate() + diff) + r.setDate(d.getDate() + (day === 0 ? -6 : 1 - day)) r.setHours(0, 0, 0, 0) - return r + return r.toISOString().slice(0, 10) } -function addDays(d: Date, n: number): Date { - const r = new Date(d) - r.setDate(r.getDate() + n) - return r +function addDaysStr(dateStr: string, n: number): string { + const d = new Date(dateStr + 'T00:00:00') + d.setDate(d.getDate() + n) + return d.toISOString().slice(0, 10) } -function fmt(d: Date): string { return d.toISOString().slice(0, 10) } -function fmtMoney(n: number): string { return `£${n.toLocaleString('en-GB', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` } -function daysInMonth(date: Date): number { return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() } +function daysInMonthFor(dateStr: string): number { + const d = new Date(dateStr + 'T00:00:00') + return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() +} + +function fmtDisplay(dateStr: string): string { + return new Date(dateStr + 'T00:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'short' }) +} + +function fmtMoney(n: number): string { + return `£${n.toLocaleString('en-GB', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` +} function pctClass(pct: number | null): string { if (pct == null) return '' @@ -30,18 +38,18 @@ function pctClass(pct: number | null): string { } export default function Weekly() { - const [weekStart, setWeekStart] = useState(() => startOfWeek(new Date())) - const [depts, setDepts] = useState([]) - const [netSales, setNetSales] = useState(0) - const [pySales, setPySales] = useState(0) - const [budget, setBudget] = useState(null) - const [showOncosts, setShowOncosts] = useState(true) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) + const [fromStr, setFromStr] = useState(() => mondayOf(new Date())) - const weekEnd = addDays(weekStart, 6) - const fromStr = fmt(weekStart) - const toStr = fmt(weekEnd) + const toStr = addDaysStr(fromStr, 6) + const todayStr = new Date().toISOString().slice(0, 10) + + const [depts, setDepts] = useState([]) + const [netSales, setNetSales] = useState(0) + const [pySales, setPySales] = useState(0) + const [budget, setBudget] = useState(null) + const [showOncosts, setShowOncosts] = useState(true) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) const load = useCallback(async () => { setLoading(true); setError(null) @@ -53,23 +61,20 @@ export default function Weekly() { ]) setDepts(actRes.departments) setShowOncosts(actRes.show_oncosts) + setNetSales(salesRes.days.reduce((s, d) => s + d.net_sales, 0)) + setPySales(salesRes.days.reduce((s, d) => s + d.py_sales, 0)) - const totalSales = salesRes.days.reduce((s, d) => s + d.net_sales, 0) - const totalPY = salesRes.days.reduce((s, d) => s + d.py_sales, 0) - setNetSales(totalSales) - setPySales(totalPY) - - // Find budget for the month of weekStart - const monthKey = `${weekStart.getFullYear()}-${String(weekStart.getMonth() + 1).padStart(2, '0')}-01` + const d0 = new Date(fromStr + 'T00:00:00') + const monthKey = `${d0.getFullYear()}-${String(d0.getMonth() + 1).padStart(2, '0')}-01` const bRow = (budgetRes.budgets as WageBudget[]).find(b => b.month === monthKey) if (bRow) { - const dim = daysInMonth(weekStart) - // Pro-rata: days in the selected week ÷ days in month - const today = new Date() - let weekDays = 7 - if (weekStart <= today && today <= weekEnd) { - weekDays = Math.ceil((today.getTime() - weekStart.getTime()) / 86_400_000) + 1 - } + const dim = daysInMonthFor(fromStr) + // Pro-rata: how many days of this week are <= today + const effectiveTo = todayStr < toStr ? todayStr : toStr + const effectiveFrom = fromStr > todayStr ? todayStr : fromStr + const weekDays = effectiveTo >= effectiveFrom + ? Math.round((new Date(effectiveTo + 'T00:00:00').getTime() - new Date(effectiveFrom + 'T00:00:00').getTime()) / 86_400_000) + 1 + : 7 setBudget(bRow.budget_amount * (weekDays / dim)) } else { setBudget(null) @@ -79,35 +84,32 @@ export default function Weekly() { } finally { setLoading(false) } - }, [fromStr, toStr, weekStart, weekEnd]) + }, [fromStr, toStr, todayStr]) // all primitive strings — stable refs useEffect(() => { load() }, [load]) - const prev = () => setWeekStart(d => addDays(d, -7)) - const next = () => setWeekStart(d => addDays(d, 7)) - const isCurrentWeek = fmt(startOfWeek(new Date())) === fmt(weekStart) + const prev = () => setFromStr(s => addDaysStr(s, -7)) + const next = () => setFromStr(s => addDaysStr(s, 7)) + const isCurrentWeek = fromStr === mondayOf(new Date()) - // Totals - const deptTotals = depts.map(dep => { - const cost = Object.values(dep.days).reduce((s, d) => s + d.cost, 0) - return { department_name: dep.department_name, cost } - }).sort((a, b) => b.cost - a.cost) + const deptTotals = depts.map(dep => ({ + department_name: dep.department_name, + cost: Object.values(dep.days).reduce((s, d) => s + d.cost, 0), + })).sort((a, b) => b.cost - a.cost) const totalWages = deptTotals.reduce((s, d) => s + d.cost, 0) const pctBudget = budget != null && budget > 0 ? (totalWages / budget) * 100 : null const pctSales = netSales > 0 ? (totalWages / netSales) * 100 : null - const weekLabel = `${weekStart.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' })} – ${weekEnd.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })}` + const weekLabel = `${fmtDisplay(fromStr)} – ${fmtDisplay(toStr)} ${new Date(toStr + 'T00:00:00').getFullYear()}` return (

Weekly Wages

-
- -
+
@@ -116,7 +118,6 @@ export default function Weekly() {
- {/* Summary cards */}
Total Wages