diff --git a/src/app/dashboard/management/income-view.tsx b/src/app/dashboard/management/income-view.tsx index fcf114f..95ec7de 100644 --- a/src/app/dashboard/management/income-view.tsx +++ b/src/app/dashboard/management/income-view.tsx @@ -100,6 +100,11 @@ export function IncomeView({ board }: { board: ManagementBoard }) { const monthlyIncome = months.map((m) => ({ label: m.label, ...incomeBreakdown(jobs, [m.index]) })); const incomeChartData = incomeSeries(jobs, indexes, board.planYear); const totals = incomeBreakdown(jobs, indexes); + const sumSeries = (pick: (point: (typeof incomeChartData)[number]) => number | null) => { + const values = incomeChartData.map(pick).filter((value): value is number => value !== null); + return values.length ? values.reduce((total, value) => total + value, 0) : null; + }; + const seriesTotals = { actual: sumSeries((p) => p.actual), forecast: sumSeries((p) => p.forecast), notStarted: sumSeries((p) => p.notStarted) }; const forecastLabel = "Forecast"; return
@@ -108,10 +113,10 @@ export function IncomeView({ board }: { board: ManagementBoard }) { {(query || month !== "all") && }
- - - - + + + +
Period total: {money(totals.total)}}>

The chart shows individual monthly amounts. The period total sums all months in view. Red future sites stay out of the cashflow totals; their planned amount is the red bar in the chart. Click an amount in the table below to enter the actual.

diff --git a/src/app/dashboard/management/management-ui.tsx b/src/app/dashboard/management/management-ui.tsx index d8c18c0..1813122 100644 --- a/src/app/dashboard/management/management-ui.tsx +++ b/src/app/dashboard/management/management-ui.tsx @@ -41,8 +41,8 @@ export function Panel({ title, subtitle, action, children, className }: { title: return

{title}

{subtitle &&

{subtitle}

}
{action}
{children}
; } -export function Stat({ label, value, note, tone, compact }: { label: string; value: string; note?: string; tone?: "positive" | "negative"; compact?: boolean }) { - return

{label}

{value}
{note && {note}}
; +export function Stat({ label, value, note, tone, compact, accent }: { label: string; value: string; note?: string; tone?: "positive" | "negative"; compact?: boolean; accent?: string | string[] }) { + return
`${color} ${(i / accent.length) * 100}% ${((i + 1) / accent.length) * 100}%`).join(", ")})` : accent } as React.CSSProperties) : undefined}>

{label}

{value}
{note && {note}}
; } export function Change({ value, percentage = false }: { value: number; percentage?: boolean }) { diff --git a/src/app/dashboard/management/management.css b/src/app/dashboard/management/management.css index 85caa5b..901725d 100644 --- a/src/app/dashboard/management/management.css +++ b/src/app/dashboard/management/management.css @@ -30,6 +30,8 @@ .mgmt-hero .is-negative { color:#ff9fa6; } .mgmt-stat-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:1rem; margin-bottom:1.5rem; } .mgmt-stat-grid .mgmt-stat { border:1px solid var(--border); background:var(--card); border-radius:12px; padding:1.2rem; } +.mgmt-stat-grid .mgmt-stat.has-accent { position:relative; overflow:hidden; } +.mgmt-stat-grid .mgmt-stat.has-accent::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--stat-accent); } .mgmt-stat.is-compact .mgmt-stat-value { font-size:1.65rem; } .mgmt-panel { min-width:0; padding:1.4rem; border:1px solid var(--border); background:var(--card); border-radius:12px; } .mgmt-panel-heading { display:flex; justify-content:space-between; align-items:flex-start; gap:1rem; margin-bottom:1.2rem; } @@ -97,7 +99,8 @@ .mgmt-heat-cell { display:block; border-radius:4px; padding:.55rem .45rem; min-width:60px; color:var(--foreground); } .mgmt-project-table td { padding:.45rem .25rem; } .mgmt-project-table td.mgmt-sticky { padding:.8rem .5rem; } -.mgmt-project-table th { padding:.7rem .4rem; } +.mgmt-project-table th { padding:.7rem .7rem .7rem .4rem; } +.mgmt-project-table .mgmt-cell-button { text-align:right; } .mgmt-project-total { font-weight:650; padding-right:.6rem!important; } .mgmt-project-summary { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:1rem; margin-bottom:1.5rem; } .mgmt-project-summary .mgmt-stat { border-left:1px solid var(--border); padding-left:1.1rem; } @@ -126,6 +129,8 @@ .mgmt-retention-table th.mgmt-sticky { text-align:left; font-size:.71rem; letter-spacing:0; color:var(--foreground); } .mgmt-retention-table td { padding:.85rem .8rem; } +.mgmt-retention-table th:not(.mgmt-sticky) { padding:.7rem .8rem; } +.mgmt-retention-table .mgmt-cell-button { text-align:right; margin:-1px; width:calc(100% + 2px); padding:0 1px; } .mgmt-retention-table .mgmt-project-meta { white-space:normal; max-width:280px; } .mgmt-cell-button { display:block; width:100%; border:1px solid transparent; border-radius:4px; background-color:transparent; font:inherit; color:inherit; text-align:center; cursor:pointer; } @@ -140,7 +145,7 @@ .mgmt-field { font-size:.72rem; color:var(--muted-foreground); } .mgmt-select { padding:.35rem .5rem; border:1px solid var(--border); border-radius:6px; background:var(--background); color:var(--foreground); font-size:.75rem; } -.mgmt-cell-sub { display:block; font-size:.58rem; font-style:italic; font-weight:400; color:var(--muted-foreground); margin-top:.1rem; white-space:nowrap; } +.mgmt-cell-sub { display:block; font-size:.58rem; font-style:italic; font-weight:400; color:#a855f7; margin-top:.1rem; white-space:nowrap; } .mgmt-edit-fields { display:grid; grid-template-columns:1fr 1fr; gap:.9rem; margin:1rem 0 .6rem; } .mgmt-edit-field { display:flex; flex-direction:column; gap:.3rem; font-size:.7rem; color:var(--muted-foreground); } .mgmt-edit-field .mgmt-cell-input { width:100%; }