Add LynkedUp Pro dashboard (dark + light) from Figma
- Full dashboard at /dashboard matching the Figma Linkedup-Web frame - Dark/light theme toggle (persisted to localStorage) - Live animated charts: stat sparkbars, P&L grouped bars, donut, gauge, pipeline bars, progress, revenue-potential - Sidebar, topbar, stat cards, weather, top sales reps - 29 icons exported from Figma (currentColor) + lucide fallbacks - Fix: bar charts had zero height (percentage in indefinite flex parent) - Fix: removed content max-width that left a right-side gap Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
import { FigIcon } from "./figicon";
|
||||
|
||||
type Item = { key: string; label: string };
|
||||
|
||||
const GROUPS: { title: string; items: Item[] }[] = [
|
||||
{
|
||||
title: "Workspace",
|
||||
items: [
|
||||
{ key: "dashboard", label: "Dashboard" },
|
||||
{ key: "owners", label: "Owners Box" },
|
||||
{ key: "projects", label: "Projects" },
|
||||
{ key: "leads", label: "Leads" },
|
||||
{ key: "verify", label: "Lead Verification" },
|
||||
{ key: "pipeline", label: "Pipeline" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Workspace",
|
||||
items: [
|
||||
{ key: "dispatch", label: "LynkDispatch" },
|
||||
{ key: "storm", label: "Storm Intel" },
|
||||
{ key: "territory", label: "Territory Map" },
|
||||
{ key: "procanvas", label: "ProCanvas" },
|
||||
{ key: "estimates", label: "Estimates" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
items: [
|
||||
{ key: "schedule", label: "Team Schedule" },
|
||||
{ key: "leaderboard", label: "Leaderboard" },
|
||||
{ key: "subtasks", label: "Subcontractor Tasks" },
|
||||
{ key: "people", label: "People" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const EXTRA: Item[] = [
|
||||
{ key: "settings", label: "Org Settings" },
|
||||
{ key: "ai", label: "AI Assistant" },
|
||||
{ key: "home", label: "Home" },
|
||||
];
|
||||
|
||||
export function Sidebar({ active, onSelect }: { active: string; onSelect: (k: string) => void }) {
|
||||
return (
|
||||
<aside className="dash-sidebar">
|
||||
<div className="dash-brand">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/image/logo.png" alt="LynkedUp Pro" />
|
||||
<span className="bk">LynkedUp Pro</span>
|
||||
</div>
|
||||
|
||||
<nav className="dash-nav">
|
||||
{GROUPS.map((g, gi) => (
|
||||
<div key={gi}>
|
||||
<div className="nav-group">{g.title}</div>
|
||||
{g.items.map((it) => <NavBtn key={it.key} it={it} active={active} onSelect={onSelect} />)}
|
||||
</div>
|
||||
))}
|
||||
<div style={{ height: 8 }} />
|
||||
{EXTRA.map((it) => <NavBtn key={it.key} it={it} active={active} onSelect={onSelect} />)}
|
||||
</nav>
|
||||
|
||||
<div className="sb-foot">
|
||||
<div className="sb-user">
|
||||
<span className="av" style={{ background: "linear-gradient(135deg,#285ef0,#09b9c6)", display: "grid", placeItems: "center", color: "#fff", fontWeight: 700, fontSize: 12 }}>JJ</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="nm">Justin Johnson</div>
|
||||
<div className="rl">Owner</div>
|
||||
</div>
|
||||
<ChevronsUpDown size={15} />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function NavBtn({ it, active, onSelect }: { it: Item; active: string; onSelect: (k: string) => void }) {
|
||||
return (
|
||||
<button className={`nav-item ${active === it.key ? "active" : ""}`} onClick={() => onSelect(it.key)}>
|
||||
<FigIcon name={it.key} size={18} />
|
||||
{it.label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user