"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,
} from "poyraz-ui/molecules";
import {
Archive,
ExternalLink,
Mail,
PauseCircle,
Pencil,
Phone,
Plus,
UserCheck,
Users,
Wallet,
type LucideIcon,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
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;
};
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",
};
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 (
Freelancer operasyonu
Müşteriler
Çalıştığın müşterileri, iletişim bilgilerini ve temel iş durumunu tek
ekrandan yönet.
{filteredClients.length > 0 ? (
Müşteri
İletişim
Durum
Projeler
İşlem
{filteredClients.map((client) => (
))}
) : (
)}
);
}
function ClientRow({ client }: { client: ClientListItem }) {
return (
{getInitials(client.name)}
{client.name}
{client.company_name || "Firma bilgisi yok"}
{client.email ? (
{client.email}
) : null}
{client.phone ? (
{client.phone}
) : null}
{client.website ? (
{client.website.replace(/^https?:\/\//, "")}
) : null}
{!client.email && !client.phone && !client.website ? (
İletişim bilgisi yok
) : null}
{statusLabels[client.status]}
{client.projectCount}
{formatCurrency(client.revenueTotal)}
{client.status !== "archived" ? (
) : null}
);
}
function ClientDialog({
mode,
client,
}: {
mode: "create" | "edit";
client?: ClientListItem;
}) {
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 WebsiteInput({
id,
name,
defaultValue,
}: {
id: string;
name: string;
defaultValue: string;
}) {
const [value, setValue] = useState(defaultValue);
return (
setValue(event.target.value.replace(/\s/g, ""))}
onBlur={() => setValue(normalizeWebsite(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"}
{hasQuery
? "Arama metnini sadeleştirerek tekrar deneyebilirsin."
: "İlk müşterini ekleyerek proje, görev ve finans kayıtlarını müşteriyle ilişkilendirmeye başlayabilirsin."}
);
}
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 normalizeWebsite(input: string) {
const value = input.trim().replace(/\s/g, "");
if (!value) {
return "";
}
if (/^https?:\/\//i.test(value)) {
return value;
}
return `https://${value}`;
}
function getWebsiteHref(input: string) {
return /^https?:\/\//i.test(input) ? input : `https://${input}`;
}
function formatCurrency(value: number) {
return new Intl.NumberFormat("tr-TR", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
}).format(value);
}