updated some UI elements and calculations
This commit is contained in:
parent
409d2560bc
commit
55d7c736c9
13 changed files with 378 additions and 121 deletions
|
|
@ -14,7 +14,7 @@ import { ChartSidebar } from '@/components/charts/ChartSidebar';
|
|||
import { ResizableSplit } from '@/components/layout/ResizableSplit';
|
||||
import { Modal, ConfirmDialog } from '@/components/common/Modal';
|
||||
import { buildHierarchyForRange, buildHierarchy, aggregate } from '@/lib/stats/aggregate';
|
||||
import { fmtMoney, todayISO } from '@/lib/format';
|
||||
import { fmtMoney, todayISO, nowInTZ } from '@/lib/format';
|
||||
|
||||
function yearStart() { return `${new Date().getFullYear()}-01-01`; }
|
||||
|
||||
|
|
@ -148,6 +148,10 @@ function WorkTab({ startDate, setStartDate }: { startDate: string; setStartDate:
|
|||
onView={(n) => setEditing(n.entry as WorkEntry)}
|
||||
onEdit={(n) => setEditing(n.entry as WorkEntry)}
|
||||
onDelete={(n) => setDeleting(n.entry!.id)}
|
||||
onToggleOutstanding={(n) => {
|
||||
const e = n.entry as WorkEntry;
|
||||
updateWorkEntry(e.id, { paymentOutstanding: !e.paymentOutstanding });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -606,13 +610,19 @@ const LEDGER_TILE_LABELS: Record<LedgerTile, string> = {
|
|||
avgMonth: 'Avg / month',
|
||||
yearProj: 'Year projected',
|
||||
thisMonth: 'This month',
|
||||
avgDay: 'Avg / day',
|
||||
avgDay: 'YTD daily avg',
|
||||
monthProj: 'Month projected',
|
||||
today: 'Today',
|
||||
};
|
||||
|
||||
const ALL_LEDGER_TILES: LedgerTile[] = ['ytd', 'avgMonth', 'yearProj', 'thisMonth', 'avgDay', 'monthProj', 'today'];
|
||||
|
||||
const METRIC_COLOR: Record<'workValue' | 'payments' | 'expenses', string> = {
|
||||
workValue: 'positive',
|
||||
payments: 'positive',
|
||||
expenses: 'negative',
|
||||
};
|
||||
|
||||
function PeriodSummaryRow({
|
||||
stats,
|
||||
metric,
|
||||
|
|
@ -626,10 +636,8 @@ function PeriodSummaryRow({
|
|||
tiles: LedgerTile[];
|
||||
onConfigure: () => void;
|
||||
}) {
|
||||
const now = new Date();
|
||||
const currentYear = String(now.getFullYear());
|
||||
const currentMonth = now.toISOString().slice(0, 7);
|
||||
const today = now.toISOString().slice(0, 10);
|
||||
const { year: nowYear, monthIdx, day: nowDay, isoDate: today, isoMonth: currentMonth } = nowInTZ();
|
||||
const currentYear = String(nowYear);
|
||||
|
||||
const y = stats.years.find((x) => x.label === currentYear);
|
||||
const m = stats.months.get(currentMonth);
|
||||
|
|
@ -638,19 +646,23 @@ function PeriodSummaryRow({
|
|||
const yValue = y?.[metric] ?? 0;
|
||||
const mValue = m?.[metric] ?? 0;
|
||||
|
||||
const monthsElapsed = now.getMonth() + 1;
|
||||
const dayOfMonth = now.getDate();
|
||||
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
|
||||
const yearStart = new Date(now.getFullYear(), 0, 1).getTime();
|
||||
const yearEnd = new Date(now.getFullYear() + 1, 0, 1).getTime();
|
||||
const yearFrac = (now.getTime() - yearStart) / (yearEnd - yearStart);
|
||||
const monthsElapsed = monthIdx + 1;
|
||||
const dayOfMonth = nowDay;
|
||||
const daysInMonth = new Date(nowYear, monthIdx + 1, 0).getDate();
|
||||
// Days elapsed since Jan 1 inclusive, using noon-UTC to avoid DST drift
|
||||
const jan1Noon = Date.parse(`${nowYear}-01-01T12:00:00Z`);
|
||||
const todayNoon = Date.parse(`${today}T12:00:00Z`);
|
||||
const daysElapsed = Math.max(1, Math.floor((todayNoon - jan1Noon) / 86400000) + 1);
|
||||
const daysInYear = (nowYear % 4 === 0 && (nowYear % 100 !== 0 || nowYear % 400 === 0)) ? 366 : 365;
|
||||
|
||||
const ytdDailyAvg = yValue / daysElapsed;
|
||||
|
||||
const tileValues: Record<LedgerTile, number | null> = {
|
||||
ytd: yValue,
|
||||
avgMonth: yValue > 0 ? yValue / monthsElapsed : null,
|
||||
yearProj: yValue > 0 && yearFrac > 0 && yearFrac < 1 ? yValue / yearFrac : null,
|
||||
yearProj: yValue > 0 ? ytdDailyAvg * daysInYear : null,
|
||||
thisMonth: mValue,
|
||||
avgDay: mValue > 0 ? mValue / dayOfMonth : null,
|
||||
avgDay: yValue > 0 ? ytdDailyAvg : null,
|
||||
monthProj: mValue > 0 && dayOfMonth > 0 && dayOfMonth < daysInMonth
|
||||
? (mValue / dayOfMonth) * daysInMonth
|
||||
: null,
|
||||
|
|
@ -671,16 +683,16 @@ function PeriodSummaryRow({
|
|||
{tiles.map((t) => {
|
||||
const value = tileValues[t];
|
||||
if (value == null) return null;
|
||||
return <StatTile key={t} label={tileDisplayLabel[t]} value={value} />;
|
||||
return <StatTile key={t} label={tileDisplayLabel[t]} value={value} className={METRIC_COLOR[metric]} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatTile({ label, value }: { label: string; value: number }) {
|
||||
function StatTile({ label, value, className = '' }: { label: string; value: number; className?: string }) {
|
||||
return (
|
||||
<div className="stat-card">
|
||||
<div className={`stat-card ${className}`}>
|
||||
<div className="stat-label">{label}</div>
|
||||
<div className="stat-value">{fmtMoney(value)}</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue