in progress
This commit is contained in:
parent
96e6ae3bfc
commit
ac007fb1e6
43
main.py
43
main.py
|
|
@ -46,6 +46,14 @@ async def graphs():
|
|||
with open("templates/graphs.html", "r", encoding="utf-8") as f:
|
||||
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 /)
|
||||
|
|
@ -67,7 +75,7 @@ async def api_data():
|
|||
# -----------------------
|
||||
# API 2 (used by /graphs)
|
||||
# -----------------------
|
||||
from fastapi import Depends, Query
|
||||
|
||||
|
||||
@app.get("/api/graphs")
|
||||
async def api_graphs(
|
||||
|
|
@ -103,6 +111,39 @@ async def api_graphs(
|
|||
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
|
||||
# -----------------------
|
||||
|
|
|
|||
282
static/style.css
282
static/style.css
|
|
@ -1,14 +1,74 @@
|
|||
/* =========================
|
||||
BASE / GLOBAL
|
||||
========================= */
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #ffffff;
|
||||
color: #ffffff;
|
||||
background: #0f1115;
|
||||
color: #e6e6e6;
|
||||
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 {
|
||||
margin: 10px 15px 5px 15px;
|
||||
}
|
||||
|
|
@ -24,18 +84,226 @@ a:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* FULL SCREEN CHART CONTAINER */
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
CHART (IMPORTANT)
|
||||
========================= */
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 80px); /* subtract header space */
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* CRITICAL: canvas must stretch */
|
||||
#chart {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
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;
|
||||
}
|
||||
|
|
@ -8,11 +8,30 @@
|
|||
|
||||
<body>
|
||||
|
||||
<h2 id="title">Graphs</h2>
|
||||
<a href="/">Back</a>
|
||||
<div class="layout">
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="chart"></canvas>
|
||||
<!-- 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>
|
||||
|
||||
<div class="chart-container" style="height: calc(100vh - 140px);">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
@ -50,7 +69,6 @@ async function loadGraph() {
|
|||
|
||||
if (raw.length === 0) return;
|
||||
|
||||
// Company name comes from FIRST row
|
||||
const companyName = `${raw[0].CompanyName} (${ticker})` || ticker;
|
||||
|
||||
const prices = raw
|
||||
|
|
@ -72,12 +90,10 @@ async function loadGraph() {
|
|||
datasets.push({
|
||||
label: companyName,
|
||||
data: prices,
|
||||
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
|
||||
pointBackgroundColor: pointColors,
|
||||
pointBorderColor: pointColors,
|
||||
pointRadius: pointSizes,
|
||||
|
|
@ -85,9 +101,6 @@ async function loadGraph() {
|
|||
});
|
||||
});
|
||||
|
||||
// -----------------------------
|
||||
// X AXIS LABELS (14 → 1 numeric)
|
||||
// -----------------------------
|
||||
const labels = [];
|
||||
const totalDays = Math.ceil(basePrices.length / 4);
|
||||
let dayCounter = totalDays;
|
||||
|
|
@ -101,9 +114,6 @@ async function loadGraph() {
|
|||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// CHART
|
||||
// -----------------------------
|
||||
new Chart(document.getElementById("chart"), {
|
||||
type: "line",
|
||||
data: {
|
||||
|
|
@ -138,7 +148,6 @@ async function loadGraph() {
|
|||
maxRotation: 0
|
||||
}
|
||||
},
|
||||
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue