diff --git a/public/icons/dashboard.svg b/public/icons/dashboard.svg new file mode 100644 index 0000000..67bd7ea --- /dev/null +++ b/public/icons/dashboard.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/dispatch.svg b/public/icons/dispatch.svg new file mode 100644 index 0000000..7efbe98 --- /dev/null +++ b/public/icons/dispatch.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/icons/estimates.svg b/public/icons/estimates.svg new file mode 100644 index 0000000..8aac2ab --- /dev/null +++ b/public/icons/estimates.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/home.svg b/public/icons/home.svg new file mode 100644 index 0000000..4f05c6f --- /dev/null +++ b/public/icons/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/i_ar.svg b/public/icons/i_ar.svg new file mode 100644 index 0000000..aa1aeb9 --- /dev/null +++ b/public/icons/i_ar.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_bell.svg b/public/icons/i_bell.svg new file mode 100644 index 0000000..c38a130 --- /dev/null +++ b/public/icons/i_bell.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/icons/i_collected.svg b/public/icons/i_collected.svg new file mode 100644 index 0000000..268db41 --- /dev/null +++ b/public/icons/i_collected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/icons/i_hotleads.svg b/public/icons/i_hotleads.svg new file mode 100644 index 0000000..969d661 --- /dev/null +++ b/public/icons/i_hotleads.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_humidity.svg b/public/icons/i_humidity.svg new file mode 100644 index 0000000..0917ac2 --- /dev/null +++ b/public/icons/i_humidity.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/i_meets.svg b/public/icons/i_meets.svg new file mode 100644 index 0000000..2850ad6 --- /dev/null +++ b/public/icons/i_meets.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_payouts.svg b/public/icons/i_payouts.svg new file mode 100644 index 0000000..2b14249 --- /dev/null +++ b/public/icons/i_payouts.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_revenue.svg b/public/icons/i_revenue.svg new file mode 100644 index 0000000..b4e31a5 --- /dev/null +++ b/public/icons/i_revenue.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/i_search.svg b/public/icons/i_search.svg new file mode 100644 index 0000000..1f6dcdd --- /dev/null +++ b/public/icons/i_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_theme.svg b/public/icons/i_theme.svg new file mode 100644 index 0000000..5e7ab90 --- /dev/null +++ b/public/icons/i_theme.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/icons/i_vendor.svg b/public/icons/i_vendor.svg new file mode 100644 index 0000000..69b336e --- /dev/null +++ b/public/icons/i_vendor.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/i_wind.svg b/public/icons/i_wind.svg new file mode 100644 index 0000000..5cdbc27 --- /dev/null +++ b/public/icons/i_wind.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/leaderboard.svg b/public/icons/leaderboard.svg new file mode 100644 index 0000000..aa627ed --- /dev/null +++ b/public/icons/leaderboard.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/icons/leads.svg b/public/icons/leads.svg new file mode 100644 index 0000000..aa58e5c --- /dev/null +++ b/public/icons/leads.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/owners.svg b/public/icons/owners.svg new file mode 100644 index 0000000..6a050d7 --- /dev/null +++ b/public/icons/owners.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/people.svg b/public/icons/people.svg new file mode 100644 index 0000000..56e48f5 --- /dev/null +++ b/public/icons/people.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/icons/pipeline.svg b/public/icons/pipeline.svg new file mode 100644 index 0000000..921b3a7 --- /dev/null +++ b/public/icons/pipeline.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/procanvas.svg b/public/icons/procanvas.svg new file mode 100644 index 0000000..d5567fd --- /dev/null +++ b/public/icons/procanvas.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/icons/projects.svg b/public/icons/projects.svg new file mode 100644 index 0000000..9e80c63 --- /dev/null +++ b/public/icons/projects.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/schedule.svg b/public/icons/schedule.svg new file mode 100644 index 0000000..2ed12d2 --- /dev/null +++ b/public/icons/schedule.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/icons/settings.svg b/public/icons/settings.svg new file mode 100644 index 0000000..7bac0ad --- /dev/null +++ b/public/icons/settings.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/icons/storm.svg b/public/icons/storm.svg new file mode 100644 index 0000000..10690a4 --- /dev/null +++ b/public/icons/storm.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/icons/subtasks.svg b/public/icons/subtasks.svg new file mode 100644 index 0000000..6f7fca2 --- /dev/null +++ b/public/icons/subtasks.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/territory.svg b/public/icons/territory.svg new file mode 100644 index 0000000..b84745b --- /dev/null +++ b/public/icons/territory.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/verify.svg b/public/icons/verify.svg new file mode 100644 index 0000000..86cb6aa --- /dev/null +++ b/public/icons/verify.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/app/dashboard/dashboard.css b/src/app/dashboard/dashboard.css new file mode 100644 index 0000000..9be87e5 --- /dev/null +++ b/src/app/dashboard/dashboard.css @@ -0,0 +1,212 @@ +/* ========================================================================= + LynkedUp Pro — Dashboard (matches Figma "Linkedup-Web") + Dark + light themes via [data-theme] on .dash-root. + ========================================================================= */ + +.dash-root { + /* accents (shared) */ + --orange: #fda913; + --orange-2: #fd6d13; + --yellow: #ffd60a; + --cyan: #09b9c6; + --green: #14bc83; + --blue: #285ef0; + --purple: #9036e9; + --red: #f0563f; + + --radius: 16px; + --radius-sm: 12px; + --font: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif; + + min-height: 100vh; + display: flex; + background: var(--bg); + color: var(--text); + font-family: var(--font); + -webkit-font-smoothing: antialiased; +} + +/* ---- dark (default) ---- */ +.dash-root, +.dash-root[data-theme="dark"] { + --bg: #060608; + --panel: #0e0e13; + --panel-2: #15151b; + --panel-3: #1b1b22; + --border: rgba(255, 255, 255, 0.07); + --border-2: rgba(255, 255, 255, 0.12); + --text: #ffffff; + --text-2: #eaeaea; + --muted: #8c8c8c; + --faint: #686868; + --track: rgba(255, 255, 255, 0.08); + --sidebar: #08080b; + --shadow: 0 18px 40px -24px rgba(0, 0, 0, 0.8); +} + +/* ---- light ---- */ +.dash-root[data-theme="light"] { + --bg: #eef1f7; + --panel: #ffffff; + --panel-2: #f5f7fb; + --panel-3: #eef1f7; + --border: rgba(15, 23, 42, 0.08); + --border-2: rgba(15, 23, 42, 0.14); + --text: #0c0e14; + --text-2: #1f2430; + --muted: #5b6472; + --faint: #97a0b0; + --track: rgba(15, 23, 42, 0.07); + --sidebar: #ffffff; + --shadow: 0 18px 40px -26px rgba(15, 23, 42, 0.25); +} + +.dash-root * { box-sizing: border-box; } +.dash-root h1, .dash-root h2, .dash-root h3, .dash-root h4 { margin: 0; } + +/* ---- sidebar ---- */ +.dash-sidebar { + width: 214px; + flex: 0 0 214px; + background: var(--sidebar); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + height: 100vh; + position: sticky; + top: 0; +} +.dash-brand { display: flex; align-items: center; gap: 9px; padding: 18px 18px 14px; } +.dash-brand img { height: 26px; width: auto; } +.dash-brand .bk { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; } +.dash-nav { flex: 1; overflow-y: auto; padding: 6px 12px 12px; } +.dash-nav::-webkit-scrollbar { width: 6px; } +.dash-nav::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 99px; } +.nav-group { font-size: 10px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); padding: 14px 10px 6px; } +.nav-item { + display: flex; align-items: center; gap: 11px; + width: 100%; padding: 8.5px 10px; border-radius: 9px; + border: 0; background: none; cursor: pointer; + color: var(--muted); font-family: inherit; font-size: 13px; font-weight: 500; + text-align: left; transition: background 0.14s, color 0.14s; +} +.nav-item:hover { background: var(--panel-2); color: var(--text-2); } +.nav-item.active { background: var(--orange); color: #1a1206; font-weight: 600; } +.nav-item.active svg { color: #1a1206; } +.nav-item svg { color: var(--faint); flex: 0 0 auto; } +.nav-item:hover svg { color: var(--text-2); } +.fig-ic { display: inline-flex; flex: 0 0 auto; color: inherit; } +.fig-ic svg { width: 100%; height: 100%; display: block; } +.dash-sidebar .sb-foot { border-top: 1px solid var(--border); padding: 10px 12px; } +.sb-user { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 10px; } +.sb-user:hover { background: var(--panel-2); } +.sb-user .av { width: 30px; height: 30px; border-radius: 8px; object-fit: cover; flex: 0 0 30px; } +.sb-user .nm { font-size: 12.5px; font-weight: 600; } +.sb-user .rl { font-size: 11px; color: var(--faint); } + +/* ---- main ---- */ +.dash-main { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.dash-topbar { + height: 64px; display: flex; align-items: center; justify-content: space-between; + padding: 0 24px; border-bottom: 1px solid var(--border); + position: sticky; top: 0; z-index: 20; + background: color-mix(in srgb, var(--bg) 80%, transparent); + backdrop-filter: blur(10px); +} +.dash-title h1 { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; } +.dash-title p { font-size: 12.5px; color: var(--muted); margin-top: 2px; } +.top-actions { display: flex; align-items: center; gap: 8px; } +.ic-btn { width: 38px; height: 38px; border-radius: 10px; border: 1px solid var(--border); background: var(--panel); color: var(--muted); display: grid; place-items: center; cursor: pointer; transition: 0.14s; } +.ic-btn:hover { color: var(--text); border-color: var(--border-2); } +.top-user { display: flex; align-items: center; gap: 9px; padding: 5px 8px 5px 5px; border: 1px solid var(--border); border-radius: 99px; background: var(--panel); cursor: pointer; } +.top-user .av { width: 30px; height: 30px; border-radius: 99px; object-fit: cover; } +.top-user .nm { font-size: 12.5px; font-weight: 600; } +.top-user .rl { font-size: 10.5px; color: var(--faint); } + +.dash-content { padding: 22px 28px 40px; width: 100%; } +.sec-title { font-size: 15px; font-weight: 700; margin: 6px 0 14px; } + +/* ---- grid helpers ---- */ +.grid { display: grid; gap: 16px; } +.row { display: flex; align-items: center; } +.between { justify-content: space-between; } +.gap-2 { gap: 8px; } .gap-3 { gap: 12px; } +.muted { color: var(--muted); } .faint { color: var(--faint); } + +/* ---- card ---- */ +.dcard { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; } +.dcard-pad-0 { padding: 0; } +.card-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; } +.card-h h3 { font-size: 14px; font-weight: 600; } +.card-sub { font-size: 11.5px; color: var(--faint); } + +/* ---- stat bits ---- */ +.stat-ic { width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; } +.big-val { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; } +.mid-val { font-size: 21px; font-weight: 800; letter-spacing: -0.02em; } +.lbl { font-size: 12px; color: var(--muted); } +.pill { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 9px; border-radius: 99px; font-size: 11px; font-weight: 600; } +.pill-orange { background: color-mix(in srgb, var(--orange) 18%, transparent); color: var(--orange); } +.pill-green { background: color-mix(in srgb, var(--green) 18%, transparent); color: var(--green); } +.pill-blue { background: color-mix(in srgb, var(--blue) 20%, transparent); color: #6f9bff; } +.pill-cyan { background: color-mix(in srgb, var(--cyan) 18%, transparent); color: var(--cyan); } +.pill-red { background: color-mix(in srgb, var(--red) 18%, transparent); color: var(--red); } + +/* small stat card (right 2x2) */ +.mini-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px 15px; } +.mini-card .top { display: flex; justify-content: space-between; align-items: flex-start; } + +/* progress */ +.bar { height: 8px; border-radius: 99px; background: var(--track); overflow: hidden; } +.bar > span { display: block; height: 100%; border-radius: 99px; } + +/* legend dot */ +.ldot { width: 8px; height: 8px; border-radius: 99px; display: inline-block; } + +/* pipeline rows */ +.pl-row { display: grid; grid-template-columns: 92px 1fr 22px; align-items: center; gap: 10px; margin-bottom: 10px; } +.pl-row .lab { font-size: 12px; color: var(--muted); } +.pl-row .n { font-size: 12px; font-weight: 700; text-align: right; } + +/* weather */ +.wx-hours { display: flex; justify-content: space-between; gap: 6px; margin-top: 14px; } +.wx-hr { text-align: center; flex: 1; } +.wx-hr .t { font-size: 11px; color: var(--faint); } +.wx-hr .te { font-size: 13px; font-weight: 700; margin-top: 6px; } + +/* reps */ +.rep-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); } +.rep-row:last-child { border-bottom: 0; } +.rep-row .rk { width: 22px; font-size: 12px; font-weight: 700; color: var(--faint); } +.rep-row .av { width: 34px; height: 34px; border-radius: 99px; object-fit: cover; } +.rep-row .nm { font-size: 13px; font-weight: 600; } +.rep-row .em { font-size: 11px; color: var(--faint); } +.rep-row .amt { font-size: 13px; font-weight: 700; color: var(--green); } + +.link-btn { font-size: 12px; font-weight: 600; color: var(--orange); background: none; border: 0; cursor: pointer; } + +/* ---- charts ---- */ +.chart { display: flex; gap: 8px; width: 100%; } +.chart-y { display: flex; flex-direction: column; justify-content: space-between; font-size: 9px; color: var(--faint); padding-bottom: 16px; text-align: right; min-width: 18px; } +.chart-plot { flex: 1; display: flex; align-items: flex-end; gap: 2px; } +.bar-group { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; } +.bar-group .bars { height: calc(100% - 20px); width: 100%; display: flex; align-items: flex-end; justify-content: center; gap: 3px; } +.vbar { border-radius: 3px; align-self: flex-end; transition: height 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); } +.bar-x { font-size: 10px; color: var(--faint); margin-top: 7px; } + +.spark { display: flex; align-items: flex-end; gap: 4px; } +.spark > span { border-radius: 3px; transition: height 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); } + +.donut { position: relative; display: grid; place-items: center; } +.donut-c { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; } +.donut-c .dv { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; } +.donut-c .dl { font-size: 11px; color: var(--faint); } + +.gauge { position: relative; } +.gauge-c { position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } +.gauge-c .gv { font-size: 18px; font-weight: 800; } +.gauge-c .gl { font-size: 10px; color: var(--faint); } + +/* responsive */ +@media (max-width: 1100px) { .dash-sidebar { display: none; } } +@media (max-width: 760px) { .dash-content { padding: 16px; } } diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index be6e634..13f18d8 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,14 +1,19 @@ -import { Sidebar } from "@/components/layout/sidebar"; +import type { Metadata } from "next"; -export default function DashboardLayout({ - children, -}: { - children: React.ReactNode; -}) { +export const metadata: Metadata = { + title: "Dashboard — LynkedUp Pro", +}; + +export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( -
- -
{children}
-
+ <> + + + + {children} + ); } diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index c40de61..551bee9 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,41 +1,5 @@ -import type { Metadata } from "next"; -import { Header } from "@/components/layout/header"; - -export const metadata: Metadata = { - title: "Dashboard", -}; - -const stats = [ - { label: "Total Leads", value: "0" }, - { label: "Open Deals", value: "0" }, - { label: "Contacts", value: "0" }, - { label: "Revenue", value: "$0" }, -]; +import { Dashboard } from "@/components/dashboard/dashboard"; export default function DashboardPage() { - return ( - <> -
-
-
- {stats.map((stat) => ( -
-

{stat.label}

-

{stat.value}

-
- ))} -
- -
-

- Yahan pages add karenge — Leads, Contacts, Deals, Tasks. Aap batao - kaunsa page pehle banana hai. -

-
-
- - ); + return ; } diff --git a/src/components/dashboard/charts.tsx b/src/components/dashboard/charts.tsx new file mode 100644 index 0000000..40b2171 --- /dev/null +++ b/src/components/dashboard/charts.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/* ---- vertical bar chart (HTML/CSS, animated heights) ---- */ +export function BarsChart({ + labels, + series, + max, + yTicks, + height = 132, + highlight, + barWidth = 7, +}: { + labels: string[]; + series: { color: string; values: number[] }[]; + max: number; + yTicks?: string[]; + height?: number; + highlight?: number; // single-series: index to highlight, others muted + barWidth?: number; +}) { + const [on, setOn] = useState(false); + useEffect(() => { + const t = setTimeout(() => setOn(true), 40); + return () => clearTimeout(t); + }, []); + + return ( +
+ {yTicks && ( +
+ {yTicks.map((t) => {t})} +
+ )} +
+ {labels.map((lab, i) => ( +
+
+ {series.map((s, si) => { + const muted = highlight !== undefined && i !== highlight; + return ( + + ); + })} +
+ {lab} +
+ ))} +
+
+ ); +} + +/* ---- mini sparkline bars (stat cards) ---- */ +export function Spark({ values, color, height = 40, width = 8 }: { values: number[]; color: string; height?: number; width?: number }) { + const [on, setOn] = useState(false); + useEffect(() => { const t = setTimeout(() => setOn(true), 40); return () => clearTimeout(t); }, []); + const max = Math.max(...values, 1); + return ( +
+ {values.map((v, i) => ( + + ))} +
+ ); +} + +/* ---- donut chart ---- */ +export function Donut({ + segments, total, label, size = 150, thickness = 18, +}: { + segments: { value: number; color: string }[]; + total: string; + label: string; + size?: number; + thickness?: number; +}) { + const [on, setOn] = useState(false); + useEffect(() => { const t = setTimeout(() => setOn(true), 60); return () => clearTimeout(t); }, []); + const r = (size - thickness) / 2; + const c = 2 * Math.PI * r; + const sum = segments.reduce((a, s) => a + s.value, 0) || 1; + let offset = 0; + return ( +
+ + + {segments.map((s, i) => { + const frac = s.value / sum; + const len = on ? frac * c : 0; + const dash = `${len} ${c - len}`; + const el = ( + + ); + offset += frac; + return el; + })} + +
+
{total}
+
{label}
+
+
+ ); +} + +/* ---- semicircle gauge ---- */ +export function Gauge({ + value, max = 100, color, size = 150, label, sub, +}: { + value: number; max?: number; color: string; size?: number; label?: string; sub?: string; +}) { + const [on, setOn] = useState(false); + useEffect(() => { const t = setTimeout(() => setOn(true), 60); return () => clearTimeout(t); }, []); + const sw = 14; + const r = (size - sw) / 2; + const cx = size / 2, cy = size / 2; + const semi = Math.PI * r; // half circumference + const frac = Math.min(1, value / max); + const len = on ? frac * semi : 0; + return ( +
+ + + + + {(label || sub) && ( +
+ {label &&
{label}
} + {sub &&
{sub}
} +
+ )} +
+ ); +} + +/* ---- progress bar ---- */ +export function Progress({ value, color, track }: { value: number; color: string; track?: boolean }) { + const [on, setOn] = useState(false); + useEffect(() => { const t = setTimeout(() => setOn(true), 50); return () => clearTimeout(t); }, []); + return ( +
+ +
+ ); +} + +/* ---- horizontal segmented bar (pipeline) ---- */ +export function HBar({ value, max, color }: { value: number; max: number; color: string }) { + const [on, setOn] = useState(false); + useEffect(() => { const t = setTimeout(() => setOn(true), 50); return () => clearTimeout(t); }, []); + return ( +
+ +
+ ); +} diff --git a/src/components/dashboard/dashboard.tsx b/src/components/dashboard/dashboard.tsx new file mode 100644 index 0000000..e87361d --- /dev/null +++ b/src/components/dashboard/dashboard.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { TrendingUp, Cloud, MoreHorizontal, ArrowUpRight } from "lucide-react"; +import { Sidebar } from "./sidebar"; +import { Topbar } from "./topbar"; +import { FigIcon } from "./figicon"; +import { BarsChart, Spark, Donut, Gauge, Progress, HBar } from "./charts"; +import "../../app/dashboard/dashboard.css"; + +const C = { + orange: "#fda913", orange2: "#fd6d13", yellow: "#ffd60a", + cyan: "#09b9c6", green: "#14bc83", blue: "#285ef0", purple: "#9036e9", red: "#f0563f", +}; + +const rnd = (v: number, p = 0.12) => Math.max(1, Math.round(v * (1 + (Math.random() * 2 - 1) * p))); +const arr = (base: number[], p = 0.14) => base.map((v) => rnd(v, p)); + +export function Dashboard() { + const [theme, setTheme] = useState<"dark" | "light">("dark"); + const [active, setActive] = useState("dashboard"); + + useEffect(() => { + try { const t = localStorage.getItem("lup_dash_theme"); if (t === "light" || t === "dark") setTheme(t); } catch {} + }, []); + function toggle() { + setTheme((t) => { const n = t === "dark" ? "light" : "dark"; try { localStorage.setItem("lup_dash_theme", n); } catch {} return n; }); + } + + // ---- live data ---- + const [pnl, setPnl] = useState({ + revenue: [4, 6, 3, 7, 5, 6, 4], gross: [5, 3, 6, 4, 7, 5, 6], + comm: [3, 5, 4, 6, 5, 7, 4], net: [4, 6, 5, 3, 6, 4, 7], + }); + const [rev, setRev] = useState([5, 7, 4, 8, 6, 9, 16, 7, 5, 8]); + const [sparks, setSparks] = useState({ + revenue: [4, 7, 5, 9, 6, 10, 8], leads: [6, 4, 8, 5, 9, 6, 10], meets: [5, 8, 6, 9, 7, 10, 8], + }); + + useEffect(() => { + const id = setInterval(() => { + setPnl((p) => ({ revenue: arr(p.revenue), gross: arr(p.gross), comm: arr(p.comm), net: arr(p.net) })); + setRev((r) => r.map((v, i) => (i === 6 ? 16 : rnd(v, 0.18)))); + setSparks((s) => ({ revenue: arr(s.revenue), leads: arr(s.leads), meets: arr(s.meets) })); + }, 2600); + return () => clearInterval(id); + }, []); + + const days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; + const yk = ["8k", "6k", "4k", "2k", "0"]; + + return ( +
+ +
+ +
+ + {/* ===== Statistics ===== */} +

Statistics

+
+
+ } ic={C.orange} tag="GROSS PROFIT $114K" value="$626K" label="Revenue" spark={sparks.revenue} sc={C.orange} /> + } ic={C.red} tag="REQUIRES ACTION" value="20k" label="Hot Leads" spark={sparks.leads} sc={C.purple} /> + } ic={C.cyan} tag="NEXT 7 DAYS" value="70k" label="Upcoming Meets" spark={sparks.meets} sc={C.cyan} /> +
+
+ } ic={C.cyan} value="$311.3k" label="Collected to Date" sub="32 payments" pill="pill-cyan" /> + } ic={C.orange} value="$325.9k" label="Outstanding AR" sub="25 pending" pill="pill-orange" /> + } ic={C.purple} value="$203.7k" label="Pending Payouts" sub="20 pending" pill="pill-blue" /> + } ic={C.blue} value="$432.4k" label="YTD Vendor Spend" sub="57 invoices" pill="pill-blue" /> +
+
+ + {/* ===== Pipeline / Win / Storm ===== */} +
+
+

Open Pipeline

27 leads
+
$396K
+ {[["New", 6], ["Contacted", 7], ["Appointment", 7], ["Estimate", 3]].map(([l, n]) => ( +
+ {l} + + {n} +
+ ))} +
+
+

Win Rate

+
94%
+ +
+ + + +
+
+
+

Storm-Attributed Revenue

19 Storm Leads
+
24%
+ +
$56K of $626K
+
+
+ + {/* ===== Roof / Risk / Weather ===== */} +
+
+

Roof Condition

Territory health snapshot
+
+ +
+
+ + +
+
+ +
+

Risk Index

Storm Impact Probability
+
+ +
+
+ + +
+
+ +
+
+
+
80°F
+
Plano, US
+
+
+
Updated: 02:48 PM
+
+ 10 mph + 82% +
+
+
+
+ {[["12 PM", 94], ["5 PM", 79], ["8 PM", 82], ["10 PM", 90], ["1 AM", 92], ["5 AM", 79]].map(([t, te]) => ( +
+
{t}
+ +
{te}°
+
+ ))} +
+
+
+ + {/* ===== P&L Summary ===== */} +

P&L Summary

+
+ + + + +
+ + {/* ===== Revenue Potential / Top Reps ===== */} +
+
+
+

Revenue Potential

By neighborhood rating
+ live +
+ +
+
+

Top Sales Reps

+ {[["Hannah Reyes", "hannah@lynkeduppro.com", C.orange], ["Cody Tatum", "cody@lynkeduppro.com", C.cyan], ["Travis Boone", "travis@lynkeduppro.com", C.blue]].map(([nm, em, col], i) => ( +
+ 0{i + 1} + {(nm as string).split(" ").map((x) => x[0]).join("")} +
+
{nm}
+
{em}
+
+ $112,500 +
+ ))} +
+
+ +
+
+
+ ); +} + +/* ---- sub components ---- */ +function BigStat({ icon, ic, tag, value, label, spark, sc }: { icon: React.ReactNode; ic: string; tag: string; value: string; label: string; spark: number[]; sc: string }) { + return ( +
+
+ {icon} + {tag} +
+
+
+
{value}
+
{label}
+
+ +
+
+ ); +} + +function MiniStat({ icon, ic, value, label, sub, pill }: { icon: React.ReactNode; ic: string; value: string; label: string; sub: string; pill: string }) { + return ( +
+
+ {icon} + {sub} +
+
{value}
+
{label}
+
+ ); +} + +function PnlCard({ title, value, labels, yk, series }: { title: string; value: string; labels: string[]; yk: string[]; series: { color: string; values: number[] }[] }) { + return ( +
+

{title}

+
{value}
+ +
+ ); +} + +function Legend({ dot, text }: { dot: string; text: string }) { + return {text}; +} + +function Readout({ big, small, color }: { big: string; small: string; color?: string }) { + return ( +
+
{big}
+
{small}
+
+ ); +} diff --git a/src/components/dashboard/figicon.tsx b/src/components/dashboard/figicon.tsx new file mode 100644 index 0000000..ef9b5c6 --- /dev/null +++ b/src/components/dashboard/figicon.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { Sparkles } from "lucide-react"; +import { figmaIcons } from "./figma-icons"; + +/** Renders a Figma-exported icon (recolored to currentColor) or a fallback. */ +export function FigIcon({ name, size = 18 }: { name: string; size?: number }) { + const svg = figmaIcons[name]; + if (!svg) return ; + return ( + + ); +} diff --git a/src/components/dashboard/figma-icons.ts b/src/components/dashboard/figma-icons.ts new file mode 100644 index 0000000..705de77 --- /dev/null +++ b/src/components/dashboard/figma-icons.ts @@ -0,0 +1,32 @@ +// Auto-generated from Figma (Linkedup-Web). Colors normalized to currentColor. +export const figmaIcons: Record = { + "dashboard": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "dispatch": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "estimates": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "home": "\n\n\n", + "i_ar": "\n\n", + "i_bell": "\n\n\n\n\n\n\n\n\n\n\n\n\n", + "i_collected": "\n\n\n\n\n\n\n\n\n", + "i_hotleads": "\n\n", + "i_humidity": "\n\n\n", + "i_meets": "\n\n", + "i_payouts": "\n\n", + "i_revenue": "\n\n\n", + "i_search": "\n\n", + "i_theme": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "i_vendor": "\n\n", + "i_wind": "\n\n\n", + "leaderboard": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "leads": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "owners": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "people": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "pipeline": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "procanvas": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "projects": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "schedule": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "settings": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "storm": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "subtasks": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "territory": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "verify": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", +}; diff --git a/src/components/dashboard/sidebar.tsx b/src/components/dashboard/sidebar.tsx new file mode 100644 index 0000000..abe56c8 --- /dev/null +++ b/src/components/dashboard/sidebar.tsx @@ -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 ( + + ); +} + +function NavBtn({ it, active, onSelect }: { it: Item; active: string; onSelect: (k: string) => void }) { + return ( + + ); +} diff --git a/src/components/dashboard/topbar.tsx b/src/components/dashboard/topbar.tsx new file mode 100644 index 0000000..031dd49 --- /dev/null +++ b/src/components/dashboard/topbar.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { Sun, ChevronDown } from "lucide-react"; +import { FigIcon } from "./figicon"; + +export function Topbar({ theme, onToggle }: { theme: "dark" | "light"; onToggle: () => void }) { + return ( +
+
+

Dashboard Overview

+

Welcome back, here's what's happening with your territory

+
+
+ + + + +
+
+ ); +}