feat: desktop image thumbnails, gallery lightbox portal, client-side compression, clickable table rows, fix header gap

This commit is contained in:
egecankomur
2026-05-12 04:49:36 +03:00
parent 3cce632eb3
commit 3554b39800
134 changed files with 7736 additions and 1913 deletions
+24 -18
View File
@@ -1,7 +1,8 @@
"use client";
import { useState } from "react";
import { MoreHorizontal, Plus, Pencil, Trash2, ToggleLeft } from "lucide-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";
@@ -17,6 +18,7 @@ import {
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 {
@@ -25,23 +27,18 @@ interface SearchesClientProps {
}
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);
}
function openCreate() { setEditing(null); setSheetOpen(true); }
function openEdit(s: CustomerSearch) { setEditing(s); setSheetOpen(true); }
async function handleToggle(s: CustomerSearch) {
const next = !s.isActive;
@@ -53,11 +50,12 @@ export function SearchesClient({ initialSearches, customers }: SearchesClientPro
}
}
async function handleDelete(s: CustomerSearch) {
if (!confirm("Bu arama kriteri silinsin mi?")) return;
const result = await deleteCustomerSearchAction(s.$id);
async function doDelete() {
if (!deleteTarget) return;
const result = await deleteCustomerSearchAction(deleteTarget.$id);
if (result.ok) {
setSearches((prev) => prev.filter((x) => x.$id !== s.$id));
setSearches((prev) => prev.filter((x) => x.$id !== deleteTarget.$id));
setDeleteTarget(null);
toast.success("Arama kriteri silindi.");
} else {
toast.error(result.error ?? "Silinemedi.");
@@ -132,12 +130,12 @@ export function SearchesClient({ initialSearches, customers }: SearchesClientPro
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-8">
<MoreHorizontal className="size-4" />
<DotsThree className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openEdit(s)}>
<Pencil className="mr-2 size-4" />
<PencilSimple className="mr-2 size-4" />
Düzenle
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleToggle(s)}>
@@ -145,10 +143,10 @@ export function SearchesClient({ initialSearches, customers }: SearchesClientPro
{s.isActive ? "Pasif yap" : "Aktif yap"}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDelete(s)}
onClick={() => setDeleteTarget(s)}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 size-4" />
<Trash className="mr-2 size-4" />
Sil
</DropdownMenuItem>
</DropdownMenuContent>
@@ -165,6 +163,14 @@ export function SearchesClient({ initialSearches, customers }: SearchesClientPro
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>
);