feat(agency): restore icons and dynamic nested client menus with expand/collapse states
This commit is contained in:
@@ -36,6 +36,20 @@ const Icons = {
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
</svg>
|
||||
),
|
||||
MessageCircle: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
</svg>
|
||||
),
|
||||
Kanban: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -53,6 +67,24 @@ const Icons = {
|
||||
<path d="M16 7v9" />
|
||||
</svg>
|
||||
),
|
||||
FileText: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14 2v6h6" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
<line x1="16" y1="17" x2="8" y2="17" />
|
||||
<polyline points="10 9 9 9 8 9" />
|
||||
</svg>
|
||||
),
|
||||
Users: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -70,6 +102,20 @@ const Icons = {
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
),
|
||||
Folder: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" />
|
||||
</svg>
|
||||
),
|
||||
Box: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -86,6 +132,23 @@ const Icons = {
|
||||
<line x1="12" y1="22.08" x2="12" y2="12" />
|
||||
</svg>
|
||||
),
|
||||
Server: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
|
||||
<rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
|
||||
<line x1="6" y1="6" x2="6.01" y2="6" />
|
||||
<line x1="6" y1="18" x2="6.01" y2="18" />
|
||||
</svg>
|
||||
),
|
||||
Repeat: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -118,6 +181,21 @@ const Icons = {
|
||||
<line x1="1" y1="10" x2="23" y2="10" />
|
||||
</svg>
|
||||
),
|
||||
TrendingDown: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="22 17 13.5 8.5 8.5 13.5 2 7" />
|
||||
<polyline points="16 17 22 17 22 11" />
|
||||
</svg>
|
||||
),
|
||||
Key: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -132,21 +210,6 @@ const Icons = {
|
||||
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
|
||||
</svg>
|
||||
),
|
||||
CheckSquare: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="9 11 12 14 22 4" />
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
||||
</svg>
|
||||
),
|
||||
Settings: () => (
|
||||
<svg
|
||||
width="14"
|
||||
@@ -159,7 +222,35 @@ const Icons = {
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</svg>
|
||||
),
|
||||
ChevronDown: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg>
|
||||
),
|
||||
ChevronRight: () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
@@ -169,11 +260,13 @@ function NavItem({
|
||||
label,
|
||||
active,
|
||||
badge,
|
||||
inset = false,
|
||||
}: {
|
||||
icon: React.ElementType;
|
||||
icon?: React.ElementType;
|
||||
label: string;
|
||||
active?: boolean;
|
||||
badge?: string;
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
@@ -184,6 +277,7 @@ function NavItem({
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
padding: "7px 10px",
|
||||
paddingLeft: inset ? 32 : 10,
|
||||
borderRadius: 6,
|
||||
background: active ? "#f6f4f0" : "transparent",
|
||||
color: active ? "#1a1a1a" : "#6b6560",
|
||||
@@ -201,15 +295,17 @@ function NavItem({
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<span
|
||||
style={{
|
||||
opacity: active ? 0.9 : 0.5,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
}}
|
||||
>
|
||||
<Icon />
|
||||
</span>
|
||||
{Icon && (
|
||||
<span
|
||||
style={{
|
||||
opacity: active ? 0.9 : 0.5,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
}}
|
||||
>
|
||||
<Icon />
|
||||
</span>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
{badge && (
|
||||
@@ -250,6 +346,11 @@ function SectionHeader({ children }: { children: React.ReactNode }) {
|
||||
export default function AgencyDashboard() {
|
||||
const { workspace } = useParams();
|
||||
const [activeTab, setActiveTab] = useState("find_clients");
|
||||
const [clientOpen, setClientOpen] = useState<string | null>("apex");
|
||||
|
||||
const toggleClient = (id: string) => {
|
||||
setClientOpen(clientOpen === id ? null : id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -357,17 +458,119 @@ export default function AgencyDashboard() {
|
||||
active={activeTab === "find_clients"}
|
||||
/>
|
||||
</div>
|
||||
<NavItem icon={Icons.Kanban} label="Pipeline" />
|
||||
<NavItem icon={Icons.Users} label="Clients" />
|
||||
<NavItem icon={Icons.MessageCircle} label="Outreach" />
|
||||
<NavItem icon={Icons.Kanban} label="Pipeline" badge="3" />
|
||||
<NavItem icon={Icons.FileText} label="Proposals & Contracts" />
|
||||
<NavItem icon={Icons.Users} label="Contacts" />
|
||||
|
||||
<SectionHeader>Delivery</SectionHeader>
|
||||
<NavItem icon={Icons.Box} label="Projects & Infra" badge="2" />
|
||||
<NavItem icon={Icons.Repeat} label="Retainers (Grow)" />
|
||||
<SectionHeader>Delivery (Projects)</SectionHeader>
|
||||
|
||||
<SectionHeader>Operations</SectionHeader>
|
||||
<NavItem icon={Icons.CheckSquare} label="Tasks" badge="5" />
|
||||
<NavItem icon={Icons.CreditCard} label="Billing & Margins" />
|
||||
<NavItem icon={Icons.Key} label="Secrets Manager" />
|
||||
{/* Nested Client Hub: Apex Plumbing */}
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<button
|
||||
onClick={() => toggleClient("apex")}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
padding: "7px 10px",
|
||||
borderRadius: 6,
|
||||
background: "transparent",
|
||||
color: "#1a1a1a",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<Icons.Folder />
|
||||
Apex Plumbing
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: "#a09a90",
|
||||
transform:
|
||||
clientOpen === "apex" ? "rotate(180deg)" : "rotate(0deg)",
|
||||
transition: "transform 0.15s",
|
||||
}}
|
||||
>
|
||||
<Icons.ChevronDown />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{clientOpen === "apex" && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<NavItem inset label="Projects (Live)" badge="1" />
|
||||
<NavItem inset label="Infrastructure (Hosting)" />
|
||||
<NavItem inset label="Secrets Manager" />
|
||||
<NavItem inset label="Client Contacts" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Nested Client Hub: Barton Creek */}
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<button
|
||||
onClick={() => toggleClient("barton")}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
padding: "7px 10px",
|
||||
borderRadius: 6,
|
||||
background: "transparent",
|
||||
color: "#1a1a1a",
|
||||
fontWeight: 600,
|
||||
fontSize: "13px",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<Icons.Folder />
|
||||
Barton Creek HVAC
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: "#a09a90",
|
||||
transform:
|
||||
clientOpen === "barton" ? "rotate(180deg)" : "rotate(0deg)",
|
||||
transition: "transform 0.15s",
|
||||
}}
|
||||
>
|
||||
<Icons.ChevronDown />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{clientOpen === "barton" && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<NavItem inset label="Projects (Live)" />
|
||||
<NavItem inset label="Infrastructure (Hosting)" />
|
||||
<NavItem inset label="Secrets Manager" />
|
||||
<NavItem inset label="Client Contacts" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SectionHeader>Finance & Ops</SectionHeader>
|
||||
<NavItem icon={Icons.CreditCard} label="Invoices & Payments" />
|
||||
<NavItem icon={Icons.TrendingDown} label="Expenses" />
|
||||
<NavItem icon={Icons.Users} label="Users" />
|
||||
<NavItem icon={Icons.Settings} label="Settings" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user