Files
budget/views/layout.php
T
Ty Clifford 51ee4ecb5a -
Finished the planner end to end.
Corrected rollover and yearly accounting, recurring targets, savings 
rules, and targeted dormant restorations.
Added editing/deletion for income, expenses, targets, and categories.
Rebuilt dashboard, analytics, calendar, settings, exports, 
authentication, and responsive mobile UI.
Added original artwork at 
[financial-flow.png](/Users/tyemeclifford/Documents/GH/budget/public/assets/images/financial-flow.png), 
generated with built-in Imagegen using an abstract financial-momentum 
landscape prompt.
Added 
[self-test.php](/Users/tyemeclifford/Documents/GH/budget/scripts/self-test.php).
Verification passed: automated accounting/export tests, full browser 
workflow, responsive 390px layout, light/dark themes, and no console 
errors. Tested with FrankenPHP v1.12.4. Live Mailgun delivery was not 
attempted without credentials.
2026-06-15 10:33:55 -04:00

117 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
$currentRoute = $route ?? (string) ($_GET['route'] ?? 'dashboard');
$currentMonth = $month ?? date('Y-m');
$previousMonth = date('Y-m', strtotime($currentMonth . '-01 -1 month'));
$nextMonth = date('Y-m', strtotime($currentMonth . '-01 +1 month'));
$appName = (string) $settings->get('app.name', 'Neon Ledger');
$defaultTheme = (string) $settings->get('theme.default', 'dark');
$user = $auth->user();
$navItems = [
['dashboard', 'home', 'Overview', true],
['transactions', 'expense', 'Transactions', true],
['calendar', 'calendar', 'Bill calendar', $settings->module('calendar')],
['comparisons', 'chart', 'Analytics', $settings->module('comparisons')],
['dead-account', 'vault', 'Dormant account', $settings->module('dead_account')],
];
?>
<!doctype html>
<html lang="en" data-theme="<?= h($defaultTheme) ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" content="#090b12">
<title><?= h($title ?? 'Planner') ?> · <?= h($appName) ?></title>
<link rel="stylesheet" href="/assets/app.css?v=<?= (int) filemtime(APP_ROOT . '/public/assets/app.css') ?>">
<script>document.documentElement.dataset.theme=localStorage.getItem('budget-theme')||<?= json_encode($defaultTheme) ?>;</script>
</head>
<body>
<div class="app-shell">
<aside class="sidebar" id="sidebar">
<a class="brand" href="<?= h(url('dashboard', ['month' => $currentMonth])) ?>">
<span class="brand-mark"><i></i><b>N</b></span>
<span class="brand-copy"><strong><?= h($appName) ?></strong><small>Personal finance planner</small></span>
</a>
<div class="sidebar-section">
<p class="nav-label">Workspace</p>
<nav class="nav" aria-label="Planner">
<?php foreach ($navItems as [$navRoute, $navIcon, $navLabel, $enabled]): ?>
<?php if ($enabled): ?>
<a class="<?= $currentRoute === $navRoute ? 'active' : '' ?>" href="<?= h(url($navRoute, ['month' => $currentMonth])) ?>">
<?= icon($navIcon) ?><span><?= h($navLabel) ?></span>
</a>
<?php endif; ?>
<?php endforeach; ?>
</nav>
</div>
<div class="sidebar-section admin-section">
<p class="nav-label">Administration</p>
<nav class="nav" aria-label="Administration">
<a class="<?= $currentRoute === 'settings' ? 'active' : '' ?>" href="<?= h(url('settings')) ?>"><?= icon('settings') ?><span>Configuration</span></a>
<?php if ($settings->module('import_export')): ?>
<a class="<?= $currentRoute === 'data' ? 'active' : '' ?>" href="<?= h(url('data', ['month' => $currentMonth])) ?>"><?= icon('data') ?><span>Import & export</span></a>
<?php endif; ?>
</nav>
</div>
<div class="sidebar-insight">
<span class="insight-orb"></span>
<p>Monthly planning</p>
<strong><?= h(date('F Y', strtotime($currentMonth . '-01'))) ?></strong>
<small>Progress is measured against income, targets, and money moved out of use.</small>
</div>
<div class="sidebar-footer">
<div class="admin-card">
<span class="avatar"><?= h(strtoupper(substr((string) ($user['email'] ?? 'A'), 0, 1))) ?></span>
<span><strong>Administrator</strong><small><?= h($user['email'] ?? '') ?></small></span>
</div>
<div class="sidebar-actions">
<button class="icon-control" type="button" data-theme-toggle aria-label="Toggle light and dark mode"><?= icon('sun') ?></button>
<form method="post">
<?= csrf_field() ?><input type="hidden" name="action" value="logout">
<button class="icon-control" type="submit" aria-label="Sign out"><?= icon('logout') ?></button>
</form>
</div>
</div>
</aside>
<button class="sidebar-scrim" type="button" data-menu-close aria-label="Close navigation"></button>
<main class="main">
<header class="topbar">
<button class="menu-button" type="button" data-menu-toggle aria-label="Open navigation"><?= icon('menu') ?></button>
<div class="page-title">
<p class="eyebrow"><?= h(date('l, F j, Y')) ?></p>
<h1><?= h($title ?? 'Planner') ?></h1>
</div>
<?php if (!in_array($currentRoute, ['settings', 'data'], true)): ?>
<div class="month-switcher">
<a aria-label="Previous month" href="<?= h(url($currentRoute, ['month' => $previousMonth])) ?>"></a>
<label>
<span class="sr-only">Budget month</span>
<input type="month" value="<?= h($currentMonth) ?>" data-month-picker data-route="<?= h($currentRoute) ?>">
</label>
<a aria-label="Next month" href="<?= h(url($currentRoute, ['month' => $nextMonth])) ?>"></a>
</div>
<a class="button primary top-action" href="<?= h(url('transactions', ['month' => $currentMonth, 'open' => 'expense-dialog'])) ?>"><?= icon('plus') ?> Add expense</a>
<?php endif; ?>
</header>
<?php foreach ($flashes as $flash): ?>
<div class="flash <?= h($flash['type']) ?>" role="status">
<span><?= $flash['type'] === 'success' ? '✓' : '!' ?></span>
<p><?= h($flash['message']) ?></p>
<button type="button" aria-label="Dismiss"><?= icon('close') ?></button>
</div>
<?php endforeach; ?>
<div class="page-content"><?= $content ?></div>
<footer class="app-footer"><span><?= h($appName) ?></span><span>Private by default · SQLite powered</span></footer>
</main>
</div>
<script src="/assets/app.js?v=<?= (int) filemtime(APP_ROOT . '/public/assets/app.js') ?>"></script>
</body>
</html>