"use client"; import { archiveClientRecord, createClientRecord, updateClientRecord, updateClientPipelineStage, } from "@/app/(dashboard)/clients/actions"; import { PendingLink } from "@/components/ui/pending-link"; import { Badge, Button, Card, CardContent, Input, Label, Textarea } from "poyraz-ui/atoms"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsContent, TabsList, TabsTrigger, } from "poyraz-ui/molecules"; import { Archive, ExternalLink, Mail, PauseCircle, Pencil, Phone, Plus, UserCheck, Users, Wallet, Clock, ArrowRight, type LucideIcon, } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { format, isPast, isToday } from "date-fns"; import { tr } from "date-fns/locale"; import { useEffect } from "react"; import { cn } from "@/lib/utils"; export type ClientListItem = { id: string; name: string; company_name: string | null; email: string | null; phone: string | null; website: string | null; status: "active" | "paused" | "archived"; notes: string | null; created_at: string; projectCount: number; revenueTotal: number; // CRM fields pipeline_stage: "lead" | "contacted" | "proposal_sent" | "won" | "lost"; next_follow_up_date: string | null; last_contact_date: string | null; client_value_score: number; }; const statusLabels = { active: "Aktif", paused: "Duraklatıldı", archived: "Arşivlendi", }; const statusClasses = { active: "border-emerald-200 bg-emerald-50 text-emerald-700", paused: "border-amber-200 bg-amber-50 text-amber-700", archived: "border-zinc-200 bg-zinc-50 text-zinc-600", }; const pipelineStages = [ { id: "lead", label: "Potansiyel (Lead)", color: "border-slate-200 bg-slate-50 text-slate-700" }, { id: "contacted", label: "İletişime Geçildi", color: "border-blue-200 bg-blue-50 text-blue-700" }, { id: "proposal_sent", label: "Teklif İletildi", color: "border-amber-200 bg-amber-50 text-amber-700" }, { id: "won", label: "Kazanıldı (Won)", color: "border-emerald-200 bg-emerald-50 text-emerald-700" }, { id: "lost", label: "Kaybedildi (Lost)", color: "border-rose-200 bg-rose-50 text-rose-700" }, ]; type ClientsClientProps = { clients: ClientListItem[]; totalRevenue: number; activeCount: number; pausedCount: number; archivedCount: number; }; export function ClientsClient({ clients, totalRevenue, activeCount, pausedCount, archivedCount, }: ClientsClientProps) { const [query, setQuery] = useState(""); const normalizedQuery = query.trim().toLowerCase(); const [draggedClientId, setDraggedClientId] = useState(null); const [localClients, setLocalClients] = useState(clients); useEffect(() => { setLocalClients(clients); }, [clients]); function handleDragStart(event: React.DragEvent, clientId: string) { setDraggedClientId(clientId); event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", clientId); } async function handleDrop(newStage: string) { if (!draggedClientId) return; const clientId = draggedClientId; setDraggedClientId(null); const client = localClients.find(c => c.id === clientId); if (!client || client.pipeline_stage === newStage) return; setLocalClients(prev => prev.map(c => c.id === clientId ? { ...c, pipeline_stage: newStage as any } : c) ); try { await updateClientPipelineStage(clientId, newStage as any); } catch (error) { setLocalClients(clients); } } const filteredClients = normalizedQuery ? localClients.filter((client) => [ client.name, client.company_name, client.email, client.phone, client.website, client.notes, ] .filter(Boolean) .some((value) => value!.toLowerCase().includes(normalizedQuery)), ) : localClients; return (
CRM & Operasyon

CRM & Müşteriler

Potansiyel müşterilerini pipeline üzerinden takip et ve müşteri ilişkilerini yönet.

c.pipeline_stage === 'lead' || c.pipeline_stage === 'contacted').length.toString()} icon={Users} iconClassName="bg-blue-50 text-blue-700" /> c.next_follow_up_date && (isPast(new Date(c.next_follow_up_date)) || isToday(new Date(c.next_follow_up_date)))).length.toString()} icon={Clock} iconClassName="bg-rose-50 text-rose-700" />
Pipeline (Kanban) Müşteri Listesi setQuery(event.target.value)} placeholder="Müşteri, firma, e-posta veya not ara" className="md:max-w-sm" />
{pipelineStages.map(stage => { const stageClients = filteredClients.filter(c => c.pipeline_stage === stage.id && c.status !== 'archived'); return ( handleDrop(stage.id)} > {stageClients.map(client => ( setDraggedClientId(null)} /> ))} {stageClients.length === 0 && (
Boş
)}
); })}
{filteredClients.length > 0 ? (
Müşteri İletişim Aşama Follow-up Projeler İşlem
{filteredClients.map((client) => ( ))}
) : ( )}
); } function DroppableColumn({ title, count, color, onDrop, children }: { title: string, count: number, color: string, onDrop: () => void, children: React.ReactNode }) { return (
{ event.preventDefault(); event.dataTransfer.dropEffect = "move"; }} onDrop={onDrop} >

{title}

{count}
{children}
); } function DraggableClientCard({ client, draggedClientId, onDragStart, onDragEnd }: { client: ClientListItem, draggedClientId: string | null, onDragStart: (e: React.DragEvent, id: string) => void, onDragEnd: () => void }) { const isDragging = draggedClientId === client.id; return (
onDragStart(e, client.id)} onDragEnd={onDragEnd} className={cn("touch-none", isDragging ? "cursor-grabbing opacity-50 ring-2 ring-primary/20 transition" : "cursor-grab transition active:cursor-grabbing")} >
e.stopPropagation()} showSpinner> {client.name}
e.stopPropagation()}> } />
{client.company_name &&

{client.company_name}

} {client.next_follow_up_date && (
{format(new Date(client.next_follow_up_date), 'd MMM yyyy', { locale: tr })}
)}
); } function ClientRow({ client }: { client: ClientListItem }) { const isFollowUpOverdue = client.next_follow_up_date && (isPast(new Date(client.next_follow_up_date)) || isToday(new Date(client.next_follow_up_date))); const stage = pipelineStages.find(s => s.id === client.pipeline_stage) || pipelineStages[0]; return (
{getInitials(client.name)}
{client.name}
{client.company_name || "Firma bilgisi yok"}
{client.email ? ( {client.email} ) : null} {client.phone ? ( {client.phone} ) : null} {!client.email && !client.phone && !client.website ? ( İletişim bilgisi yok ) : null}
{stage.label}
{client.next_follow_up_date ? (
{format(new Date(client.next_follow_up_date), 'd MMM yyyy', { locale: tr })}
) : ( - )}
{client.projectCount} Proje
{formatCurrency(client.revenueTotal)}
Düzenle} />
); } function ClientDialog({ mode, client, trigger }: { mode: "create" | "edit"; client?: ClientListItem; trigger?: React.ReactNode; }) { const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const action = mode === "create" ? createClientRecord : updateClientRecord; async function handleSubmit(formData: FormData) { setIsSubmitting(true); try { await action(formData); setOpen(false); } finally { setIsSubmitting(false); } } return ( {trigger || ( )}
{client ? : null} {mode === "create" ? "Yeni müşteri" : "Müşteriyi düzenle"} Müşterinin iletişim ve CRM detaylarını girin.
); } function ClientFormFields({ client }: { client?: ClientListItem }) { return (