"use client"; import { getDocumentIntlLocale } from "@/lib/i18n/browser"; import { useTranslations } from "@/components/i18n/i18n-provider"; import { 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, toast, } from "poyraz-ui/molecules"; import { Mail, Pencil, Phone, Plus, UserCheck, Users, Wallet, Clock, ArrowRight, } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { format, isPast, isToday } from "date-fns"; import { getDocumentDateFnsLocale } from "@/lib/i18n/date-fns"; import { cn } from "@/lib/utils"; import { StatCard } from "@/components/system/stat-card"; 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; translations?: Record>; }; type ClientPipelineStage = ClientListItem["pipeline_stage"]; const pipelineStages: Array<{ id: ClientPipelineStage; label: string; color: string; }> = [ { 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; activeLocales: { code: string; name: string }[]; }; export function ClientsClient({ clients, totalRevenue, activeCount, activeLocales, }: ClientsClientProps) { const t = useTranslations(); const [query, setQuery] = useState(""); const normalizedQuery = query.trim().toLowerCase(); const [draggedClientId, setDraggedClientId] = useState(null); const [pipelineOverrides, setPipelineOverrides] = useState< Partial> >({}); const localClients = clients.map((client) => ({ ...client, pipeline_stage: pipelineOverrides[client.id] ?? client.pipeline_stage, })); function handleDragStart(event: React.DragEvent, clientId: string) { setDraggedClientId(clientId); event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", clientId); } async function handleDrop(newStage: ClientPipelineStage) { if (!draggedClientId) return; const clientId = draggedClientId; setDraggedClientId(null); const client = localClients.find(c => c.id === clientId); if (!client || client.pipeline_stage === newStage) return; const previousStage = client.pipeline_stage; setPipelineOverrides((current) => ({ ...current, [clientId]: newStage })); try { await updateClientPipelineStage(clientId, newStage); toast.success(t("clients.messages.stageUpdated")); } catch (error) { setPipelineOverrides((current) => ({ ...current, [clientId]: previousStage, })); toast.error( error instanceof Error ? error.message : t("clients.errors.stageUpdateFailed"), ); } } 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 (

{t("clients.title")}

c.pipeline_stage === 'lead' || c.pipeline_stage === 'contacted').length.toString()} icon={Users} tone="blue" /> 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} tone="rose" />
{t("clients.tabs.pipeline")} {t("clients.tabs.list")} setQuery(event.target.value)} placeholder={t("clients.search")} 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 && (
{t("clients.empty.pipeline")}
)}
); })}
{filteredClients.length > 0 ? (
{t("clients.list.client")} {t("clients.list.contact")} {t("clients.list.stage")} {t("clients.list.followUp")} Finans İşlem
{filteredClients.map(client => ( ))}
) : ( 0} /> )}
); } 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: getDocumentDateFnsLocale() })}
)}
); } function ClientRow({ client, activeLocales }: { client: ClientListItem, activeLocales: { code: string; name: string }[] }) { const t = useTranslations(); 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 || t("clients.list.noCompany")}
{client.email ? ( {client.email} ) : null} {client.phone ? ( {client.phone} ) : null} {!client.email && !client.phone && !client.website ? ( {t("clients.list.noContact")} ) : null}
{t(`clients.pipeline.${stage.id}`)}
{client.next_follow_up_date ? (
{format(new Date(client.next_follow_up_date), 'd MMM yyyy', { locale: getDocumentDateFnsLocale() })}
) : ( - )}
{client.projectCount} {t("clients.list.projects")}
{formatCurrency(client.revenueTotal)}
{t("clients.actions.edit")}} />
); } function ClientDialog({ mode, client, trigger, activeLocales }: { mode: "create" | "edit"; client?: ClientListItem; trigger?: React.ReactNode; activeLocales: { code: string; name: string }[]; }) { const t = useTranslations(); 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); toast.success(mode === "create" ? t("clients.messages.created") : t("clients.messages.updated")); } catch (error) { toast.error( error instanceof Error ? error.message : t("clients.errors.saveFailed"), ); } finally { setIsSubmitting(false); } } return ( {trigger || ( )}
{client ? : null} {mode === "create" ? t("clients.form.createTitle") : t("clients.form.editTitle")} {t("clients.description")}
); } function ClientFormFields({ client, activeLocales }: { client?: ClientListItem, activeLocales: { code: string; name: string }[] }) { const t = useTranslations(); return (
{activeLocales.map((locale) => ( {locale.name} ))}
{activeLocales.map((locale) => (