Files
kovakemlak-crm/src/components/customers/searches-client.tsx
T

178 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { DotsThree, Plus, PencilSimple, Trash, ToggleLeft } from '@/lib/icons';
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
} from "@/components/ui/table";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
deleteCustomerSearchAction,
toggleCustomerSearchActiveAction,
} from "@/lib/appwrite/customer-search-actions";
import { SearchFormSheet } from "./search-form-sheet";
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
import type { Customer, CustomerSearch } from "@/lib/appwrite/schema";
interface SearchesClientProps {
initialSearches: CustomerSearch[];
customers: Customer[];
}
export function SearchesClient({ initialSearches, customers }: SearchesClientProps) {
const router = useRouter();
const [searches, setSearches] = useState(initialSearches);
const [sheetOpen, setSheetOpen] = useState(false);
const [editing, setEditing] = useState<CustomerSearch | null>(null);
const [deleteTarget, setDeleteTarget] = useState<CustomerSearch | null>(null);
function customerName(id: string) {
return customers.find((c) => c.$id === id)?.name ?? id;
}
function openCreate() { setEditing(null); setSheetOpen(true); }
function openEdit(s: CustomerSearch) { setEditing(s); setSheetOpen(true); }
async function handleToggle(s: CustomerSearch) {
const next = !s.isActive;
const result = await toggleCustomerSearchActiveAction(s.$id, next);
if (result.ok) {
setSearches((prev) => prev.map((x) => x.$id === s.$id ? { ...x, isActive: next } : x));
} else {
toast.error(result.error ?? "Durum güncellenemedi.");
}
}
async function doDelete() {
if (!deleteTarget) return;
const result = await deleteCustomerSearchAction(deleteTarget.$id);
if (result.ok) {
setSearches((prev) => prev.filter((x) => x.$id !== deleteTarget.$id));
setDeleteTarget(null);
toast.success("Arama kriteri silindi.");
} else {
toast.error(result.error ?? "Silinemedi.");
}
}
function formatRange(min?: number | null, max?: number | null, unit = "") {
if (min && max) return `${min.toLocaleString("tr-TR")}${max.toLocaleString("tr-TR")} ${unit}`.trim();
if (min) return `${min.toLocaleString("tr-TR")} ${unit}`.trim();
if (max) return `${max.toLocaleString("tr-TR")} ${unit}`.trim();
return "—";
}
function parseJsonList(json?: string | null): string {
if (!json) return "—";
try {
const arr = JSON.parse(json) as string[];
return arr.length ? arr.join(", ") : "—";
} catch {
return json;
}
}
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Arama Kriterleri</h1>
<Button onClick={openCreate} size="sm">
<Plus className="mr-1.5 size-4" />
Yeni Kriter
</Button>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Müşteri</TableHead>
<TableHead>Tür</TableHead>
<TableHead>Oda</TableHead>
<TableHead>Fiyat Aralığı</TableHead>
<TableHead>Şehir / İlçe</TableHead>
<TableHead>Durum</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{searches.length === 0 && (
<TableRow>
<TableCell colSpan={7} className="text-muted-foreground text-center py-10">
Henüz arama kriteri yok.
</TableCell>
</TableRow>
)}
{searches.map((s) => (
<TableRow key={s.$id}>
<TableCell className="font-medium">{customerName(s.customerId)}</TableCell>
<TableCell>{s.listingType ?? "Tümü"}</TableCell>
<TableCell>{parseJsonList(s.roomCounts)}</TableCell>
<TableCell className="tabular-nums">{formatRange(s.minPrice, s.maxPrice, "₺")}</TableCell>
<TableCell>
{[parseJsonList(s.cities), parseJsonList(s.districts)]
.filter((v) => v !== "—")
.join(" / ") || "—"}
</TableCell>
<TableCell>
<Badge variant={s.isActive ? "default" : "secondary"}>
{s.isActive ? "Aktif" : "Pasif"}
</Badge>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-8">
<DotsThree className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openEdit(s)}>
<PencilSimple className="mr-2 size-4" />
Düzenle
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleToggle(s)}>
<ToggleLeft className="mr-2 size-4" />
{s.isActive ? "Pasif yap" : "Aktif yap"}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setDeleteTarget(s)}
className="text-destructive focus:text-destructive"
>
<Trash className="mr-2 size-4" />
Sil
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<SearchFormSheet
open={sheetOpen}
onOpenChange={setSheetOpen}
search={editing}
customers={customers}
onSuccess={() => router.refresh()}
/>
<DeleteConfirmDialog
open={!!deleteTarget}
onOpenChange={(v) => { if (!v) setDeleteTarget(null); }}
title="Bu arama kriteri silinsin mi?"
description="Arama kriteri ve eşleşme bildirimleri kalıcı olarak silinecek."
onConfirm={doDelete}
/>
</div>
);
}