Push the rest

This commit is contained in:
2026-05-11 10:58:46 +02:00
parent adb5c1a439
commit 0031caf16c
94 changed files with 11777 additions and 3474 deletions

View File

@@ -1,363 +1,145 @@
import { trace, SpanStatusCode } from "@opentelemetry/api";
import { useQuery } from "@tanstack/react-query";
import { startTransition, useDeferredValue } from "react";
import {
Area,
AreaChart,
CartesianGrid,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { getDashboard } from "./api/client";
import { Navigate, NavLink, Route, Routes } from "react-router-dom";
import { useAuth } from "./auth/AuthContext";
const money = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
import SalesDashboard from "./pages/aw/SalesDashboard";
import RepScores from "./pages/aw/RepScores";
import ProductDemand from "./pages/aw/ProductDemand";
import AnomalyDetection from "./pages/aw/AnomalyDetection";
import StockDashboard from "./pages/wwi/StockDashboard";
import SupplierScores from "./pages/wwi/SupplierScores";
import WhatIf from "./pages/wwi/WhatIf";
import BusinessEvents from "./pages/wwi/BusinessEvents";
import OperationsPage from "./pages/ops/OperationsPage";
import AuditPage from "./pages/ops/AuditPage";
import ExportsPage from "./pages/ops/ExportsPage";
const tracer = trace.getTracer("bi-frontend-ui");
function formatCompactDate(value: string): string {
return new Date(value).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
});
function NavItem({ to, label }: { to: string; label: string }) {
return (
<NavLink
to={to}
className={({ isActive }) => `nav-link${isActive ? " nav-active" : ""}`}
>
{label}
</NavLink>
);
}
function formatTooltipMoney(
value: string | number | readonly (string | number)[] | undefined,
): string {
const raw = Array.isArray(value) ? Number(value[0]) : Number(value);
return money.format(Number.isFinite(raw) ? raw : 0);
}
function formatTooltipNumber(
value: string | number | readonly (string | number)[] | undefined,
): string {
const raw = Array.isArray(value) ? Number(value[0]) : Number(value);
return Number.isFinite(raw) ? raw.toFixed(2) : "0.00";
function CenteredShell({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen grid place-items-center text-center p-4 text-[#d6e7ff]">
{children}
</div>
);
}
export default function App() {
const auth = useAuth();
const dashboardQuery = useQuery({
queryKey: ["dashboard"],
queryFn: getDashboard,
staleTime: 30_000,
refetchInterval: 120_000,
enabled: auth.authenticated || !auth.enabled,
});
const deferredRankings = useDeferredValue(
dashboardQuery.data?.rankings ?? [],
);
const chartHistory =
dashboardQuery.data?.history.slice(-120).map((point) => ({
date: point.date,
actual: point.revenue,
forecast: null as number | null,
lower: null as number | null,
upper: null as number | null,
})) ?? [];
const chartForecast =
dashboardQuery.data?.forecasts.slice(0, 45).map((point) => ({
date: point.date,
actual: null as number | null,
forecast: point.predicted_revenue,
lower: point.lower_bound,
upper: point.upper_bound,
})) ?? [];
const trendData = [...chartHistory, ...chartForecast];
const refreshData = () => {
tracer.startActiveSpan("frontend.refresh_click", async (span) => {
try {
startTransition(() => {
void dashboardQuery.refetch();
});
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.recordException(error as Error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: "Failed to refresh dashboard data.",
});
} finally {
span.end();
}
});
};
if (auth.loading) {
return <div className="loading-shell">Initializing OIDC session...</div>;
return <CenteredShell>Initializing OIDC session</CenteredShell>;
}
if (auth.error) {
return (
<div className="loading-shell">
Authentication setup error.
<br />
{auth.error}
</div>
);
return <CenteredShell>Authentication error: {auth.error}</CenteredShell>;
}
if (auth.enabled && !auth.authenticated) {
return (
<div className="loading-shell">
Authentication required.
<br />
<button
className="refresh-button"
onClick={() => void auth.login()}
type="button"
>
Sign In with OIDC
</button>
</div>
<CenteredShell>
<div className="flex flex-col items-center gap-4">
<p className="m-0 text-lg">Authentication required.</p>
<button className="btn-primary" onClick={() => void auth.login()} type="button">
Sign In with OIDC
</button>
</div>
</CenteredShell>
);
}
if (dashboardQuery.isLoading) {
return (
<div className="loading-shell">
Loading telemetry-enabled BI dashboard...
</div>
);
}
if (dashboardQuery.error || !dashboardQuery.data) {
return (
<div className="loading-shell">
Dashboard could not load.
<br />
{(dashboardQuery.error as Error | undefined)?.message ??
"No response from backend."}
</div>
);
}
const { kpis, recommendations, telemetry } = dashboardQuery.data;
const topScore = deferredRankings[0]?.score ?? 0;
return (
<main className="app-shell">
<div className="radial-glow" />
<header className="dashboard-header">
<div>
<p className="eyebrow">Business Intelligence Command Center</p>
<h1>Warehouse Forecasting and Ranking Dashboard</h1>
<p className="subtitle">
Data sources: <strong>WorldWideImporters</strong> +{" "}
<strong>AdventureWorks2022DWH</strong> (read-only) with
OpenTelemetry traces from browser to SQL.
</p>
<p className="trace-id">
Last backend trace:{" "}
<code>{telemetry.backendTraceId ?? "missing-trace-id-header"}</code>
</p>
<div className="flex min-h-screen">
{/* Sidebar */}
<nav className="
w-[220px] max-[980px]:w-[180px]
shrink-0
bg-[rgba(8,16,28,0.92)]
border-r border-[rgba(186,212,255,0.22)]
flex flex-col
py-5 px-3
sticky top-0 h-screen overflow-y-auto
max-sm:w-full max-sm:h-auto max-sm:static
max-sm:flex-row max-sm:flex-wrap max-sm:gap-2 max-sm:p-3
">
<div className="flex items-center gap-2 font-bold text-[0.95rem] tracking-tight mb-6 px-[0.4rem] max-sm:mb-0">
<span className="text-[#57d4ff] text-[0.7rem]"></span>
<span>OTel BI Platform</span>
</div>
<div className="auth-actions">
<p className="subtitle">
User: <strong>{auth.subject ?? "unknown"}</strong>
</p>
<div className="header-actions">
<button
className="refresh-button"
onClick={refreshData}
type="button"
>
Refresh
</button>
{auth.enabled ? (
<button
className="logout-button"
onClick={() => void auth.logout()}
type="button"
>
Sign Out
</button>
) : null}
<div className="mb-5">
<div className="text-[0.65rem] uppercase tracking-[0.12em] text-[rgba(233,244,255,0.7)] px-[0.4rem] mb-1">
AdventureWorks DW
</div>
<NavItem to="/aw/sales" label="Sales & Forecast" />
<NavItem to="/aw/reps" label="Rep Scores" />
<NavItem to="/aw/products" label="Product Demand" />
<NavItem to="/aw/anomalies" label="Anomaly Detection" />
</div>
</header>
<section className="kpi-grid">
<article className="kpi-card">
<p>Total Revenue</p>
<h2>{money.format(kpis.total_revenue)}</h2>
</article>
<article className="kpi-card">
<p>Gross Margin</p>
<h2>{kpis.gross_margin_pct.toFixed(2)}%</h2>
</article>
<article className="kpi-card">
<p>Avg Order Value</p>
<h2>{money.format(kpis.avg_order_value)}</h2>
</article>
<article className="kpi-card">
<p>Total Quantity</p>
<h2>
{kpis.total_quantity.toLocaleString("en-US", {
maximumFractionDigits: 0,
})}
</h2>
</article>
</section>
<div className="mb-5">
<div className="text-[0.65rem] uppercase tracking-[0.12em] text-[rgba(233,244,255,0.7)] px-[0.4rem] mb-1">
WideWorldImporters DW
</div>
<NavItem to="/wwi/stock" label="Stock & Reorder" />
<NavItem to="/wwi/suppliers" label="Supplier Scores" />
<NavItem to="/wwi/whatif" label="What-if Scenarios" />
<NavItem to="/wwi/events" label="Business Events" />
</div>
<section className="panel-grid">
<article className="panel wide">
<div className="panel-title-row">
<h3>Revenue Trend + Forecast</h3>
<span>{trendData.length} points</span>
<div className="mb-5">
<div className="text-[0.65rem] uppercase tracking-[0.12em] text-[rgba(233,244,255,0.7)] px-[0.4rem] mb-1">
Platform
</div>
<div className="chart-wrap">
<ResponsiveContainer width="100%" height={320}>
<LineChart data={trendData}>
<CartesianGrid
strokeDasharray="4 4"
stroke="rgba(255,255,255,0.08)"
/>
<XAxis
dataKey="date"
tickFormatter={formatCompactDate}
stroke="rgba(255,255,255,0.65)"
/>
<YAxis
tickFormatter={(value) => money.format(value)}
stroke="rgba(255,255,255,0.65)"
/>
<Tooltip
labelFormatter={(label) =>
new Date(label).toLocaleDateString("en-US")
}
formatter={formatTooltipMoney}
/>
<Area
type="monotone"
dataKey="upper"
stroke="none"
fill="rgba(90, 201, 255, 0.1)"
/>
<Area
type="monotone"
dataKey="lower"
stroke="none"
fill="rgba(15, 20, 31, 0.9)"
/>
<Line
type="monotone"
dataKey="actual"
stroke="#f9de70"
strokeWidth={2.5}
dot={false}
/>
<Line
type="monotone"
dataKey="forecast"
stroke="#57d4ff"
strokeWidth={2.5}
strokeDasharray="8 5"
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</article>
<NavItem to="/ops/jobs" label="Operations" />
<NavItem to="/ops/audit" label="Audit Log" />
<NavItem to="/ops/exports" label="Export History" />
</div>
<article className="panel">
<div className="panel-title-row">
<h3>Top Product Score</h3>
<span>Weighted ranking index</span>
</div>
<div className="score-wrap">
<ResponsiveContainer width="100%" height={240}>
<AreaChart
data={[
{ label: "baseline", value: 0 },
{ label: "current", value: topScore },
]}
>
<CartesianGrid
strokeDasharray="3 3"
stroke="rgba(255,255,255,0.08)"
/>
<XAxis dataKey="label" stroke="rgba(255,255,255,0.65)" />
<YAxis stroke="rgba(255,255,255,0.65)" />
<Tooltip formatter={formatTooltipNumber} />
<Area
type="monotone"
dataKey="value"
stroke="#8ef2c7"
fill="rgba(142, 242, 199, 0.28)"
/>
</AreaChart>
</ResponsiveContainer>
<p className="score-caption">
Current leader score <strong>{topScore.toFixed(2)}</strong> / 100
<div className="mt-auto pt-4 border-t border-[rgba(186,212,255,0.22)] max-sm:mt-0 max-sm:pt-0 max-sm:border-t-0">
{auth.subject && (
<p className="text-[0.78rem] text-[rgba(233,244,255,0.7)] m-0 mb-2 overflow-hidden text-ellipsis whitespace-nowrap">
{auth.subject}
</p>
</div>
</article>
)}
{auth.enabled && (
<button className="btn-ghost" onClick={() => void auth.logout()} type="button">
Sign Out
</button>
)}
</div>
</nav>
<article className="panel wide">
<div className="panel-title-row">
<h3>Product Rankings</h3>
<span>Top {deferredRankings.length}</span>
</div>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Rank</th>
<th>Product</th>
<th>Category</th>
<th>Revenue</th>
<th>Margin</th>
<th>Score</th>
</tr>
</thead>
<tbody>
{deferredRankings.map((item) => (
<tr key={`${item.rank}-${item.product_id}`}>
<td>{item.rank}</td>
<td>{item.product_name}</td>
<td>{item.category}</td>
<td>{money.format(item.revenue)}</td>
<td>{item.margin_pct.toFixed(2)}%</td>
<td>{item.score.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</article>
{/* Main content */}
<main className="flex-1 overflow-auto p-6">
<Routes>
<Route index element={<Navigate to="/aw/sales" replace />} />
<article className="panel">
<div className="panel-title-row">
<h3>Recommendations</h3>
<span>Action queue</span>
</div>
<ul className="recommendations-list">
{recommendations.map((item, index) => (
<li key={`${item.title}-${index}`}>
<span className={`priority ${item.priority}`}>
{item.priority}
</span>
<h4>{item.title}</h4>
<p>{item.summary}</p>
</li>
))}
</ul>
</article>
</section>
</main>
<Route path="/aw/sales" element={<SalesDashboard />} />
<Route path="/aw/reps" element={<RepScores />} />
<Route path="/aw/products" element={<ProductDemand />} />
<Route path="/aw/anomalies" element={<AnomalyDetection />} />
<Route path="/wwi/stock" element={<StockDashboard />} />
<Route path="/wwi/suppliers" element={<SupplierScores />} />
<Route path="/wwi/whatif" element={<WhatIf />} />
<Route path="/wwi/events" element={<BusinessEvents />} />
<Route path="/ops/jobs" element={<OperationsPage />} />
<Route path="/ops/audit" element={<AuditPage />} />
<Route path="/ops/exports" element={<ExportsPage />} />
<Route path="*" element={<Navigate to="/aw/sales" replace />} />
</Routes>
</main>
</div>
);
}