diff --git a/apps/dashboard/src/components/bi/BiCharts.tsx b/apps/dashboard/src/components/bi/BiCharts.tsx new file mode 100644 index 0000000..85cf48f --- /dev/null +++ b/apps/dashboard/src/components/bi/BiCharts.tsx @@ -0,0 +1,232 @@ +import { + Bar, + BarChart, + CartesianGrid, + Cell, + Legend, + Line, + LineChart, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { BI, BI_SERIES, ROOM_STATUS_COLORS, chartTooltipStyle } from './chartTheme'; + +export function RoomStatusDonut({ + data, + emptyLabel, +}: { + data: { name: string; status: string; value: number }[]; + emptyLabel: string; +}) { + if (!data.length) { + return ( +
{emptyLabel}
; + } + return ( +{emptyLabel}
; + } + return ( +{emptyLabel}
; + } + return ( +{emptyLabel}
; + } + return ( +{subtitle}
+{dateLabel}
++ {title} +
++ {value} +
+ {subtitle &&{subtitle}
} + {trend && ( += 0 ? 'text-telivity-dark-teal' : 'text-telivity-orange' + }`} + > + {trend.value >= 0 ? '▲' : '▼'} {Math.abs(trend.value).toFixed(1)}% {trend.label} +
+ )} +{subtitle}
} +| {t('dashboard.portfolio.property')} | -{t('dashboard.occupancy')} | -{t('dashboard.adr')} | -{t('dashboard.revpar')} | -{t('dashboard.portfolio.revenue')} | -||||||
|---|---|---|---|---|---|---|---|---|---|---|
| - - | -{formatOccupancyPercent(row.occupancyRate)} | -{formatMoney(row.adr, currencyCode)} | -{formatMoney(row.revpar, currencyCode)} | -{formatMoney(row.totalRevenue, currencyCode)} | +
| {t('dashboard.portfolio.property')} | +{t('dashboard.occupancy')} | +{t('dashboard.portfolio.revenue')} |
|---|
{Array.isArray(arrList) ? arrList.length : 0}
-{t('dashboard.arrivals')}
-{Array.isArray(ihList) ? ihList.length : 0}
-{t('dashboard.inHouse')}
-{Array.isArray(depList) ? depList.length : 0}
-{t('dashboard.departures')}
-+ {t('dashboard.panels.openFolioBalance')} +
++ {money(Number(outstanding.totalBalanceDue ?? 0))} +
++ {t('dashboard.panels.openFolios', { + count: Number(outstanding.totalFoliosOpen ?? 0), + })} +
{totalRooms - occupiedCount}
-{t('dashboard.availableRooms')}
++ {t('dashboard.panels.lastAudit')} +
++ {audit.lastAuditDate + ? `${audit.lastAuditDate} · ${audit.lastAuditStatus ?? '—'}` + : t('dashboard.panels.noAudit')} +
+ {t(labelKey)} +
++ {money(Number(agingBuckets[key] ?? 0))} +
+{t('dashboard.hk.noUrgent')}
+ )} + +{row.value}
+{row.label}
+{t('dashboard.waitingForEvents')}
)} -{t('reports.portfolioNotice')}
-{t('reports.selectSingleProperty')}
-{t('reports.pickReport')}
++ {t('reports.pickReportHint')} +
{t('reports.portfolioNotice')}
+{t('reports.selectSingleProperty')}
| {t('reports.property')} | -{t('reports.revenue')} | -{t('reports.occupancy')} | -ADR | -RevPAR | -
|---|---|---|---|---|
| {propertyNameMap.get(row.propertyId) ?? row.propertyId} | -{formatMoney(row.totalRevenue, currencyCode)} | -{formatOccupancyPercent(row.occupancyRate)} | -{formatMoney(row.adr, currencyCode)} | -{formatMoney(row.revpar, currencyCode)} | -
| {t('reports.property')} | +{t('reports.revenue')} | +{t('reports.occupancy')} | +{t('dashboard.adr')} | +{t('dashboard.revpar')} | +
|---|---|---|---|---|
| + {propertyNameMap.get(row.propertyId) ?? row.propertyId} + | +{money(row.totalRevenue)} | +{formatOccupancyPercent(row.occupancyRate)} | +{money(row.adr)} | +{money(row.revpar)} | +
| Property | -Occupied | -Available | -Occupancy | -Arrivals | -
|---|---|---|---|---|
| {propertyNameMap.get(row.propertyId) ?? row.propertyId} | -{row.occupiedRooms} | -{row.availableRooms} | -{formatOccupancyPercent(row.occupancyRate)} | -{row.arrivals} | -
| {t('reports.property')} | +{t('reports.occupied')} | +{t('reports.available')} | +{t('reports.occupancy')} | +{t('reports.arrivals')} | +
|---|---|---|---|---|
| + {propertyNameMap.get(row.propertyId) ?? row.propertyId} + | +{row.occupiedRooms} | +{row.availableRooms} | +{formatOccupancyPercent(row.occupancyRate)} | +{row.arrivals} | +
{t('reports.noTrend')}
+ {report === 'occupancy-trend' && ( +| {t('reports.trialBalanceLedger')} | -{t('reports.trialBalanceOpening')} | -{t('reports.trialBalanceNetActivity')} | -{t('reports.trialBalanceTransfersIn')} | -{t('reports.trialBalanceTransfersOut')} | -{t('reports.trialBalanceClosing')} | -
|---|---|---|---|---|---|
| {t(`reports.${labelKey}`)} | -{formatMoney(row.opening, currencyCode)} | -{formatMoney(row.netActivity, currencyCode)} | -{formatMoney(row.transfersIn, currencyCode)} | -{formatMoney(row.transfersOut, currencyCode)} | -{formatMoney(row.closing, currencyCode)} | -
| {t('reports.trialBalanceLedger')} | ++ {t('reports.trialBalanceOpening')} + | ++ {t('reports.trialBalanceNetActivity')} + | ++ {t('reports.trialBalanceTransfersIn')} + | ++ {t('reports.trialBalanceTransfersOut')} + | ++ {t('reports.trialBalanceClosing')} + | +
|---|---|---|---|---|---|
| + {t(`reports.${labelKey}`)} + | +{money(row.opening)} | +{money(row.netActivity)} | +{money(row.transfersIn)} | +{money(row.transfersOut)} | +{money(row.closing)} | +
+ {money(reportData.interLedgerTransfers)} +
+| {t('reports.date')} | -{t('reports.added')} | -{t('reports.lost')} | -{t('reports.netPickup')} | -
|---|---|---|---|
| {row.date} | -+{row.roomNightsAdded} | --{row.roomNightsLost} | -{row.netPickup >= 0 ? `+${row.netPickup}` : row.netPickup} | -
| {t('reports.date')} | +{t('reports.added')} | +{t('reports.lost')} | +{t('reports.netPickup')} | +
|---|---|---|---|
| {row.date} | ++ +{row.roomNightsAdded} + | ++ -{row.roomNightsLost} + | ++ {row.netPickup >= 0 ? `+${row.netPickup}` : row.netPickup} + | +
{t('reports.noPickup')}
+ )}{t('reports.noPickup')}
)} -{t('reports.noPace')}
- )} -