319 lines
13 KiB
JavaScript
319 lines
13 KiB
JavaScript
// ============================================================
|
|
// page-customer.jsx — CRM company record page.
|
|
// Header (logo + name + status + actions), 2-col layout:
|
|
// left — details panel (industry, owner, links, deals)
|
|
// right — tabbed work area (Overview / Activity / People / Notes)
|
|
// Pure content. Wrap in any *Chrome to compose.
|
|
// ============================================================
|
|
|
|
const CustomerBody = ({ theme = "light" }) => {
|
|
const dark = theme === "dark";
|
|
const c = dark ? {
|
|
bg: "#0f0f14", panel: "#13131a", border: "#ffffff10",
|
|
text: "#e8e8ee", subtext: "#9a9aa6", muted: "#6a6a78",
|
|
rowAlt: "#ffffff04", accent: "#7a78ff", ring: "#5e5cff",
|
|
chipBg: "#ffffff08", chipText: "#dcdce4",
|
|
inputBg: "#0a0a10",
|
|
} : {
|
|
bg: "#fcfcfb", panel: "#ffffff", border: "#e8e8e3",
|
|
text: "#111", subtext: "#5a5a5e", muted: "#8a8a90",
|
|
rowAlt: "#f9f9f6", accent: "#5e5cff", ring: "#5e5cff",
|
|
chipBg: "#f1f0eb", chipText: "#3a3a3e",
|
|
inputBg: "#fff",
|
|
};
|
|
|
|
const KV = ({ k, v }) => (
|
|
<div style={{
|
|
display: "grid", gridTemplateColumns: "110px 1fr", gap: 10,
|
|
padding: "8px 0", fontSize: 13, alignItems: "baseline",
|
|
}}>
|
|
<span style={{ color: c.muted }}>{k}</span>
|
|
<span style={{ color: c.text }}>{v}</span>
|
|
</div>
|
|
);
|
|
|
|
const Tag = ({ children, color }) => (
|
|
<span style={{
|
|
display: "inline-flex", alignItems: "center", gap: 5,
|
|
padding: "2px 8px", borderRadius: 999,
|
|
background: color ? `${color}1f` : c.chipBg,
|
|
color: color || c.chipText,
|
|
fontSize: 11, fontWeight: 500, whiteSpace: "nowrap",
|
|
}}>
|
|
{color && <span style={{
|
|
width: 6, height: 6, borderRadius: "50%", background: color,
|
|
}}></span>}
|
|
{children}
|
|
</span>
|
|
);
|
|
|
|
const Avatar = ({ name, color = "#d4b8a8", size = 24, ring }) => (
|
|
<div style={{
|
|
width: size, height: size, borderRadius: "50%", background: color,
|
|
fontSize: size * 0.4, fontWeight: 600, color: "#3a2820",
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
flexShrink: 0, boxShadow: ring ? `0 0 0 2px ${c.panel}, 0 0 0 3px ${ring}` : "none",
|
|
}}>{name}</div>
|
|
);
|
|
|
|
return (
|
|
<div style={{
|
|
display: "grid", gridTemplateRows: "auto 1fr", height: "100%",
|
|
background: c.bg, color: c.text, fontFamily: SANS, overflow: "hidden",
|
|
}}>
|
|
{/* Header */}
|
|
<div style={{
|
|
padding: "16px 28px", borderBottom: `1px solid ${c.border}`,
|
|
display: "flex", alignItems: "center", gap: 16,
|
|
}}>
|
|
<div style={{
|
|
width: 44, height: 44, borderRadius: 10,
|
|
background: "linear-gradient(135deg, #f6c560 0%, #e08c4a 100%)",
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
fontWeight: 700, fontSize: 18, color: "#3a2210",
|
|
}}>NS</div>
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 3 }}>
|
|
<h1 style={{
|
|
fontSize: 22, fontWeight: 600, margin: 0, letterSpacing: "-0.01em",
|
|
}}>Northstar Logistics</h1>
|
|
<Tag color="#22c55e">Customer</Tag>
|
|
<Tag color="#5e5cff">Tier 1</Tag>
|
|
</div>
|
|
<div style={{
|
|
fontSize: 12, color: c.muted, display: "flex", gap: 14, alignItems: "center",
|
|
}}>
|
|
<span>northstarlogistics.com</span>
|
|
<span>·</span>
|
|
<span>Created Aug 2024</span>
|
|
<span>·</span>
|
|
<span>Last touched 2h ago</span>
|
|
</div>
|
|
</div>
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
<button style={{
|
|
padding: "7px 12px", borderRadius: 6, fontSize: 12, fontFamily: SANS,
|
|
background: dark ? "#ffffff08" : "#fff",
|
|
border: `1px solid ${c.border}`,
|
|
color: c.text, cursor: "pointer", display: "flex", alignItems: "center", gap: 6,
|
|
}}><Icon d={P.star} size={13}/> Star</button>
|
|
<button style={{
|
|
padding: "7px 12px", borderRadius: 6, fontSize: 12, fontFamily: SANS,
|
|
background: dark ? "#ffffff08" : "#fff",
|
|
border: `1px solid ${c.border}`,
|
|
color: c.text, cursor: "pointer",
|
|
}}>Share</button>
|
|
<button style={{
|
|
padding: "7px 14px", borderRadius: 6, fontSize: 12, fontFamily: SANS,
|
|
background: dark ? "#fff" : "#111",
|
|
color: dark ? "#111" : "#fff",
|
|
border: "none", cursor: "pointer", fontWeight: 500,
|
|
display: "flex", alignItems: "center", gap: 6,
|
|
}}><Icon d={P.plus} size={12}/> Log activity</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div style={{
|
|
display: "grid", gridTemplateColumns: "320px 1fr", gap: 0,
|
|
overflow: "hidden",
|
|
}}>
|
|
{/* Details rail */}
|
|
<div style={{
|
|
padding: "20px 24px", borderRight: `1px solid ${c.border}`,
|
|
overflowY: "auto",
|
|
}}>
|
|
<div style={{
|
|
fontSize: 11, color: c.muted, letterSpacing: "0.06em",
|
|
textTransform: "uppercase", fontWeight: 500, marginBottom: 6,
|
|
}}>About</div>
|
|
<KV k="Industry" v="Freight & Logistics" />
|
|
<KV k="Employees" v="240 — 500" />
|
|
<KV k="HQ" v="Rotterdam, NL" />
|
|
<KV k="Founded" v="2011" />
|
|
<KV k="Owner" v={
|
|
<span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
|
|
<Avatar name="MR" size={18} /> Mira Reyes
|
|
</span>
|
|
} />
|
|
<KV k="Source" v="Referral · DH" />
|
|
|
|
<div style={{
|
|
fontSize: 11, color: c.muted, letterSpacing: "0.06em",
|
|
textTransform: "uppercase", fontWeight: 500, marginTop: 22, marginBottom: 8,
|
|
}}>Tags</div>
|
|
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
|
|
<Tag color="#e08c4a">Enterprise</Tag>
|
|
<Tag color="#22c55e">Renewal Q3</Tag>
|
|
<Tag color="#5e5cff">EMEA</Tag>
|
|
<Tag>Logistics</Tag>
|
|
</div>
|
|
|
|
<div style={{
|
|
fontSize: 11, color: c.muted, letterSpacing: "0.06em",
|
|
textTransform: "uppercase", fontWeight: 500, marginTop: 22, marginBottom: 8,
|
|
}}>Open opportunities</div>
|
|
{[
|
|
{ name: "Q3 — Carrier API", v: "€84,000", stage: "Negotiation", p: 70 },
|
|
{ name: "EU expansion", v: "€38,500", stage: "Proposal", p: 40 },
|
|
{ name: "Renewal · Pro", v: "€24,000", stage: "Discovery", p: 15 },
|
|
].map(d => (
|
|
<div key={d.name} style={{
|
|
padding: "10px 12px", borderRadius: 8,
|
|
background: c.panel, border: `1px solid ${c.border}`,
|
|
marginBottom: 8,
|
|
}}>
|
|
<div style={{
|
|
display: "flex", justifyContent: "space-between",
|
|
alignItems: "baseline", marginBottom: 6,
|
|
}}>
|
|
<span style={{ fontSize: 13, fontWeight: 500 }}>{d.name}</span>
|
|
<span style={{
|
|
fontSize: 12, color: c.subtext, fontVariantNumeric: "tabular-nums",
|
|
}}>{d.v}</span>
|
|
</div>
|
|
<div style={{
|
|
fontSize: 11, color: c.muted, display: "flex",
|
|
justifyContent: "space-between", marginBottom: 4,
|
|
}}>
|
|
<span>{d.stage}</span><span>{d.p}%</span>
|
|
</div>
|
|
<div style={{
|
|
height: 3, borderRadius: 2,
|
|
background: dark ? "#ffffff10" : "#eeeee9",
|
|
overflow: "hidden",
|
|
}}>
|
|
<div style={{
|
|
width: `${d.p}%`, height: "100%",
|
|
background: d.p > 60 ? "#22c55e" : d.p > 30 ? "#f6c560" : "#9a9aa6",
|
|
}}></div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tabs + work area */}
|
|
<div style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
|
<div style={{
|
|
padding: "0 28px", borderBottom: `1px solid ${c.border}`,
|
|
display: "flex", gap: 0,
|
|
}}>
|
|
{["Overview", "Activity", "People", "Notes", "Files"].map((t, i) => (
|
|
<div key={t} style={{
|
|
padding: "14px 14px", fontSize: 13, fontWeight: 500,
|
|
color: i === 1 ? c.text : c.muted,
|
|
borderBottom: i === 1 ? `2px solid ${c.accent}` : "2px solid transparent",
|
|
cursor: "pointer", position: "relative", top: 1,
|
|
}}>{t}{t === "Activity" && " · 28"}</div>
|
|
))}
|
|
</div>
|
|
|
|
<div style={{ flex: 1, overflowY: "auto", padding: "24px 28px" }}>
|
|
{/* KPI row */}
|
|
<div style={{
|
|
display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12,
|
|
marginBottom: 22,
|
|
}}>
|
|
{[
|
|
{ l: "Pipeline", v: "€146.5k", s: "+€12.4k 30d", up: true },
|
|
{ l: "Closed-won", v: "€220k", s: "lifetime" },
|
|
{ l: "Open deals", v: "3", s: "1 stalled", warn: true },
|
|
{ l: "Health", v: "82 / 100", s: "stable", up: true },
|
|
].map(k => (
|
|
<div key={k.l} style={{
|
|
padding: "14px 16px", borderRadius: 10,
|
|
background: c.panel, border: `1px solid ${c.border}`,
|
|
}}>
|
|
<div style={{ fontSize: 11, color: c.muted, marginBottom: 6 }}>{k.l}</div>
|
|
<div style={{
|
|
fontSize: 22, fontWeight: 600, letterSpacing: "-0.01em",
|
|
}}>{k.v}</div>
|
|
<div style={{
|
|
fontSize: 11, color: k.up ? "#22c55e" : k.warn ? "#f6c560" : c.muted,
|
|
marginTop: 2,
|
|
}}>{k.s}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Activity timeline */}
|
|
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12 }}>Activity</div>
|
|
<div style={{ position: "relative", paddingLeft: 22 }}>
|
|
<div style={{
|
|
position: "absolute", left: 9, top: 6, bottom: 6,
|
|
width: 1, background: c.border,
|
|
}}></div>
|
|
{[
|
|
{ dot: "#22c55e", t: "Deal moved to Negotiation",
|
|
who: "Mira Reyes", w: "Q3 — Carrier API · €84,000",
|
|
when: "2 hours ago" },
|
|
{ dot: "#5e5cff", t: "Email sent · proposal v4",
|
|
who: "Mira Reyes", w: "To: Sun Kim, Devi Patel — opened 6 times",
|
|
when: "Yesterday" },
|
|
{ dot: "#f6c560", t: "Call logged · 32 min",
|
|
who: "Theo Roux", w: "Walkthrough with their ops lead — promising",
|
|
when: "2 days ago" },
|
|
{ dot: "#9a9aa6", t: "Note added",
|
|
who: "Mira Reyes", w: "They want SSO and SCIM by Sept. — gating item.",
|
|
when: "4 days ago" },
|
|
].map((a, i) => (
|
|
<div key={i} style={{ marginBottom: 16, position: "relative" }}>
|
|
<span style={{
|
|
position: "absolute", left: -19, top: 4, width: 11, height: 11,
|
|
background: a.dot, borderRadius: "50%",
|
|
boxShadow: `0 0 0 3px ${c.bg}`,
|
|
}}></span>
|
|
<div style={{
|
|
display: "flex", justifyContent: "space-between", alignItems: "baseline",
|
|
}}>
|
|
<span style={{ fontSize: 13, fontWeight: 500 }}>{a.t}</span>
|
|
<span style={{ fontSize: 11, color: c.muted }}>{a.when}</span>
|
|
</div>
|
|
<div style={{ fontSize: 12, color: c.subtext, marginTop: 2 }}>
|
|
{a.who} · {a.w}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* People row */}
|
|
<div style={{
|
|
fontSize: 13, fontWeight: 600, marginTop: 12, marginBottom: 12,
|
|
display: "flex", justifyContent: "space-between", alignItems: "center",
|
|
}}>
|
|
<span>People at Northstar · 6</span>
|
|
<button style={{
|
|
background: "transparent", border: "none", color: c.accent,
|
|
fontSize: 12, fontFamily: SANS, cursor: "pointer",
|
|
}}>View all →</button>
|
|
</div>
|
|
<div style={{
|
|
display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10,
|
|
}}>
|
|
{[
|
|
{ i: "SK", n: "Sun Kim", r: "VP Operations", c: "#e8a87c" },
|
|
{ i: "DP", n: "Devi Patel", r: "Procurement", c: "#a8c8e8" },
|
|
{ i: "TR", n: "Theo Roux", r: "CFO", c: "#c8e8a8" },
|
|
].map(p => (
|
|
<div key={p.i} style={{
|
|
display: "flex", alignItems: "center", gap: 10,
|
|
padding: "10px 12px", borderRadius: 8,
|
|
background: c.panel, border: `1px solid ${c.border}`,
|
|
}}>
|
|
<Avatar name={p.i} color={p.c} size={32} />
|
|
<div style={{ minWidth: 0 }}>
|
|
<div style={{ fontSize: 13, fontWeight: 500 }}>{p.n}</div>
|
|
<div style={{ fontSize: 11, color: c.muted }}>{p.r}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
window.CustomerBody = CustomerBody;
|