"use client";
import {
archiveClientRecord,
createClientRecord,
updateClientRecord,
} from "@/app/(dashboard)/clients/actions";
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";
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 filteredClients = normalizedQuery
? clients.filter((client) =>
[
client.name,
client.company_name,
client.email,
client.phone,
client.website,
client.notes,
]
.filter(Boolean)
.some((value) => value!.toLowerCase().includes(normalizedQuery)),
)
: clients;
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 (
{stage.label}
{stageClients.length}
{stageClients.map(client => (
{client.company_name && {client.company_name}
}
{client.next_follow_up_date && (
{format(new Date(client.next_follow_up_date), 'd MMM yyyy', { locale: tr })}
)}
))}
{stageClients.length === 0 && (
Boş
)}
);
})}
{filteredClients.length > 0 ? (
Müşteri
İletişim
Aşama
Follow-up
Projeler
İşlem
{filteredClients.map((client) => (
))}
) : (
)}
);
}
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)}
);
}
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 (
);
}
function ClientFormFields({ client }: { client?: ClientListItem }) {
return (
);
}
function PhoneInput({ id, name, defaultValue }: { id: string; name: string; defaultValue: string; }) {
const [value, setValue] = useState(defaultValue);
return (
setValue(formatPhone(event.target.value))}
/>
);
}
function StatCard({ label, value, description, icon: Icon, iconClassName }: { label: string; value: string; description?: string; icon: LucideIcon; iconClassName: string; }) {
return (
{label}
{value}
{description ?
{description}
: null}
);
}
function EmptyState({ hasQuery }: { hasQuery: boolean }) {
return (
{hasQuery ? "Aramana uygun müşteri yok" : "Henüz müşteri eklenmedi"}
İlk müşterini ekleyerek potansiyel satışlarını takip etmeye başla.
);
}
function getInitials(name: string) {
return name.split(" ").filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
}
function formatPhone(input: string) {
const digits = input.replace(/\D/g, "");
if (!digits) return "";
const local = digits.startsWith("90") ? digits.slice(2, 12) : digits.startsWith("0") ? digits.slice(1, 11) : digits.slice(0, 10);
const area = local.slice(0, 3);
const first = local.slice(3, 6);
const second = local.slice(6, 8);
const third = local.slice(8, 10);
let formatted = "+90";
if (area) formatted += ` (${area}`;
if (area.length === 3) formatted += ")";
if (first) formatted += ` ${first}`;
if (second) formatted += ` ${second}`;
if (third) formatted += ` ${third}`;
return formatted;
}
function formatCurrency(value: number) {
return new Intl.NumberFormat("tr-TR", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(value);
}