in progress

This commit is contained in:
Chase Eller 2026-04-14 20:37:42 -04:00
parent 96e6ae3bfc
commit ac007fb1e6
No known key found for this signature in database
GPG Key ID: 9233D42F495E657C
4 changed files with 452 additions and 22 deletions

43
main.py
View File

@ -46,6 +46,14 @@ async def graphs():
with open("templates/graphs.html", "r", encoding="utf-8") as f: with open("templates/graphs.html", "r", encoding="utf-8") as f:
return HTMLResponse(f.read()) return HTMLResponse(f.read())
# -----------------------
# PAGE 3: /portgraphs
# -----------------------
@app.get("/port")
async def port():
with open("templates/portfoliograph.html", "r", encoding="utf-8") as f:
return HTMLResponse(f.read())
# ----------------------- # -----------------------
# API 1 (used by /) # API 1 (used by /)
@ -67,7 +75,7 @@ async def api_data():
# ----------------------- # -----------------------
# API 2 (used by /graphs) # API 2 (used by /graphs)
# ----------------------- # -----------------------
from fastapi import Depends, Query
@app.get("/api/graphs") @app.get("/api/graphs")
async def api_graphs( async def api_graphs(
@ -103,6 +111,39 @@ async def api_graphs(
return results return results
@app.get("/api/portfolio")
async def api_folio(
ticker: str = Query(...),
uuid: int = Query(0),
):
tickers = [t.strip().upper() for t in ticker.split(",") if t.strip()]
with engine.connect() as conn:
results = {}
for t in tickers:
query = text("""
SELECT
p.*,
s.CurPrice,
(s.CurPrice * p.SharesOwned) AS CurrentValue
FROM STOCKMARKET_PORTFOLIO p
JOIN STOCKMARKET_STOCKS s
ON s.Ticker = p.Ticker
WHERE p.Ticker = :ticker
AND p.UUID = :uuid;
""")
result = conn.execute(query, {
"ticker": t,
"uuid": uuid
})
rows = [dict(r._mapping) for r in result]
results[t] = rows
return results
# ----------------------- # -----------------------
# RUN # RUN
# ----------------------- # -----------------------

View File

@ -1,14 +1,74 @@
/* =========================
BASE / GLOBAL
========================= */
html, body { html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #ffffff; background: #0f1115;
color: #ffffff; color: #e6e6e6;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
overflow: hidden; /* prevents page scroll messing with chart height */ overflow: hidden;
} }
/* Header area */ /* =========================
LAYOUT SYSTEM
========================= */
.layout {
display: flex;
height: 100vh;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.page {
flex: 1;
overflow: hidden;
padding: 20px;
display: flex;
flex-direction: column;
}
/* =========================
SIDEBAR
========================= */
.sidebar {
width: 220px;
background: #1a1d23;
padding: 15px;
border-right: 1px solid #2a2f3a;
}
.nav-item {
padding: 10px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 6px;
color: #b5b5b5;
}
.nav-item:hover {
background: #2a2f3a;
color: #fff;
}
.nav-item.active {
background: #3a3f4b;
color: white;
}
/* =========================
TYPOGRAPHY
========================= */
h2 { h2 {
margin: 10px 15px 5px 15px; margin: 10px 15px 5px 15px;
} }
@ -24,18 +84,226 @@ a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* FULL SCREEN CHART CONTAINER */ .section-title {
font-size: 18px;
margin-bottom: 15px;
}
/* =========================
CHART (IMPORTANT)
========================= */
.chart-container { .chart-container {
width: 100%; width: 100%;
height: calc(100vh - 80px); /* subtract header space */ flex: 1;
display: flex; display: flex;
padding: 10px 15px; padding: 10px 15px;
box-sizing: border-box; box-sizing: border-box;
} }
/* CRITICAL: canvas must stretch */
#chart { #chart {
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100% !important;
display: block; display: block;
} }
/* =========================
EDITOR PANEL
========================= */
.editor {
width: 420px;
background: #1a1d23;
border: 1px solid #2a2f3a;
border-radius: 10px;
padding: 20px;
}
.center-wrap {
display: flex;
height: 100%;
justify-content: center;
align-items: flex-start;
}
/* =========================
FORMS
========================= */
.form-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #2a2f3a;
border-radius: 8px;
background: #151922;
margin-bottom: 10px;
}
.form-label {
font-size: 12px;
color: #9aa4b2;
margin-bottom: 5px;
}
input, select {
width: 100%;
box-sizing: border-box;
padding: 8px;
background: #0f1115;
border: 1px solid #2a2f3a;
border-radius: 6px;
color: white;
}
/* =========================
BUTTONS
========================= */
button {
padding: 8px 12px;
border: none;
border-radius: 6px;
cursor: pointer;
background: #5865f2;
color: white;
}
button:hover {
background: #4752c4;
}
.small-btn {
background: #3a3f4b;
margin-left: 10px;
}
/* =========================
ADMIN PANEL
========================= */
.admin-container {
display: flex;
height: 100%;
gap: 15px;
}
.admin-left {
width: 320px;
display: flex;
flex-direction: column;
background: #1a1d23;
border: 1px solid #2a2f3a;
border-radius: 10px;
padding: 10px;
}
.admin-right {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
}
/* =========================
SEARCH / USERS
========================= */
.search-box {
width: 100%;
box-sizing: border-box;
padding: 8px;
margin-bottom: 10px;
background: #0f1115;
border: 1px solid #2a2f3a;
border-radius: 6px;
color: white;
}
.user-list {
flex: 1;
overflow-y: auto;
}
.user-card {
padding: 10px;
border-radius: 6px;
border: 1px solid #2a2f3a;
margin-bottom: 8px;
cursor: pointer;
}
.user-card:hover {
background: #2a2f3a;
}
.user-card.active {
background: #3a3f4b;
}
/* =========================
INFO BLOCKS
========================= */
.info-block {
display: flex;
justify-content: space-between;
padding: 10px;
border: 1px solid #2a2f3a;
border-radius: 8px;
background: #151922;
margin-bottom: 10px;
}
.info-label {
color: #9aa4b2;
}
/* =========================
TOASTS
========================= */
.toast {
position: fixed;
bottom: 20px;
right: 20px;
padding: 10px 14px;
border-radius: 8px;
font-size: 13px;
color: white;
background: #2a2f3a;
border: 1px solid #3a3f4b;
opacity: 0;
transform: translateY(10px);
transition: all 0.25s ease;
pointer-events: none;
z-index: 9999;
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
.toast.success {
background: #1f3b2c;
border-color: #2f6b4f;
}
.toast.error {
background: #3b1f1f;
border-color: #6b2f2f;
}
/* =========================
HEADERS
========================= */
.edit-header {
font-size: 16px;
margin-bottom: 15px;
}
.edit-header span {
text-decoration: underline;
}

View File

@ -8,13 +8,32 @@
<body> <body>
<h2 id="title">Graphs</h2> <div class="layout">
<!-- SIDEBAR (optional placeholder - remove if not needed) -->
<div class="sidebar">
<div class="nav-item active">Graphs</div>
<div class="nav-item">Dashboard</div>
<div class="nav-item">Settings</div>
</div>
<!-- MAIN CONTENT -->
<div class="content">
<div class="page">
<h2 class="section-title">Graphs</h2>
<a href="/">Back</a> <a href="/">Back</a>
<div class="chart-container"> <div class="chart-container" style="height: calc(100vh - 140px);">
<canvas id="chart"></canvas> <canvas id="chart"></canvas>
</div> </div>
</div>
</div>
</div>
<script> <script>
function getParams() { function getParams() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
@ -50,7 +69,6 @@ async function loadGraph() {
if (raw.length === 0) return; if (raw.length === 0) return;
// Company name comes from FIRST row
const companyName = `${raw[0].CompanyName} (${ticker})` || ticker; const companyName = `${raw[0].CompanyName} (${ticker})` || ticker;
const prices = raw const prices = raw
@ -72,12 +90,10 @@ async function loadGraph() {
datasets.push({ datasets.push({
label: companyName, label: companyName,
data: prices, data: prices,
borderColor: color, borderColor: color,
backgroundColor: color, backgroundColor: color,
borderWidth: 2, borderWidth: 2,
fill: false, fill: false,
pointBackgroundColor: pointColors, pointBackgroundColor: pointColors,
pointBorderColor: pointColors, pointBorderColor: pointColors,
pointRadius: pointSizes, pointRadius: pointSizes,
@ -85,9 +101,6 @@ async function loadGraph() {
}); });
}); });
// -----------------------------
// X AXIS LABELS (14 → 1 numeric)
// -----------------------------
const labels = []; const labels = [];
const totalDays = Math.ceil(basePrices.length / 4); const totalDays = Math.ceil(basePrices.length / 4);
let dayCounter = totalDays; let dayCounter = totalDays;
@ -101,9 +114,6 @@ async function loadGraph() {
} }
} }
// -----------------------------
// CHART
// -----------------------------
new Chart(document.getElementById("chart"), { new Chart(document.getElementById("chart"), {
type: "line", type: "line",
data: { data: {
@ -138,7 +148,6 @@ async function loadGraph() {
maxRotation: 0 maxRotation: 0
} }
}, },
y: { y: {
title: { title: {
display: true, display: true,

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html>
<head>
<title>Portfolio Debug View</title>
<link rel="stylesheet" href="/static/style.css">
<style>
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.json-box {
flex: 1;
background: #0f1115;
border: 1px solid #2a2f3a;
border-radius: 8px;
padding: 15px;
overflow: auto;
white-space: pre;
font-family: monospace;
font-size: 13px;
margin-top: 10px;
}
.error {
color: #ff6b6b;
padding: 15px;
}
.meta {
font-size: 12px;
color: #9aa4b2;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="layout">
<!-- SIDEBAR -->
<div class="sidebar">
<div class="nav-item">Dashboard</div>
<div class="nav-item active">Debug View</div>
<div class="nav-item">Settings</div>
</div>
<!-- CONTENT -->
<div class="content">
<div class="page">
<div class="header">
<h2 class="section-title">Portfolio Debug View</h2>
<a href="/">Back</a>
</div>
<div class="meta" id="meta"></div>
<div id="output" class="json-box">Loading...</div>
</div>
</div>
</div>
<script>
function getParams() {
const url = new URL(window.location.href);
return {
tickers: url.searchParams.get("ticker"),
uuid: url.searchParams.get("uuid")
};
}
async function loadData() {
const { tickers, uuid } = getParams();
const meta = document.getElementById("meta");
const output = document.getElementById("output");
if (!tickers || !uuid) {
output.innerHTML = "Missing ?ticker or ?uuid parameter";
return;
}
meta.innerText = `Requesting: ticker=${tickers} | uuid=${uuid}`;
try {
const res = await fetch(
`/api/portfolio?ticker=${encodeURIComponent(tickers)}&uuid=${encodeURIComponent(uuid)}`
);
const data = await res.json();
output.innerText = JSON.stringify(data, null, 4);
} catch (err) {
output.innerHTML = `<div class="error">Error loading data: ${err}</div>`;
}
}
loadData();
</script>
</body>
</html>