ultisuite-client/components/gmail/contacts-page/contacts-sidebar.tsx
R3D347HR4Y 77f99d8d8a hehe
2026-05-19 00:48:20 +02:00

234 lines
7.3 KiB
TypeScript

"use client"
import { useState } from "react"
import {
Users,
Clock,
UserPlus,
Merge,
Upload,
Trash2,
Plus,
Tag,
Menu,
ChevronDown,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useContactsStore } from "@/lib/contacts/contacts-store"
import { useNavStore } from "@/lib/stores/nav-store"
import type { ContactsPageView } from "./contacts-app-shell"
interface ContactsSidebarProps {
currentView: ContactsPageView
activeLabelId?: string | null
onNavigate: (view: ContactsPageView) => void
onCreateContact: () => void
onBulkCreate?: () => void
onSelectLabel?: (id: string) => void
}
export function ContactsSidebar({
currentView,
activeLabelId,
onNavigate,
onCreateContact,
onBulkCreate,
onSelectLabel,
}: ContactsSidebarProps) {
const contacts = useContactsStore((s) => s.contacts)
const mergeSuggestionCount = useContactsStore((s) => s.getMergeSuggestions().length)
const labelRows = useNavStore((s) => s.labelRows)
const addLabelRowFromSidebar = useNavStore((s) => s.addLabelRowFromSidebar)
const [labelInput, setLabelInput] = useState("")
const [showLabelInput, setShowLabelInput] = useState(false)
const availableLabels = labelRows.filter((r) => r.enabled !== false)
function handleAddLabel() {
const trimmed = labelInput.trim()
if (trimmed) {
addLabelRowFromSidebar(trimmed)
setLabelInput("")
setShowLabelInput(false)
}
}
return (
<aside className="flex h-full w-60 shrink-0 flex-col border-r border-gray-200 bg-white">
{/* Logo + hamburger */}
<div className="flex h-16 items-center gap-2 px-4">
<Button variant="ghost" size="icon" className="h-10 w-10 rounded-full text-gray-600">
<Menu className="h-5 w-5" />
</Button>
<div className="flex items-center gap-2">
<Users className="h-6 w-6 text-[#5f6368]" />
<span className="text-[22px] font-normal text-[#5f6368]">Contacts</span>
</div>
</div>
{/* Create button */}
<div className="px-3 pb-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="flex h-14 w-full items-center gap-3 rounded-2xl bg-white px-4 shadow-md ring-1 ring-gray-200 transition-shadow hover:shadow-lg"
>
<Plus className="h-5 w-5 text-[#1a73e8]" />
<span className="flex-1 text-left text-sm font-medium text-[#3c4043]">Créer un contact</span>
<ChevronDown className="h-4 w-4 text-[#5f6368]" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuItem onClick={onCreateContact}>
<UserPlus className="mr-2 h-4 w-4" />
Créer un contact
</DropdownMenuItem>
<DropdownMenuItem onClick={onBulkCreate}>
<Users className="mr-2 h-4 w-4" />
Créer plusieurs contacts
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Nav items */}
<nav className="flex-1 overflow-y-auto px-2">
<NavItem
icon={<Users className="h-5 w-5" />}
label="Contacts"
count={contacts.length}
active={currentView === "contacts"}
onClick={() => onNavigate("contacts")}
/>
<NavItem
icon={<Clock className="h-5 w-5" />}
label="Fréquents"
active={currentView === "frequent"}
onClick={() => onNavigate("frequent")}
/>
<NavItem
icon={<UserPlus className="h-5 w-5" />}
label="Autres contacts"
active={currentView === "other"}
onClick={() => onNavigate("other")}
/>
<div className="my-2 border-t border-gray-200" />
<p className="px-3 py-2 text-xs font-medium text-[#5f6368]">Corriger et gérer</p>
<NavItem
icon={<Merge className="h-5 w-5" />}
label="Fusionner et corriger"
badge={mergeSuggestionCount > 0 ? mergeSuggestionCount : undefined}
active={currentView === "merge"}
onClick={() => onNavigate("merge")}
/>
<NavItem
icon={<Upload className="h-5 w-5" />}
label="Importer"
active={currentView === "import"}
onClick={() => onNavigate("import")}
/>
<NavItem
icon={<Trash2 className="h-5 w-5" />}
label="Corbeille"
active={currentView === "trash"}
onClick={() => onNavigate("trash")}
/>
<div className="my-2 border-t border-gray-200" />
{/* Labels section */}
<div className="flex items-center justify-between px-3 py-2">
<p className="text-xs font-medium text-[#5f6368]">Libellés</p>
<button
type="button"
onClick={() => setShowLabelInput(true)}
className="rounded-full p-1 text-[#5f6368] hover:bg-gray-100"
>
<Plus className="h-4 w-4" />
</button>
</div>
{showLabelInput && (
<div className="flex items-center gap-1 px-3 pb-2">
<input
type="text"
value={labelInput}
onChange={(e) => setLabelInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleAddLabel()}
placeholder="Nom du libellé"
className="flex-1 rounded border border-gray-300 px-2 py-1 text-sm outline-none focus:border-blue-500"
autoFocus
/>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleAddLabel}>
<Plus className="h-4 w-4" />
</Button>
</div>
)}
{availableLabels.map((label) => {
const count = contacts.filter((c) => c.labels?.includes(label.id)).length
return (
<NavItem
key={label.id}
icon={<Tag className="h-5 w-5" />}
label={label.label}
count={count}
active={currentView === "label" && activeLabelId === label.id}
onClick={() => onSelectLabel?.(label.id)}
/>
)
})}
</nav>
</aside>
)
}
function NavItem({
icon,
label,
count,
badge,
active,
onClick,
}: {
icon: React.ReactNode
label: string
count?: number
badge?: number
active: boolean
onClick: () => void
}) {
return (
<button
type="button"
onClick={onClick}
className={`flex w-full items-center gap-3 rounded-full px-3 py-2 text-sm transition-colors ${
active
? "bg-[#c2e7ff] font-medium text-[#001d35]"
: "text-[#1f1f1f] hover:bg-gray-100"
}`}
>
<span className={active ? "text-[#001d35]" : "text-[#444746]"}>{icon}</span>
<span className="flex-1 truncate text-left">{label}</span>
{badge !== undefined && (
<span className="flex h-5 min-w-5 items-center justify-center rounded-full bg-[#ea4335] px-1.5 text-[11px] font-medium text-white">
{badge}
</span>
)}
{count !== undefined && (
<span className="text-xs text-[#5f6368]">{count}</span>
)}
</button>
)
}