Node.js/Express + PostgreSQL forecasting app with AG Grid Enterprise pivot UI. Supports baseline, scale, recode, clone operations on configurable source tables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
192 lines
9.4 KiB
HTML
192 lines
9.4 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Pivot Forecast</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@31.0.0/styles/ag-grid.css">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@31.0.0/styles/ag-theme-alpine.css">
|
||
<link rel="stylesheet" href="styles.css">
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
|
||
<!-- Sidebar -->
|
||
<nav id="sidebar">
|
||
<div class="sidebar-brand">Pivot Forecast</div>
|
||
<ul class="nav-links">
|
||
<li data-view="sources" class="active">Sources</li>
|
||
<li data-view="versions">Versions</li>
|
||
<li data-view="forecast">Forecast</li>
|
||
<li data-view="log">Log</li>
|
||
</ul>
|
||
<div class="sidebar-context">
|
||
<div id="ctx-source" class="ctx-item hidden">
|
||
<span class="ctx-label">Source</span>
|
||
<span id="ctx-source-name"></span>
|
||
</div>
|
||
<div id="ctx-version" class="ctx-item hidden">
|
||
<span class="ctx-label">Version</span>
|
||
<span id="ctx-version-name"></span>
|
||
<span id="ctx-version-status" class="status-badge"></span>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-user">
|
||
<label>User</label>
|
||
<input type="text" id="input-pf-user" placeholder="your name" />
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Main content -->
|
||
<main id="content">
|
||
<div id="status-bar" class="hidden"></div>
|
||
|
||
<!-- ===== SOURCES VIEW ===== -->
|
||
<div id="view-sources" class="view active">
|
||
<div class="two-col-layout">
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span>Database Tables</span>
|
||
<div class="header-actions">
|
||
<button id="btn-register" class="btn btn-primary hidden">Register Table</button>
|
||
</div>
|
||
</div>
|
||
<div id="tables-grid" class="ag-theme-alpine grid-fill"></div>
|
||
</div>
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span id="right-panel-title">Registered Sources</span>
|
||
<div class="header-actions">
|
||
<button id="btn-back-sources" class="btn hidden">← Sources</button>
|
||
<button id="btn-save-cols" class="btn hidden">Save Columns</button>
|
||
<button id="btn-generate-sql" class="btn btn-primary hidden">Generate SQL</button>
|
||
</div>
|
||
</div>
|
||
<div id="sources-list-grid" class="ag-theme-alpine grid-fill"></div>
|
||
<div id="col-meta-grid" class="ag-theme-alpine grid-fill hidden"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== VERSIONS VIEW ===== -->
|
||
<div id="view-versions" class="view hidden">
|
||
<div class="view-toolbar">
|
||
<span id="versions-source-label"></span>
|
||
<button id="btn-new-version" class="btn btn-primary">New Version</button>
|
||
</div>
|
||
<div id="new-version-form" class="inline-form hidden">
|
||
<h3>Create Version</h3>
|
||
<div class="form-row">
|
||
<label>Name<input type="text" id="ver-name" placeholder="e.g. FY2025 v1" /></label>
|
||
<label>Description<input type="text" id="ver-desc" placeholder="optional" /></label>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button id="btn-create-version" class="btn btn-primary">Create</button>
|
||
<button id="btn-cancel-version" class="btn">Cancel</button>
|
||
</div>
|
||
</div>
|
||
<div id="versions-grid" class="ag-theme-alpine"></div>
|
||
<div id="version-actions" class="version-actions hidden">
|
||
<span id="version-actions-label"></span>
|
||
<button class="btn btn-primary" id="vbtn-forecast">Open Forecast</button>
|
||
<button class="btn" id="vbtn-baseline">Load Baseline</button>
|
||
<button class="btn" id="vbtn-reference">Load Reference</button>
|
||
<button class="btn" id="vbtn-toggle">Close Version</button>
|
||
<button class="btn btn-danger" id="vbtn-delete">Delete</button>
|
||
</div>
|
||
<div id="load-data-form" class="inline-form hidden">
|
||
<h3 id="load-data-title">Load Baseline</h3>
|
||
<div class="form-row">
|
||
<label>Date From<input type="date" id="load-date-from" /></label>
|
||
<label>Date To<input type="date" id="load-date-to" /></label>
|
||
<label>Note<input type="text" id="load-note" placeholder="optional" /></label>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button id="btn-load-submit" class="btn btn-primary">Load</button>
|
||
<button id="btn-load-cancel" class="btn">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== FORECAST VIEW ===== -->
|
||
<div id="view-forecast" class="view hidden">
|
||
<div class="forecast-toolbar">
|
||
<span id="forecast-label">No version selected</span>
|
||
<button id="btn-forecast-refresh" class="btn">Refresh</button>
|
||
<button id="btn-expand-all" class="btn">Expand All</button>
|
||
<button id="btn-collapse-all" class="btn">Collapse All</button>
|
||
</div>
|
||
<div class="forecast-layout">
|
||
<div id="pivot-panel">
|
||
<div id="pivot-grid" class="ag-theme-alpine"></div>
|
||
</div>
|
||
<div id="operation-panel">
|
||
<div class="op-section">
|
||
<div class="op-title">Slice</div>
|
||
<div id="slice-display">
|
||
<span class="op-hint">Click a row to select a slice</span>
|
||
</div>
|
||
<button id="btn-clear-slice" class="btn btn-sm hidden">Clear</button>
|
||
</div>
|
||
<div id="op-forms-area" class="hidden">
|
||
<div class="op-tabs">
|
||
<button class="op-tab active" data-op="scale">Scale</button>
|
||
<button class="op-tab" data-op="recode">Recode</button>
|
||
<button class="op-tab" data-op="clone">Clone</button>
|
||
</div>
|
||
<!-- Scale -->
|
||
<div id="op-scale" class="op-form">
|
||
<label>Value Δ<input type="number" id="scale-value-incr" step="any" placeholder="0" /></label>
|
||
<label>Units Δ<input type="number" id="scale-units-incr" step="any" placeholder="0" /></label>
|
||
<label class="label-inline"><input type="checkbox" id="scale-pct" /> Treat as %</label>
|
||
<label>Note<input type="text" id="scale-note" placeholder="optional" /></label>
|
||
<button id="btn-submit-scale" class="btn btn-primary">Apply Scale</button>
|
||
</div>
|
||
<!-- Recode -->
|
||
<div id="op-recode" class="op-form hidden">
|
||
<div class="op-hint">Enter new values for dimensions to replace:</div>
|
||
<div id="recode-fields"></div>
|
||
<label>Note<input type="text" id="recode-note" placeholder="optional" /></label>
|
||
<button id="btn-submit-recode" class="btn btn-primary">Apply Recode</button>
|
||
</div>
|
||
<!-- Clone -->
|
||
<div id="op-clone" class="op-form hidden">
|
||
<div class="op-hint">Override dimension values on cloned rows:</div>
|
||
<div id="clone-fields"></div>
|
||
<label>Scale Factor<input type="number" id="clone-scale" step="any" value="1" /></label>
|
||
<label>Note<input type="text" id="clone-note" placeholder="optional" /></label>
|
||
<button id="btn-submit-clone" class="btn btn-primary">Apply Clone</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== LOG VIEW ===== -->
|
||
<div id="view-log" class="view hidden">
|
||
<div id="log-grid" class="ag-theme-alpine grid-fill"></div>
|
||
</div>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Table preview modal -->
|
||
<div id="modal-overlay" class="modal-overlay hidden">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<span id="modal-title">Preview</span>
|
||
<button id="modal-close" class="btn-icon">×</button>
|
||
</div>
|
||
<div id="modal-body"></div>
|
||
<div class="modal-footer">
|
||
<button id="btn-modal-register" class="btn btn-primary">Register This Table</button>
|
||
<button id="btn-modal-close" class="btn">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/ag-grid-enterprise@31.0.0/dist/ag-grid-enterprise.min.js"></script>
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|