Add 24-month dept split chart below rolling trend
Stacked bar chart (Rooms/Dry/Wet) for the previous 12 months and last 12 months side by side, with a reference line marking the boundary. No extra API calls — data comes from the existing 36-month fetch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4744e74dfa
commit
6a2990b34e
3 changed files with 69 additions and 5 deletions
|
|
@ -253,6 +253,24 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Monthly dept split for last 24 months (indices 12–35)
|
||||||
|
// — previous 12 months followed by last 12 months
|
||||||
|
const monthly_split = []
|
||||||
|
for (let i = 12; i < 36; i++) {
|
||||||
|
const tm = trendMonths[i]
|
||||||
|
const data = trendRevRaw[i]?.data ?? []
|
||||||
|
let rooms = 0, dry = 0, wet = 0
|
||||||
|
for (const d of data) {
|
||||||
|
rooms += parseFloat(d.accom?.otb ?? 0)
|
||||||
|
dry += parseFloat(d.dry?.otb ?? 0)
|
||||||
|
wet += parseFloat(d.wet?.otb ?? 0)
|
||||||
|
}
|
||||||
|
monthly_split.push({
|
||||||
|
label: `${MONTH_LABELS[tm.month - 1]}-${String(tm.year).slice(2)}`,
|
||||||
|
rooms, dry, wet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
week_ending: toDateStr(weekEndDate),
|
week_ending: toDateStr(weekEndDate),
|
||||||
week_start: toDateStr(weekStartDate),
|
week_start: toDateStr(weekStartDate),
|
||||||
|
|
@ -262,6 +280,7 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
occupancy,
|
occupancy,
|
||||||
month_progress,
|
month_progress,
|
||||||
monthly_trend,
|
monthly_trend,
|
||||||
|
monthly_split,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ 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 } from '../types'
|
import type { WeeklyActualData, WeekSummaryRow, MonthSplitEntry } from '../types'
|
||||||
|
|
||||||
// ── Formatters ─────────────────────────────────────────────────────────────
|
// ── Formatters ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -255,13 +256,47 @@ function OccupancyTable({ occupancy }: { occupancy: WeeklyActualData['occupancy'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Section: Monthly Dept Split (24 months) ────────────────────────────────
|
||||||
|
|
||||||
|
function MonthSplitChart({ splits }: { splits: MonthSplitEntry[] }) {
|
||||||
|
// Mark the boundary between previous-12 and last-12
|
||||||
|
const boundaryLabel = splits[11]?.label
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wa-chart-card" style={{ marginTop: 0 }}>
|
||||||
|
<h4>Monthly Turnover by Dept — Previous 12 & Last 12 Months</h4>
|
||||||
|
<ResponsiveContainer width="100%" height={240}>
|
||||||
|
<BarChart data={splits} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" vertical={false} />
|
||||||
|
<XAxis dataKey="label" tick={{ fontSize: 8 }} interval={1} />
|
||||||
|
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
||||||
|
<Tooltip formatter={fmtChartCcy} />
|
||||||
|
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
||||||
|
{boundaryLabel && (
|
||||||
|
<ReferenceLine
|
||||||
|
x={boundaryLabel}
|
||||||
|
stroke="#6b7280"
|
||||||
|
strokeDasharray="4 3"
|
||||||
|
label={{ value: '← prev 12 | last 12 →', position: 'top', fontSize: 9, fill: '#6b7280' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Section: Month Progress ────────────────────────────────────────────────
|
// ── Section: Month Progress ────────────────────────────────────────────────
|
||||||
|
|
||||||
function MonthProgressSection({
|
function MonthProgressSection({
|
||||||
mp, trend,
|
mp, trend, splits,
|
||||||
}: {
|
}: {
|
||||||
mp: WeeklyActualData['month_progress']
|
mp: WeeklyActualData['month_progress']
|
||||||
trend: WeeklyActualData['monthly_trend']
|
trend: WeeklyActualData['monthly_trend']
|
||||||
|
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 },
|
||||||
|
|
@ -366,6 +401,8 @@ function MonthProgressSection({
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MonthSplitChart splits={splits} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -467,7 +504,7 @@ 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} />
|
<MonthProgressSection mp={data.month_progress} trend={data.monthly_trend} splits={data.monthly_split} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Utilities ────────────────────────────────────────────── */}
|
{/* ── Utilities ────────────────────────────────────────────── */}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,13 @@ export interface MonthTrend {
|
||||||
last_year_avg: number
|
last_year_avg: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MonthSplitEntry {
|
||||||
|
label: string
|
||||||
|
rooms: number
|
||||||
|
dry: number
|
||||||
|
wet: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface WeeklyActualData {
|
export interface WeeklyActualData {
|
||||||
week_ending: string
|
week_ending: string
|
||||||
week_start: string
|
week_start: string
|
||||||
|
|
@ -118,6 +125,7 @@ export interface WeeklyActualData {
|
||||||
}
|
}
|
||||||
month_progress: MonthProgress
|
month_progress: MonthProgress
|
||||||
monthly_trend: MonthTrend[]
|
monthly_trend: MonthTrend[]
|
||||||
|
monthly_split: MonthSplitEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Directors Forecast types ────────────────────────────────────────────────
|
// ── Directors Forecast types ────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue