2026-06-22 02:48:52 +02:00
const euro = ( value ) => ` € ${ Number ( value ) . toFixed ( 2 ) } ` ;
async function loadDashboard ( period ) {
const query = period ? ` ?period= ${ encodeURIComponent ( period ) } ` : "" ;
const response = await fetch ( ` /api/dashboard ${ query } ` ) ;
if ( ! response . ok ) throw new Error ( "Failed to load dashboard" ) ;
return response . json ( ) ;
}
function setBadge ( el , status ) {
el . textContent = status ;
2026-06-22 03:09:44 +02:00
el . active = status === "generating" || status === "neutral" ;
el . draft = status === "burning" ;
2026-06-22 02:48:52 +02:00
}
2026-06-22 03:09:44 +02:00
function renderMetrics ( data ) {
2026-06-22 02:48:52 +02:00
const { snapshot , liquidity } = data ;
const cards = [
{
label : "Remaining budget" ,
value : euro ( liquidity . remaining _budget ) ,
sub : ` ${ liquidity . months _tracked } months tracked ` ,
} ,
{
label : "Period net liquidity" ,
value : euro ( snapshot . period _net _liquidity ) ,
sub : snapshot . liquidity _status ,
} ,
{
label : "Active members" ,
value : snapshot . active _members ,
sub : data . members [ 0 ] ? . username ? ` @ ${ data . members [ 0 ] . username } ` : "—" ,
} ,
{
label : "Member payment (gross)" ,
value : euro ( snapshot . monthly _revenue ) ,
sub : ` Net ${ euro ( data . payments . at ( - 1 ) ? . net _amount ? ? 0 ) } after Stripe ` ,
} ,
{
label : "Infrastructure / month" ,
value : euro ( snapshot . monthly _infrastructure _cost ) ,
sub : ` Processing ${ euro ( snapshot . monthly _payment _processing _cost ) } ` ,
} ,
{
label : "Cumulative net liquidity" ,
value : euro ( liquidity . cumulative _net _liquidity ) ,
sub : liquidity . liquidity _status ,
} ,
] ;
2026-06-22 03:09:44 +02:00
document . getElementById ( "metric-grid" ) . innerHTML = cards
2026-06-22 02:48:52 +02:00
. map (
( card ) => `
2026-06-22 03:09:44 +02:00
< wn - card size = "sm" >
< wn - eyebrow slot = "header" > $ { card . label } < / w n - e y e b r o w >
< div class = "obs-metric__value" > $ { card . value } < / d i v >
< span slot = "footer" > $ { card . sub } < / s p a n >
< / w n - c a r d > `
2026-06-22 02:48:52 +02:00
)
. join ( "" ) ;
}
function renderBudget ( data ) {
const { liquidity } = data ;
const initial = Number ( liquidity . initial _budget ) ;
const remaining = Number ( liquidity . remaining _budget ) ;
const pct = Math . max ( 0 , Math . min ( 100 , ( remaining / initial ) * 100 ) ) ;
2026-06-22 03:09:44 +02:00
const banner = document . getElementById ( "budget-banner" ) ;
const caption = remaining >= 0 ? "Within allocated budget" : "Over allocated budget" ;
2026-06-22 02:48:52 +02:00
document . getElementById ( "budget-fill" ) . style . width = ` ${ pct } % ` ;
2026-06-22 03:09:44 +02:00
document . getElementById ( "budget-caption" ) . textContent = caption ;
banner . variant = remaining < initial * 0.25 ? "warn" : "info" ;
2026-06-22 02:48:52 +02:00
document . getElementById ( "budget-stats" ) . innerHTML = [
[ "Initial budget" , euro ( initial ) ] ,
[ "Cumulative member payments (net)" , euro ( liquidity . cumulative _member _payments ) ] ,
[ "Cumulative infrastructure" , euro ( liquidity . cumulative _infrastructure _cost ) ] ,
[ "Cumulative processing" , euro ( liquidity . cumulative _payment _processing _cost ) ] ,
]
. map (
2026-06-22 03:09:44 +02:00
( [ label , value ] ) => `
< wn - field - row label = "${label}" narrow >
< span class = "wn-field-row__value" > $ { value } < / s p a n >
< / w n - f i e l d - r o w > `
2026-06-22 02:48:52 +02:00
)
. join ( "" ) ;
}
function renderChart ( history ) {
const max = Math . max ( ... history . map ( ( row ) => Math . abs ( Number ( row . net _liquidity ) ) ) , 1 ) ;
document . getElementById ( "liquidity-chart" ) . innerHTML = history
. map ( ( row ) => {
const value = Number ( row . net _liquidity ) ;
const width = ( Math . abs ( value ) / max ) * 50 ;
2026-06-22 03:09:44 +02:00
const barClass = value < 0 ? "obs-liquidity-row__bar--neg" : "obs-liquidity-row__bar--pos" ;
const valueClass = value < 0 ? "obs-liquidity-row__value--neg" : "" ;
2026-06-22 02:48:52 +02:00
return `
2026-06-22 03:09:44 +02:00
< div class = "obs-liquidity-row" >
< div class = "obs-liquidity-row__period" > $ { row . period } < / d i v >
< div class = "obs-liquidity-row__bar-wrap" >
< div class = "obs-liquidity-row__bar ${barClass}" style = "width:${width}%" > < / d i v >
< / d i v >
< div class = "obs-liquidity-row__value ${valueClass}" > $ { euro ( value ) } < / d i v >
2026-06-22 02:48:52 +02:00
< / d i v > ` ;
} )
. join ( "" ) ;
}
function renderInfra ( data ) {
const domains = data . infrastructure . domains ? . domains ? ? [ ] ;
const servers = data . infrastructure . virtual _servers ? . servers ? ? [ ] ;
const stripe = data . infrastructure . stripe ? . membership ? ? { } ;
2026-06-22 03:09:44 +02:00
const payout = data . infrastructure . stripe ? . payout _account ? ? "payout" ;
2026-06-22 02:48:52 +02:00
const items = [
... domains . map (
( d ) =>
2026-06-22 03:09:44 +02:00
` <wn-field-row label=" ${ d . name } " narrow><span class="wn-field-row__value"> ${ d . monthly _eur } EUR/mo · ${ d . tld } </span></wn-field-row> `
2026-06-22 02:48:52 +02:00
) ,
... servers . map (
( s ) =>
2026-06-22 03:09:44 +02:00
` <wn-field-row label=" ${ s . name } " narrow><span class="wn-field-row__value"> ${ s . monthly _eur } EUR/mo · since ${ s . started } </span></wn-field-row> `
2026-06-22 02:48:52 +02:00
) ,
2026-06-22 03:09:44 +02:00
` <wn-field-row label="Stripe · ${ stripe . member _username ? ? "member" } " narrow><span class="wn-field-row__value"> ${ stripe . gross _monthly _eur } gross · ${ stripe . fee _monthly _eur } fee · ${ stripe . net _payout _monthly _eur } net to ${ payout } </span></wn-field-row> ` ,
2026-06-22 02:48:52 +02:00
] ;
2026-06-22 03:09:44 +02:00
document . getElementById ( "infra-stack" ) . innerHTML = ` <div class="obs-infra-list"> ${ items . join ( "" ) } </div> ` ;
2026-06-22 02:48:52 +02:00
}
function renderTables ( data ) {
document . getElementById ( "history-body" ) . innerHTML = data . history
. map (
( row ) => `
< tr >
2026-06-22 03:09:44 +02:00
< td class = "mono" > $ { row . period } < / t d >
< td class = "mono" > $ { row . active _members } < / t d >
< td class = "obs-num mono" > $ { euro ( row . gross _revenue ) } < / t d >
< td class = "obs-num mono" > $ { euro ( row . infrastructure _cost ) } < / t d >
< td class = "obs-num mono" > $ { euro ( row . payment _processing _cost ) } < / t d >
< td class = "obs-num mono" > $ { euro ( row . net _liquidity ) } < / t d >
2026-06-22 02:48:52 +02:00
< / t r > `
)
. join ( "" ) ;
document . getElementById ( "pricing-body" ) . innerHTML = data . pricing _models
. map (
( model ) => `
< tr >
2026-06-22 03:09:44 +02:00
< td class = "mono" > $ { model . id } < / t d >
2026-06-22 02:48:52 +02:00
< td > $ { model . name } < / t d >
2026-06-22 03:09:44 +02:00
< td class = "mono" > $ { model . model _type } < / t d >
< td > < wn - tag > $ { model . status } < / w n - t a g > < / t d >
2026-06-22 02:48:52 +02:00
< / t r > `
)
. join ( "" ) ;
}
function populatePeriods ( history , current ) {
const select = document . getElementById ( "period-select" ) ;
select . innerHTML = [ ... history ] . reverse ( ) . map (
( row ) => ` <option value=" ${ row . period } " ${ row . period === current ? "selected" : "" } > ${ row . period } </option> `
) ;
2026-06-22 03:09:44 +02:00
select . value = current ;
if ( ! select . _obsBound ) {
select . addEventListener ( "wn-change" , async ( event ) => {
const next = await loadDashboard ( event . detail . value ) ;
render ( next ) ;
} ) ;
select . _obsBound = true ;
}
}
async function loadDesignRefLabel ( ) {
try {
const response = await fetch ( "/ui/vendor/whynot-design/.whynot-design-ref" ) ;
if ( ! response . ok ) return ;
const ref = ( await response . text ( ) ) . trim ( ) . slice ( 0 , 7 ) ;
const label = document . getElementById ( "design-ref-label" ) ;
if ( label && ref ) label . textContent = ` whynot-design @ ${ ref } ` ;
} catch {
// optional footer detail
}
2026-06-22 02:48:52 +02:00
}
function render ( data ) {
document . getElementById ( "design-link" ) . href = data . design _reference ;
2026-06-22 03:09:44 +02:00
loadDesignRefLabel ( ) ;
const header = document . getElementById ( "page-header" ) ;
header . lede = ` Period ${ data . period } . Ledger-backed view of infrastructure spend, member payments, and remaining budget. Customer cost-pass-through billing is not active. ` ;
2026-06-22 02:48:52 +02:00
setBadge ( document . getElementById ( "liquidity-badge" ) , data . snapshot . liquidity _status ) ;
2026-06-22 03:09:44 +02:00
renderMetrics ( data ) ;
2026-06-22 02:48:52 +02:00
renderBudget ( data ) ;
renderChart ( data . history ) ;
renderInfra ( data ) ;
renderTables ( data ) ;
populatePeriods ( data . history , data . period ) ;
}
loadDashboard ( )
. then ( render )
. catch ( ( error ) => {
2026-06-22 03:09:44 +02:00
document . body . innerHTML = ` <main class="wn-main"><wn-banner variant="error" title="Load failed"> ${ error } </wn-banner></main> ` ;
2026-06-22 02:48:52 +02:00
} ) ;