feat: Quiet Luxury design system + Pipeline (ex-Lead) redesign

- Design system foundations: Plus Jakarta Sans font, shadow-card/radius
  tokens in @theme, design-reference/DESIGN-SYSTEM.md with component inventory
- Collapsible admin shell (AdminShell): smooth w-64<->w-20 sidebar, new top
  header with "Admin" + avatar placeholder, localStorage-persisted state
- Rename route /admin/leads -> /admin/pipeline (redirect stubs preserved),
  nav label Lead -> Pipeline; DB table unchanged
- Reusable primitives: StatusBadge, SearchInput, SegmentedToggle (dual-theme)
- Luxury restyle of lead table, kanban (6 stages + @dnd-kit intact), PageHeader
- Tokenize editable-cell / option-multi-select for dark-mode legibility

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 16:16:44 +02:00
parent 43cb7e7469
commit 86e1499e8f
34 changed files with 3134 additions and 231 deletions
+65
View File
@@ -0,0 +1,65 @@
"use client";
import { useEffect, useState } from "react";
import { Menu, User } from "lucide-react";
import { AdminSidebar } from "@/components/admin/AdminSidebar";
const SIDEBAR_STORAGE_KEY = "iamcavalli:sidebar";
/**
* Client shell for every /admin/* page: collapsible sidebar + top header +
* <main>. Collapse state is persisted to localStorage and read on mount
* (starts expanded on server/first paint to avoid a hydration mismatch,
* then reconciles client-side — mirrors the pattern used by useTheme).
*/
export function AdminShell({ children }: { children: React.ReactNode }) {
const [collapsed, setCollapsed] = useState(false);
useEffect(() => {
const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY);
if (stored === "collapsed") setCollapsed(true);
}, []);
function toggleCollapsed() {
setCollapsed((prev) => {
const next = !prev;
window.localStorage.setItem(SIDEBAR_STORAGE_KEY, next ? "collapsed" : "expanded");
return next;
});
}
return (
<div className="flex min-h-screen bg-background">
<AdminSidebar collapsed={collapsed} />
<div className="flex-1 flex flex-col min-w-0">
{/* Top header bar */}
<header className="h-16 shrink-0 bg-card border-b border-border px-6 flex items-center justify-between">
<div className="flex items-center gap-4">
<button
type="button"
onClick={toggleCollapsed}
title="Espandi/Comprimi Sidebar"
aria-label="Espandi/Comprimi Sidebar"
className="p-2 -ml-2 rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors focus:outline-none"
>
<Menu className="w-5 h-5" />
</button>
<span className="text-xs font-medium text-muted-foreground">Area di Lavoro</span>
</div>
<div className="flex items-center gap-3">
<span className="text-xs font-semibold text-foreground">Admin</span>
{/* TODO: gestione utenti/collaboratori — placeholder avatar until
real user accounts + uploaded profile images are wired up. */}
<div className="w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center shrink-0">
<User className="w-4 h-4" />
</div>
</div>
</header>
<main className="flex-1 px-8 py-8 overflow-y-auto">{children}</main>
</div>
</div>
);
}
+30 -11
View File
@@ -15,10 +15,11 @@ import {
FileText,
} from "lucide-react";
import ThemeToggle from "@/components/ThemeToggle";
import { cn } from "@/lib/utils";
const NAV_ITEMS = [
{ href: "/admin", label: "Dashboard", icon: LayoutDashboard, exact: true },
{ href: "/admin/leads", label: "Lead", icon: Zap },
{ href: "/admin/pipeline", label: "Pipeline", icon: Zap },
{ href: "/admin/clients", label: "Clienti", icon: Users },
{ href: "/admin/projects", label: "Progetti", icon: FolderOpen },
{ href: "/admin/preventivi", label: "Preventivi", icon: FileText },
@@ -27,17 +28,32 @@ const NAV_ITEMS = [
{ href: "/admin/impostazioni", label: "Impostazioni", icon: Settings },
];
export function AdminSidebar() {
export function AdminSidebar({ collapsed = false }: { collapsed?: boolean }) {
const pathname = usePathname();
const isActive = (href: string, exact?: boolean) =>
exact ? pathname === href : pathname === href || pathname.startsWith(href + "/");
// Text fades out immediately on collapse, but only fades back in once the
// width transition is underway on expand (avoids reflow mid-animation).
const textClass = cn(
"whitespace-nowrap transition-opacity duration-200 delay-150",
collapsed && "opacity-0 pointer-events-none delay-0"
);
return (
<aside className="w-56 shrink-0 min-h-screen bg-[#1A463C] flex flex-col">
<aside
className={cn(
"shrink-0 min-h-screen bg-[#1A463C] flex flex-col overflow-hidden",
"transition-[width] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)]",
collapsed ? "w-20" : "w-64"
)}
>
{/* Logo */}
<div className="px-5 py-5 border-b border-white/10">
<span className="font-bold text-white tracking-tight text-sm">iamcavalli</span>
<span className={cn("font-bold text-white tracking-tight text-sm", textClass)}>
iamcavalli
</span>
</div>
{/* Nav links */}
@@ -48,14 +64,16 @@ export function AdminSidebar() {
<Link
key={href}
href={href}
className={`flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors ${
title={collapsed ? label : undefined}
className={cn(
"flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors",
active
? "bg-white/15 text-white font-medium"
: "text-white/65 hover:text-white hover:bg-white/10"
}`}
)}
>
<Icon size={16} strokeWidth={1.8} />
{label}
<Icon size={16} strokeWidth={1.8} className="shrink-0" />
<span className={textClass}>{label}</span>
</Link>
);
})}
@@ -64,15 +82,16 @@ export function AdminSidebar() {
{/* Theme + Logout */}
<div className="px-3 py-4 border-t border-white/10 flex flex-col gap-2">
<div className="flex items-center justify-between px-3">
<span className="text-xs text-white/50">Tema</span>
<span className={cn("text-xs text-white/50", textClass)}>Tema</span>
<ThemeToggle />
</div>
<button
onClick={() => signOut({ callbackUrl: "/admin/login" })}
title={collapsed ? "Esci" : undefined}
className="flex items-center gap-3 px-3 py-2 w-full rounded-md text-sm text-white/65 hover:text-white hover:bg-white/10 transition-colors"
>
<LogOut size={16} strokeWidth={1.8} />
Esci
<LogOut size={16} strokeWidth={1.8} className="shrink-0" />
<span className={textClass}>Esci</span>
</button>
</div>
</aside>
+3 -3
View File
@@ -6,10 +6,10 @@ export function PageHeader({ title, subtitle, action }: {
action?: ReactNode;
}) {
return (
<div className="mb-6 flex items-start justify-between gap-4">
<div className="mb-6 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-[#1a1a1a]">{title}</h1>
{subtitle && <p className="mt-1 text-sm text-[#71717a]">{subtitle}</p>}
<h1 className="text-2xl font-semibold tracking-tight text-foreground">{title}</h1>
{subtitle && <p className="mt-1 text-xs text-muted-foreground">{subtitle}</p>}
</div>
{action}
</div>
@@ -47,7 +47,7 @@ export async function FollowUpWidget() {
</span>
</div>
<Link
href="/admin/leads"
href="/admin/pipeline"
className="text-xs font-medium text-[#71717a] hover:text-[#1A463C] transition-colors"
>
Vedi tutti
@@ -59,7 +59,7 @@ export async function FollowUpWidget() {
{visible.map((lead) => (
<li key={lead.id}>
<Link
href={`/admin/leads/${lead.id}`}
href={`/admin/pipeline/${lead.id}`}
className="flex items-center gap-3 px-5 py-3 hover:bg-[#f9f9f9] transition-colors group"
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#1A463C]/10 text-[#1A463C] text-xs font-bold">
@@ -85,7 +85,7 @@ export async function FollowUpWidget() {
{leads.length > MAX_ROWS && (
<Link
href="/admin/leads"
href="/admin/pipeline"
className="block px-5 py-2.5 text-center text-xs font-medium text-[#71717a] hover:text-[#1A463C] border-t border-[#f4f4f5] transition-colors"
>
Vedi tutti i {leads.length} lead
+1 -1
View File
@@ -13,7 +13,7 @@ import {
renameLeadTag,
deleteTranscript,
convertLeadToClient,
} from "@/app/admin/leads/actions";
} from "@/app/admin/pipeline/actions";
import { formatDistanceToNow, format } from "date-fns";
import { it } from "date-fns/locale";
import Link from "next/link";
+1 -1
View File
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { createLeadSchema, LEAD_STAGES, type CreateLeadInput } from "@/lib/lead-validators";
import { Lead } from "@/db/schema";
import { createLead, updateLead } from "@/app/admin/leads/actions";
import { createLead, updateLead } from "@/app/admin/pipeline/actions";
import {
Dialog,
DialogContent,
+34 -57
View File
@@ -3,28 +3,17 @@
import Link from "next/link";
import { useState, useRef, useEffect, useTransition } from "react";
import { useRouter } from "next/navigation";
import { Check } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Check, ArrowRight } from "lucide-react";
import { EditableCell } from "@/components/ui/editable-cell";
import { OptionMultiSelect } from "@/components/ui/option-multi-select";
import { StatusBadge } from "@/components/ui/StatusBadge";
import {
updateLeadField,
addLeadTag,
removeLeadTag,
renameLeadTag,
} from "@/app/admin/leads/actions";
} from "@/app/admin/pipeline/actions";
import type { LeadWithTags, LeadFieldOptions } from "@/lib/admin-queries";
import { cn } from "@/lib/utils";
const STAGE_COLOR: Record<string, string> = {
contacted: "bg-blue-100 text-blue-800",
qualified: "bg-purple-100 text-purple-800",
proposal_sent: "bg-amber-100 text-amber-800",
negotiating: "bg-orange-100 text-orange-800",
won: "bg-green-100 text-green-800",
lost: "bg-red-100 text-red-800",
};
const COLUMN_HEADERS = [
"Nome",
@@ -61,23 +50,15 @@ function StatusCell({
}, []);
return (
<div ref={containerRef} className="relative w-full min-w-[120px]">
<div ref={containerRef} className="relative w-full min-w-[120px] flex justify-center">
<div
onClick={() => setIsOpen((v) => !v)}
className="flex items-center gap-1 px-2 py-1 rounded transition-colors duration-150 min-h-[28px] cursor-pointer hover:bg-[#f0f0f0]"
className="flex items-center gap-1 px-2 py-1 rounded transition-colors duration-150 min-h-[28px] cursor-pointer hover:bg-muted"
>
<Badge
variant="outline"
className={cn(
STAGE_COLOR[lead.status] ?? "",
"border-transparent text-xs px-2 py-0.5 font-medium truncate"
)}
>
{lead.status.replace(/_/g, " ")}
</Badge>
<StatusBadge status={lead.status} />
</div>
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border border-[#e5e7eb] rounded-md shadow-md p-1.5 z-20 min-w-[180px]">
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-1 bg-popover border border-border rounded-md shadow-card-hover p-1.5 z-20 min-w-[180px]">
<div className="flex flex-col gap-0.5">
{options.status.map((stage) => (
<button
@@ -87,17 +68,12 @@ function StatusCell({
setIsOpen(false);
run(() => updateLeadField(lead.id, "status", stage));
}}
className="flex items-center gap-2 rounded px-1.5 py-1 hover:bg-[#f5f5f5] text-left"
className="flex items-center gap-2 rounded px-1.5 py-1 hover:bg-muted text-left"
>
<span className="w-3.5 flex-shrink-0">
{lead.status === stage && <Check className="h-3.5 w-3.5 text-[#1A463C]" />}
{lead.status === stage && <Check className="h-3.5 w-3.5 text-primary" />}
</span>
<Badge
variant="outline"
className={cn(STAGE_COLOR[stage] ?? "", "border-transparent text-xs px-2 py-0.5 font-medium truncate")}
>
{stage.replace(/_/g, " ")}
</Badge>
<StatusBadge status={stage} />
</button>
))}
</div>
@@ -132,8 +108,8 @@ function LeadRow({
return (
<>
<tr className="border-b border-[#e5e7eb] hover:bg-[#f9f9f9] transition-colors duration-150">
<td className="py-2 px-3 font-medium text-[#1a1a1a] min-w-[160px]">
<tr className="hover:bg-muted/40 transition-colors duration-150">
<td className="py-4 px-6 font-medium text-foreground min-w-[160px]">
<EditableCell
value={lead.name}
type="text"
@@ -141,7 +117,7 @@ function LeadRow({
onSave={(v) => run(() => updateLeadField(lead.id, "name", v))}
/>
</td>
<td className="py-2 px-3 min-w-[160px]">
<td className="py-4 px-6 min-w-[160px] text-muted-foreground text-xs">
<EditableCell
value={lead.email ?? ""}
type="text"
@@ -149,7 +125,7 @@ function LeadRow({
onSave={(v) => run(() => updateLeadField(lead.id, "email", v))}
/>
</td>
<td className="py-2 px-3 min-w-[140px]">
<td className="py-4 px-6 min-w-[140px] text-muted-foreground text-xs font-mono">
<EditableCell
value={lead.phone ?? ""}
type="text"
@@ -157,7 +133,7 @@ function LeadRow({
onSave={(v) => run(() => updateLeadField(lead.id, "phone", v))}
/>
</td>
<td className="py-2 px-3 min-w-[140px]">
<td className="py-4 px-6 min-w-[140px]">
<EditableCell
value={lead.company ?? ""}
type="text"
@@ -165,10 +141,10 @@ function LeadRow({
onSave={(v) => run(() => updateLeadField(lead.id, "company", v))}
/>
</td>
<td className="py-2 px-3 min-w-[120px]">
<td className="py-4 px-6 min-w-[120px] text-center">
<StatusCell lead={lead} options={options} run={run} />
</td>
<td className="py-2 px-3 min-w-[180px]">
<td className="py-4 px-6 min-w-[180px]">
<EditableCell
value={lead.next_action ?? ""}
type="text"
@@ -176,7 +152,7 @@ function LeadRow({
onSave={(v) => run(() => updateLeadField(lead.id, "next_action", v))}
/>
</td>
<td className="py-2 px-3 min-w-[180px]">
<td className="py-4 px-6 min-w-[180px] text-center">
<OptionMultiSelect
values={lead.tags}
options={options.tags}
@@ -185,15 +161,19 @@ function LeadRow({
onRename={(o, n) => run(() => renameLeadTag(o, n))}
/>
</td>
<td className="py-2 px-3 min-w-[80px]">
<Button variant="ghost" size="sm" asChild>
<Link href={`/admin/leads/${lead.id}`}>Dettagli</Link>
</Button>
<td className="py-4 px-6 min-w-[80px] text-right">
<Link
href={`/admin/pipeline/${lead.id}`}
className="text-xs font-medium text-primary hover:text-primary/80 inline-flex items-center gap-1 group transition-colors"
>
Dettagli
<ArrowRight className="w-3 h-3 transform group-hover:translate-x-0.5 transition-transform" />
</Link>
</td>
</tr>
{error && (
<tr>
<td colSpan={COL_COUNT} className="py-1 px-3 text-xs text-red-600">
<td colSpan={COL_COUNT} className="py-1 px-6 text-xs text-destructive">
{error}
</td>
</tr>
@@ -210,22 +190,19 @@ export function LeadTable({
options: LeadFieldOptions;
}) {
return (
<div className="bg-white rounded-xl border border-[#e5e7eb]">
<div className="bg-card rounded-xl border border-border shadow-card overflow-hidden">
<div className="overflow-x-auto min-h-[300px]">
<table className="w-full text-sm">
<thead className="bg-[#f9f9f9] border-b border-[#e5e7eb] sticky top-0 z-[1]">
<tr>
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-border bg-muted/50 text-muted-foreground text-[11px] font-semibold uppercase tracking-wider">
{COLUMN_HEADERS.map((header) => (
<th
key={header}
className="text-left py-2 px-3 font-semibold text-[#71717a]"
>
<th key={header} className="py-4 px-6">
{header}
</th>
))}
</tr>
</thead>
<tbody>
<tbody className="divide-y divide-border text-sm">
{leads.map((lead) => (
<LeadRow key={lead.id} lead={lead} options={options} />
))}
@@ -234,7 +211,7 @@ export function LeadTable({
</div>
{leads.length === 0 && (
<div className="text-center py-8 text-gray-500">Nessun lead trovato</div>
<div className="text-center py-8 text-muted-foreground">Nessun lead trovato</div>
)}
</div>
);
+21 -21
View File
@@ -13,7 +13,7 @@ import {
useDroppable,
useDraggable,
} from "@dnd-kit/core";
import { updateLeadField } from "@/app/admin/leads/actions";
import { updateLeadField } from "@/app/admin/pipeline/actions";
import type { LeadWithTags } from "@/lib/admin-queries";
type LeadStage = "contacted" | "qualified" | "proposal_sent" | "negotiating" | "won" | "lost";
@@ -24,12 +24,12 @@ const LEAD_COLUMNS: {
headerClass: string;
dotClass: string;
}[] = [
{ id: "contacted", label: "Contattato", headerClass: "text-[#71717a]", dotClass: "bg-[#d4d4d8]" },
{ id: "qualified", label: "Qualificato", headerClass: "text-purple-700", dotClass: "bg-purple-400" },
{ id: "proposal_sent", label: "Offerta inviata", headerClass: "text-amber-700", dotClass: "bg-amber-400" },
{ id: "negotiating", label: "Trattativa", headerClass: "text-orange-700", dotClass: "bg-orange-400" },
{ id: "won", label: "Vinto", headerClass: "text-green-700", dotClass: "bg-green-500" },
{ id: "lost", label: "Perso", headerClass: "text-red-700", dotClass: "bg-red-400" },
{ id: "contacted", label: "Contattato", headerClass: "text-muted-foreground", dotClass: "bg-blue-400 dark:bg-blue-500" },
{ id: "qualified", label: "Qualificato", headerClass: "text-purple-700 dark:text-purple-400", dotClass: "bg-purple-400 dark:bg-purple-500" },
{ id: "proposal_sent", label: "Offerta inviata", headerClass: "text-amber-700 dark:text-amber-400", dotClass: "bg-amber-400 dark:bg-amber-500" },
{ id: "negotiating", label: "Trattativa", headerClass: "text-orange-700 dark:text-orange-400", dotClass: "bg-orange-400 dark:bg-orange-500" },
{ id: "won", label: "Vinto", headerClass: "text-emerald-700 dark:text-emerald-400", dotClass: "bg-emerald-500" },
{ id: "lost", label: "Perso", headerClass: "text-red-700 dark:text-red-400", dotClass: "bg-red-400 dark:bg-red-500" },
];
const VALID_STAGES = LEAD_COLUMNS.map((c) => c.id) as string[];
@@ -49,26 +49,26 @@ function DroppableColumn({
return (
<div
ref={setNodeRef}
className={`flex flex-col rounded-xl border-2 transition-colors ${
isOver ? "border-[#1A463C] bg-[#1A463C]/5" : "border-[#e5e7eb] bg-[#f9f9f9]"
className={`flex flex-col rounded-xl border p-4 transition-colors ${
isOver ? "border-primary bg-primary/5" : "border-border bg-muted/60"
} min-h-[240px]`}
>
<div className={`px-4 py-3 flex items-center justify-between ${headerClass}`}>
<div className={`flex items-center justify-between pb-2 mb-2 border-b border-border ${headerClass}`}>
<div className="flex items-center gap-2">
<span className={`w-2 h-2 rounded-full ${dotClass}`} />
<span className="text-xs font-bold uppercase tracking-wider">{label}</span>
</div>
<span className="text-xs font-semibold tabular-nums bg-white rounded-full px-2 py-0.5 border border-[#e5e7eb]">
<span className="text-xs font-semibold tabular-nums bg-card rounded-full px-2 py-0.5 border border-border text-muted-foreground">
{leads.length}
</span>
</div>
<div className="flex-1 p-3 space-y-2">
<div className="flex-1 space-y-2">
{leads.map((lead) => (
<DraggableLeadCard key={lead.id} lead={lead} isActive={activeId === lead.id} />
))}
{leads.length === 0 && (
<p className="text-xs text-[#d4d4d8] italic text-center py-10 select-none">
<p className="text-xs text-muted-foreground italic text-center py-10 select-none">
Nessun lead
</p>
)}
@@ -90,16 +90,16 @@ function DraggableLeadCard({ lead, isActive }: { lead: LeadWithTags; isActive: b
style={style}
{...listeners}
{...attributes}
className={`bg-white rounded-lg border border-[#e5e7eb] px-3 py-2.5 cursor-grab active:cursor-grabbing shadow-sm select-none transition-opacity ${
isDragging ? "opacity-30" : "hover:border-[#1A463C]/40 hover:shadow"
className={`bg-card rounded-lg border border-border px-3 py-2.5 cursor-grab active:cursor-grabbing shadow-sm select-none transition-all duration-200 ${
isDragging ? "opacity-30" : "hover:border-primary/30 hover:shadow-card-hover"
}`}
>
<p className="text-sm font-medium text-[#1a1a1a]">{lead.name}</p>
<p className="text-sm font-medium text-foreground">{lead.name}</p>
{lead.company && (
<p className="text-xs text-[#71717a]">{lead.company}</p>
<p className="text-xs text-muted-foreground">{lead.company}</p>
)}
{lead.next_action && (
<p className="text-xs text-[#71717a] mt-1 line-clamp-1">{lead.next_action}</p>
<p className="text-xs text-muted-foreground mt-1 line-clamp-1">{lead.next_action}</p>
)}
</div>
);
@@ -170,10 +170,10 @@ export function LeadsKanbanBoard({ leads }: { leads: LeadWithTags[] }) {
<DragOverlay dropAnimation={null}>
{activeLead && (
<div className="bg-white rounded-lg border-2 border-[#1A463C] px-3 py-2.5 shadow-xl rotate-1 pointer-events-none">
<p className="text-sm font-medium text-[#1a1a1a]">{activeLead.name}</p>
<div className="bg-card rounded-lg border-2 border-primary px-3 py-2.5 shadow-xl rotate-1 pointer-events-none">
<p className="text-sm font-medium text-foreground">{activeLead.name}</p>
{activeLead.company && (
<p className="text-xs text-[#71717a]">{activeLead.company}</p>
<p className="text-xs text-muted-foreground">{activeLead.company}</p>
)}
</div>
)}
+18 -35
View File
@@ -1,8 +1,8 @@
"use client";
import { useState, useMemo } from "react";
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react";
import { SearchInput } from "@/components/ui/SearchInput";
import { SegmentedToggle } from "@/components/ui/SegmentedToggle";
import { LeadTable } from "@/components/admin/leads/LeadTable";
import { LeadsKanbanBoard } from "@/components/admin/leads/LeadsKanbanBoard";
import type { LeadWithTags, LeadFieldOptions } from "@/lib/admin-queries";
@@ -32,39 +32,22 @@ export function LeadsViewToggle({
return (
<div className="space-y-4">
<div className="flex items-center justify-between gap-4">
<div className="relative max-w-sm flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#71717a]" />
<Input
type="text"
placeholder="Cerca lead..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9 h-9"
/>
</div>
<div className="flex items-center gap-1 bg-[#f4f4f5] rounded-lg p-1 w-fit flex-shrink-0">
<button
onClick={() => setView("list")}
className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-all ${
view === "list"
? "bg-white text-[#1A463C] shadow-sm"
: "text-[#71717a] hover:text-[#1a1a1a]"
}`}
>
Lista
</button>
<button
onClick={() => setView("kanban")}
className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-all ${
view === "kanban"
? "bg-white text-[#1A463C] shadow-sm"
: "text-[#71717a] hover:text-[#1a1a1a]"
}`}
>
Kanban
</button>
</div>
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<SearchInput
placeholder="Cerca lead..."
value={query}
onChange={(e) => setQuery(e.target.value)}
containerClassName="w-full md:w-80"
/>
<SegmentedToggle
options={[
{ value: "list", label: "Lista" },
{ value: "kanban", label: "Kanban" },
]}
value={view}
onChange={setView}
className="self-start md:self-auto flex-shrink-0"
/>
</div>
{view === "list" ? (
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createActivitySchema, ACTIVITY_TYPES } from "@/lib/lead-validators";
import { logActivity } from "@/app/admin/leads/actions";
import { logActivity } from "@/app/admin/pipeline/actions";
import {
Dialog,
DialogContent,
@@ -4,7 +4,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { assignQuoteToLead } from "@/app/admin/leads/actions";
import { assignQuoteToLead } from "@/app/admin/pipeline/actions";
import {
Dialog,
DialogContent,
@@ -4,7 +4,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { addTranscript } from "@/app/admin/leads/actions";
import { addTranscript } from "@/app/admin/pipeline/actions";
import {
Dialog,
DialogContent,