Restructure weekly actuals charts — Sales History section + month cumulative
Backend:
- Always fetch full calendar month (not truncated at week ending)
- Add month_daily[] to response (per-day TY/LY by dept, is_actual flag)
Frontend:
- New 'Sales History' section with:
- Monthly dept line chart: 12-month x-axis, TY vs prior year per dept
(Rooms/Dry/Wet solid, LY dashed — same colour per dept)
- Rolling 12-month average trend (moved from Month Progress)
- Month Progress section gains cumulative line chart:
- X-axis = day of month, 6 lines (3 TY solid + 3 LY dashed)
- TY lines stop at week ending; LY spans full month
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7f0d1057c1
commit
ffa13d6e57
3 changed files with 165 additions and 65 deletions
|
|
@ -88,9 +88,9 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
// 5-week window: start 4 weeks before this week's Sunday
|
// 5-week window: start 4 weeks before this week's Sunday
|
||||||
const fiveWeekStart = addDays(weekStartDate, -28)
|
const fiveWeekStart = addDays(weekStartDate, -28)
|
||||||
|
|
||||||
// Month to date: 1st of month → week ending date
|
// Full calendar month (needed for daily chart — month progress filters by date)
|
||||||
const monthStart = new Date(year, month - 1, 1)
|
const monthStart = new Date(year, month - 1, 1)
|
||||||
const monthDays = Math.round((weekEndDate - monthStart) / 86400000) + 1
|
const daysInMonth = new Date(year, month, 0).getDate()
|
||||||
|
|
||||||
// 36 months of raw totals needed to compute rolling 12-month averages for
|
// 36 months of raw totals needed to compute rolling 12-month averages for
|
||||||
// 12 display points (indices 23–35) plus their LY equivalents (indices 0–23)
|
// 12 display points (indices 23–35) plus their LY equivalents (indices 0–23)
|
||||||
|
|
@ -106,10 +106,8 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
fcFetch(`/forecast/revenue?start_date=${toDateStr(fiveWeekStart)}&days=35&type=all`),
|
fcFetch(`/forecast/revenue?start_date=${toDateStr(fiveWeekStart)}&days=35&type=all`),
|
||||||
// This week rooms (for occupancy table)
|
// This week rooms (for occupancy table)
|
||||||
fcFetch(`/forecast/rooms?start_date=${toDateStr(weekStartDate)}&days=7`),
|
fcFetch(`/forecast/rooms?start_date=${toDateStr(weekStartDate)}&days=7`),
|
||||||
// Month to date revenue
|
// Full month revenue (used for month progress totals + daily chart)
|
||||||
monthDays > 0
|
fcFetch(`/forecast/revenue?start_date=${toDateStr(monthStart)}&days=${daysInMonth}&type=all`),
|
||||||
? fcFetch(`/forecast/revenue?start_date=${toDateStr(monthStart)}&days=${monthDays}&type=all`)
|
|
||||||
: Promise.resolve({ data: [] }),
|
|
||||||
// 12 monthly trend revenue calls
|
// 12 monthly trend revenue calls
|
||||||
...trendMonths.map(({ year: y, month: m }) => {
|
...trendMonths.map(({ year: y, month: m }) => {
|
||||||
const firstDay = new Date(y, m - 1, 1)
|
const firstDay = new Date(y, m - 1, 1)
|
||||||
|
|
@ -118,11 +116,11 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
const [fiveWeekRevRaw, thisWeekRoomsRaw, monthRevRaw, ...trendRevRaw] = results
|
const [fiveWeekRevRaw, thisWeekRoomsRaw, fullMonthRevRaw, ...trendRevRaw] = results
|
||||||
|
|
||||||
const fiveWeekRevMap = buildDayMap(fiveWeekRevRaw)
|
const fiveWeekRevMap = buildDayMap(fiveWeekRevRaw)
|
||||||
const thisWeekRoomsMap = buildDayMap(thisWeekRoomsRaw)
|
const thisWeekRoomsMap = buildDayMap(thisWeekRoomsRaw)
|
||||||
const monthRevMap = buildDayMap(monthRevRaw)
|
const fullMonthRevMap = buildDayMap(fullMonthRevRaw)
|
||||||
|
|
||||||
// ── Five week summaries ────────────────────────────────────────────────────
|
// ── Five week summaries ────────────────────────────────────────────────────
|
||||||
const fiveWeeks = [0, 1, 2, 3, 4].map(w => {
|
const fiveWeeks = [0, 1, 2, 3, 4].map(w => {
|
||||||
|
|
@ -181,11 +179,11 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
let mBud = { rooms: 0, dry: 0, wet: 0 }
|
let mBud = { rooms: 0, dry: 0, wet: 0 }
|
||||||
let mLy = { rooms: 0, dry: 0, wet: 0 }
|
let mLy = { rooms: 0, dry: 0, wet: 0 }
|
||||||
|
|
||||||
for (let i = 0; i < monthDays; i++) {
|
for (let i = 0; i < daysInMonth; i++) {
|
||||||
const d = addDays(monthStart, i)
|
const d = addDays(monthStart, i)
|
||||||
if (d > weekEndDate) break
|
if (d > weekEndDate) break
|
||||||
const dateStr = toDateStr(d)
|
const dateStr = toDateStr(d)
|
||||||
const rev = monthRevMap[dateStr] || {}
|
const rev = fullMonthRevMap[dateStr] || {}
|
||||||
|
|
||||||
mNet.rooms += parseFloat(rev.accom?.otb ?? 0)
|
mNet.rooms += parseFloat(rev.accom?.otb ?? 0)
|
||||||
mNet.dry += parseFloat(rev.dry?.otb ?? 0)
|
mNet.dry += parseFloat(rev.dry?.otb ?? 0)
|
||||||
|
|
@ -224,6 +222,26 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Month daily (for cumulative progress line chart) ──────────────────────
|
||||||
|
const weekEndStr = toDateStr(weekEndDate)
|
||||||
|
const month_daily = []
|
||||||
|
for (let i = 0; i < daysInMonth; i++) {
|
||||||
|
const d = addDays(monthStart, i)
|
||||||
|
const dateStr = toDateStr(d)
|
||||||
|
const rev = fullMonthRevMap[dateStr] || {}
|
||||||
|
month_daily.push({
|
||||||
|
day: i + 1,
|
||||||
|
date: dateStr,
|
||||||
|
ty_rooms: parseFloat(rev.accom?.otb ?? 0),
|
||||||
|
ty_dry: parseFloat(rev.dry?.otb ?? 0),
|
||||||
|
ty_wet: parseFloat(rev.wet?.otb ?? 0),
|
||||||
|
ly_rooms: parseFloat(rev.accom?.prior_final ?? 0),
|
||||||
|
ly_dry: parseFloat(rev.dry?.prior_final ?? 0),
|
||||||
|
ly_wet: parseFloat(rev.wet?.prior_final ?? 0),
|
||||||
|
is_actual: dateStr <= weekEndStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── Monthly trend (rolling 12-month average) ──────────────────────────────
|
// ── Monthly trend (rolling 12-month average) ──────────────────────────────
|
||||||
// Build raw monthly totals for all 36 months
|
// Build raw monthly totals for all 36 months
|
||||||
const allMonthTotals = trendMonths.map((_, i) => {
|
const allMonthTotals = trendMonths.map((_, i) => {
|
||||||
|
|
@ -279,6 +297,7 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
five_weeks: fiveWeeks,
|
five_weeks: fiveWeeks,
|
||||||
occupancy,
|
occupancy,
|
||||||
month_progress,
|
month_progress,
|
||||||
|
month_daily,
|
||||||
monthly_trend,
|
monthly_trend,
|
||||||
monthly_split,
|
monthly_split,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ import {
|
||||||
PieChart, Pie, Cell,
|
PieChart, Pie, Cell,
|
||||||
LineChart, Line,
|
LineChart, Line,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
ReferenceLine,
|
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
import { getWeeklyActual } from '../api'
|
import { getWeeklyActual } from '../api'
|
||||||
import type { WeeklyActualData, WeekSummaryRow, MonthSplitEntry } from '../types'
|
import type { WeeklyActualData, WeekSummaryRow, MonthSplitEntry, MonthDailyEntry } from '../types'
|
||||||
|
|
||||||
// ── Formatters ─────────────────────────────────────────────────────────────
|
// ── Formatters ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -53,18 +52,16 @@ function shiftWeek(dateStr: string, weeks: number): string {
|
||||||
// ── Chart colours ──────────────────────────────────────────────────────────
|
// ── Chart colours ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const CHART_COLORS = {
|
const CHART_COLORS = {
|
||||||
rooms: '#c9a84c',
|
rooms: '#c9a84c',
|
||||||
dry: '#2563eb',
|
dry: '#2563eb',
|
||||||
wet: '#059669',
|
wet: '#059669',
|
||||||
thisWeek: '#c9a84c',
|
thisWeek: '#c9a84c',
|
||||||
budget: '#9ca3af',
|
budget: '#9ca3af',
|
||||||
lastYear: '#d1d5db',
|
lastYear: '#d1d5db',
|
||||||
}
|
}
|
||||||
|
|
||||||
const PIE_COLORS = [CHART_COLORS.rooms, CHART_COLORS.dry, CHART_COLORS.wet]
|
const PIE_COLORS = [CHART_COLORS.rooms, CHART_COLORS.dry, CHART_COLORS.wet]
|
||||||
|
|
||||||
// ── Tooltip formatter ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const fmtChartCcy = (v: any): string => {
|
const fmtChartCcy = (v: any): string => {
|
||||||
if (v == null || Array.isArray(v)) return '—'
|
if (v == null || Array.isArray(v)) return '—'
|
||||||
|
|
@ -256,34 +253,49 @@ function OccupancyTable({ occupancy }: { occupancy: WeeklyActualData['occupancy'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Section: Monthly Dept Split (24 months) ────────────────────────────────
|
// ── Chart: Month Cumulative Progress vs LY ─────────────────────────────────
|
||||||
|
|
||||||
function MonthSplitChart({ splits }: { splits: MonthSplitEntry[] }) {
|
function MonthProgressLineChart({ daily }: { daily: MonthDailyEntry[] }) {
|
||||||
// Mark the boundary between previous-12 and last-12
|
let cumTyRooms = 0, cumTyDry = 0, cumTyWet = 0
|
||||||
const boundaryLabel = splits[11]?.label
|
let cumLyRooms = 0, cumLyDry = 0, cumLyWet = 0
|
||||||
|
|
||||||
|
const chartData = daily.map(d => {
|
||||||
|
cumLyRooms += d.ly_rooms
|
||||||
|
cumLyDry += d.ly_dry
|
||||||
|
cumLyWet += d.ly_wet
|
||||||
|
if (d.is_actual) {
|
||||||
|
cumTyRooms += d.ty_rooms
|
||||||
|
cumTyDry += d.ty_dry
|
||||||
|
cumTyWet += d.ty_wet
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label: String(d.day),
|
||||||
|
ty_rooms: d.is_actual ? cumTyRooms : null,
|
||||||
|
ty_dry: d.is_actual ? cumTyDry : null,
|
||||||
|
ty_wet: d.is_actual ? cumTyWet : null,
|
||||||
|
ly_rooms: cumLyRooms,
|
||||||
|
ly_dry: cumLyDry,
|
||||||
|
ly_wet: cumLyWet,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wa-chart-card" style={{ marginTop: 0 }}>
|
<div className="wa-chart-card">
|
||||||
<h4>Monthly Turnover by Dept — Previous 12 & Last 12 Months</h4>
|
<h4>Month Cumulative by Dept — vs Last Year</h4>
|
||||||
<ResponsiveContainer width="100%" height={240}>
|
<ResponsiveContainer width="100%" height={220}>
|
||||||
<BarChart data={splits} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
<LineChart data={chartData} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||||
<XAxis dataKey="label" tick={{ fontSize: 8 }} interval={1} />
|
<XAxis dataKey="label" tick={{ fontSize: 9 }} />
|
||||||
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
||||||
<Tooltip formatter={fmtChartCcy} />
|
<Tooltip formatter={fmtChartCcy} />
|
||||||
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
||||||
{boundaryLabel && (
|
<Line type="monotone" dataKey="ty_rooms" stroke={CHART_COLORS.rooms} strokeWidth={2.5} dot={false} name="Rooms TY" />
|
||||||
<ReferenceLine
|
<Line type="monotone" dataKey="ty_dry" stroke={CHART_COLORS.dry} strokeWidth={2.5} dot={false} name="Dry TY" />
|
||||||
x={boundaryLabel}
|
<Line type="monotone" dataKey="ty_wet" stroke={CHART_COLORS.wet} strokeWidth={2.5} dot={false} name="Wet TY" />
|
||||||
stroke="#6b7280"
|
<Line type="monotone" dataKey="ly_rooms" stroke={CHART_COLORS.rooms} strokeWidth={1.5} dot={false} name="Rooms LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
strokeDasharray="4 3"
|
<Line type="monotone" dataKey="ly_dry" stroke={CHART_COLORS.dry} strokeWidth={1.5} dot={false} name="Dry LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
label={{ value: '← prev 12 | last 12 →', position: 'top', fontSize: 9, fill: '#6b7280' }}
|
<Line type="monotone" dataKey="ly_wet" stroke={CHART_COLORS.wet} strokeWidth={1.5} dot={false} name="Wet LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
/>
|
</LineChart>
|
||||||
)}
|
|
||||||
<Bar dataKey="rooms" stackId="a" fill={CHART_COLORS.rooms} name="Rooms" />
|
|
||||||
<Bar dataKey="dry" stackId="a" fill={CHART_COLORS.dry} name="Dry" />
|
|
||||||
<Bar dataKey="wet" stackId="a" fill={CHART_COLORS.wet} name="Wet" radius={[2,2,0,0]} />
|
|
||||||
</BarChart>
|
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -292,11 +304,10 @@ function MonthSplitChart({ splits }: { splits: MonthSplitEntry[] }) {
|
||||||
// ── Section: Month Progress ────────────────────────────────────────────────
|
// ── Section: Month Progress ────────────────────────────────────────────────
|
||||||
|
|
||||||
function MonthProgressSection({
|
function MonthProgressSection({
|
||||||
mp, trend, splits,
|
mp, monthDaily,
|
||||||
}: {
|
}: {
|
||||||
mp: WeeklyActualData['month_progress']
|
mp: WeeklyActualData['month_progress']
|
||||||
trend: WeeklyActualData['monthly_trend']
|
monthDaily: MonthDailyEntry[]
|
||||||
splits: WeeklyActualData['monthly_split']
|
|
||||||
}) {
|
}) {
|
||||||
const deptBarData = [
|
const deptBarData = [
|
||||||
{ name: 'Rooms', 'Net Sales': mp.split.rooms.net, 'Budget Net': mp.split.rooms.budget_net, 'Last Year': mp.split.rooms.ly_net },
|
{ name: 'Rooms', 'Net Sales': mp.split.rooms.net, 'Budget Net': mp.split.rooms.budget_net, 'Last Year': mp.split.rooms.ly_net },
|
||||||
|
|
@ -368,7 +379,7 @@ function MonthProgressSection({
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Charts row */}
|
{/* Charts: dept bar + cumulative line */}
|
||||||
<div className="wa-charts-row" style={{ marginTop: 16 }}>
|
<div className="wa-charts-row" style={{ marginTop: 16 }}>
|
||||||
<div className="wa-chart-card">
|
<div className="wa-chart-card">
|
||||||
<h4>Month Progress — Dept vs Budget vs Last Year</h4>
|
<h4>Month Progress — Dept vs Budget vs Last Year</h4>
|
||||||
|
|
@ -386,27 +397,78 @@ function MonthProgressSection({
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="wa-chart-card">
|
<MonthProgressLineChart daily={monthDaily} />
|
||||||
<h4>Rolling 12-Month Avg Turnover — Trend</h4>
|
|
||||||
<ResponsiveContainer width="100%" height={220}>
|
|
||||||
<LineChart data={trend} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
|
||||||
<XAxis dataKey="label" tick={{ fontSize: 9 }} interval={1} />
|
|
||||||
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
|
||||||
<Tooltip formatter={fmtChartCcy} />
|
|
||||||
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
|
||||||
<Line type="monotone" dataKey="this_year_avg" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} name="This Year" />
|
|
||||||
<Line type="monotone" dataKey="last_year_avg" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} name="Last Year" strokeDasharray="5 3" />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonthSplitChart splits={splits} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Chart: Monthly Dept Lines — 12 months TY vs prior year ────────────────
|
||||||
|
|
||||||
|
function MonthlySplitLineChart({ splits }: { splits: MonthSplitEntry[] }) {
|
||||||
|
// splits[0..11] = prior 12 months (LY equivalent)
|
||||||
|
// splits[12..23] = last 12 months (TY)
|
||||||
|
const chartData = splits.slice(12).map((ty, i) => ({
|
||||||
|
label: ty.label,
|
||||||
|
ty_rooms: ty.rooms,
|
||||||
|
ty_dry: ty.dry,
|
||||||
|
ty_wet: ty.wet,
|
||||||
|
ly_rooms: splits[i].rooms,
|
||||||
|
ly_dry: splits[i].dry,
|
||||||
|
ly_wet: splits[i].wet,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wa-chart-card">
|
||||||
|
<h4>Monthly Turnover by Dept — Last 12 Months vs Prior Year</h4>
|
||||||
|
<ResponsiveContainer width="100%" height={260}>
|
||||||
|
<LineChart data={chartData} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||||
|
<XAxis dataKey="label" tick={{ fontSize: 9 }} />
|
||||||
|
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
||||||
|
<Tooltip formatter={fmtChartCcy} />
|
||||||
|
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
||||||
|
<Line type="monotone" dataKey="ty_rooms" stroke={CHART_COLORS.rooms} strokeWidth={2.5} dot={false} name="Rooms" />
|
||||||
|
<Line type="monotone" dataKey="ty_dry" stroke={CHART_COLORS.dry} strokeWidth={2.5} dot={false} name="Dry" />
|
||||||
|
<Line type="monotone" dataKey="ty_wet" stroke={CHART_COLORS.wet} strokeWidth={2.5} dot={false} name="Wet" />
|
||||||
|
<Line type="monotone" dataKey="ly_rooms" stroke={CHART_COLORS.rooms} strokeWidth={1.5} dot={false} name="Rooms LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
|
<Line type="monotone" dataKey="ly_dry" stroke={CHART_COLORS.dry} strokeWidth={1.5} dot={false} name="Dry LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
|
<Line type="monotone" dataKey="ly_wet" stroke={CHART_COLORS.wet} strokeWidth={1.5} dot={false} name="Wet LY" strokeDasharray="4 3" strokeOpacity={0.5} />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Sales History ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function SalesHistorySection({
|
||||||
|
trend, splits,
|
||||||
|
}: {
|
||||||
|
trend: WeeklyActualData['monthly_trend']
|
||||||
|
splits: WeeklyActualData['monthly_split']
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MonthlySplitLineChart splits={splits} />
|
||||||
|
<div className="wa-chart-card" style={{ marginTop: 16 }}>
|
||||||
|
<h4>Rolling 12-Month Avg Turnover — Trend</h4>
|
||||||
|
<ResponsiveContainer width="100%" height={220}>
|
||||||
|
<LineChart data={trend} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||||
|
<XAxis dataKey="label" tick={{ fontSize: 9 }} interval={1} />
|
||||||
|
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
||||||
|
<Tooltip formatter={fmtChartCcy} />
|
||||||
|
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
||||||
|
<Line type="monotone" dataKey="this_year_avg" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} name="This Year" />
|
||||||
|
<Line type="monotone" dataKey="last_year_avg" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} name="Last Year" strokeDasharray="5 3" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Section: Utilities Placeholder ─────────────────────────────────────────
|
// ── Section: Utilities Placeholder ─────────────────────────────────────────
|
||||||
|
|
||||||
function UtilitiesPlaceholder() {
|
function UtilitiesPlaceholder() {
|
||||||
|
|
@ -504,7 +566,13 @@ export default function WeeklyActual() {
|
||||||
<h2 className="wa-section-title">
|
<h2 className="wa-section-title">
|
||||||
{MONTH_NAMES[data.month_progress.month - 1]} {data.month_progress.year} — Month Progress to Date
|
{MONTH_NAMES[data.month_progress.month - 1]} {data.month_progress.year} — Month Progress to Date
|
||||||
</h2>
|
</h2>
|
||||||
<MonthProgressSection mp={data.month_progress} trend={data.monthly_trend} splits={data.monthly_split} />
|
<MonthProgressSection mp={data.month_progress} monthDaily={data.month_daily} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Sales History ───────────────────────────────────────── */}
|
||||||
|
<section className="wa-section">
|
||||||
|
<h2 className="wa-section-title">Sales History</h2>
|
||||||
|
<SalesHistorySection trend={data.monthly_trend} splits={data.monthly_split} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Utilities ────────────────────────────────────────────── */}
|
{/* ── Utilities ────────────────────────────────────────────── */}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,18 @@ export interface MonthTrend {
|
||||||
last_year_avg: number
|
last_year_avg: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MonthDailyEntry {
|
||||||
|
day: number
|
||||||
|
date: string
|
||||||
|
ty_rooms: number
|
||||||
|
ty_dry: number
|
||||||
|
ty_wet: number
|
||||||
|
ly_rooms: number
|
||||||
|
ly_dry: number
|
||||||
|
ly_wet: number
|
||||||
|
is_actual: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface MonthSplitEntry {
|
export interface MonthSplitEntry {
|
||||||
label: string
|
label: string
|
||||||
rooms: number
|
rooms: number
|
||||||
|
|
@ -124,6 +136,7 @@ export interface WeeklyActualData {
|
||||||
totals: OccTotals
|
totals: OccTotals
|
||||||
}
|
}
|
||||||
month_progress: MonthProgress
|
month_progress: MonthProgress
|
||||||
|
month_daily: MonthDailyEntry[]
|
||||||
monthly_trend: MonthTrend[]
|
monthly_trend: MonthTrend[]
|
||||||
monthly_split: MonthSplitEntry[]
|
monthly_split: MonthSplitEntry[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue