feat: desktop image thumbnails, gallery lightbox portal, client-side compression, clickable table rows, fix header gap
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { MoreHorizontal, Plus, Pencil, Trash2, CheckCircle } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { DotsThree, Plus, PencilSimple, Trash, CheckCircle, List, CalendarDots } from '@/lib/icons';
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -17,9 +18,13 @@ import {
|
||||
deleteActivityAction,
|
||||
} from "@/lib/appwrite/activity-actions";
|
||||
import { ActivityFormSheet } from "./activity-form-sheet";
|
||||
import { ActivityCalendar } from "./activity-calendar";
|
||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||
import type { Activity, Customer, Property } from "@/lib/appwrite/schema";
|
||||
import { ACTIVITY_TYPE_LABELS } from "@/lib/appwrite/schema";
|
||||
|
||||
type ViewMode = "list" | "calendar";
|
||||
|
||||
interface ActivitiesClientProps {
|
||||
initialActivities: Activity[];
|
||||
customers: Customer[];
|
||||
@@ -31,9 +36,12 @@ export function ActivitiesClient({
|
||||
customers,
|
||||
properties,
|
||||
}: ActivitiesClientProps) {
|
||||
const router = useRouter();
|
||||
const [activities, setActivities] = useState(initialActivities);
|
||||
const [sheetOpen, setSheetOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<Activity | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<Activity | null>(null);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
||||
|
||||
function customerName(id?: string | null) {
|
||||
if (!id) return "—";
|
||||
@@ -45,15 +53,19 @@ export function ActivitiesClient({
|
||||
return properties.find((p) => p.$id === id)?.title ?? "—";
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
setEditing(null);
|
||||
setSheetOpen(true);
|
||||
}
|
||||
useEffect(() => {
|
||||
const open = () => { setEditing(null); setSheetOpen(true); };
|
||||
const close = () => setSheetOpen(false);
|
||||
window.addEventListener("kovak:open-form-activities", open);
|
||||
window.addEventListener("kovak:close-form-activities", close);
|
||||
return () => {
|
||||
window.removeEventListener("kovak:open-form-activities", open);
|
||||
window.removeEventListener("kovak:close-form-activities", close);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function openEdit(a: Activity) {
|
||||
setEditing(a);
|
||||
setSheetOpen(true);
|
||||
}
|
||||
function openCreate() { setEditing(null); setSheetOpen(true); }
|
||||
function openEdit(a: Activity) { setEditing(a); setSheetOpen(true); }
|
||||
|
||||
async function handleComplete(a: Activity) {
|
||||
const result = await completeActivityAction(a.$id);
|
||||
@@ -67,11 +79,12 @@ export function ActivitiesClient({
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(a: Activity) {
|
||||
if (!confirm("Bu aktivite silinsin mi?")) return;
|
||||
const result = await deleteActivityAction(a.$id);
|
||||
async function doDelete() {
|
||||
if (!deleteTarget) return;
|
||||
const result = await deleteActivityAction(deleteTarget.$id);
|
||||
if (result.ok) {
|
||||
setActivities((prev) => prev.filter((x) => x.$id !== a.$id));
|
||||
setActivities((prev) => prev.filter((x) => x.$id !== deleteTarget.$id));
|
||||
setDeleteTarget(null);
|
||||
toast.success("Aktivite silindi.");
|
||||
} else {
|
||||
toast.error(result.error ?? "Silinemedi.");
|
||||
@@ -80,15 +93,57 @@ export function ActivitiesClient({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
<h1 className="text-2xl font-bold">Aktiviteler</h1>
|
||||
<Button onClick={openCreate} size="sm">
|
||||
<Plus className="mr-1.5 size-4" />
|
||||
Yeni Aktivite
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* View toggle */}
|
||||
<div className="flex rounded-md border overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
viewMode === "list"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-background text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<List className="size-3.5" />
|
||||
Liste
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode("calendar")}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
viewMode === "calendar"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-background text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<CalendarDots className="size-3.5" />
|
||||
Takvim
|
||||
</button>
|
||||
</div>
|
||||
<Button onClick={openCreate} size="sm" data-tour="activities-add">
|
||||
<Plus className="mr-1.5 size-4" />
|
||||
Yeni Aktivite
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border">
|
||||
{/* Calendar view */}
|
||||
{viewMode === "calendar" && (
|
||||
<ActivityCalendar
|
||||
activities={activities}
|
||||
customers={customers}
|
||||
properties={properties}
|
||||
onEdit={openEdit}
|
||||
onComplete={handleComplete}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* List view */}
|
||||
{viewMode === "list" && (
|
||||
<div data-tour="activities-table" className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -133,7 +188,7 @@ export function ActivitiesClient({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-8">
|
||||
<MoreHorizontal className="size-4" />
|
||||
<DotsThree className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
@@ -144,14 +199,14 @@ export function ActivitiesClient({
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => openEdit(a)}>
|
||||
<Pencil className="mr-2 size-4" />
|
||||
<PencilSimple className="mr-2 size-4" />
|
||||
Düzenle
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDelete(a)}
|
||||
onClick={() => setDeleteTarget(a)}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<Trash2 className="mr-2 size-4" />
|
||||
<Trash className="mr-2 size-4" />
|
||||
Sil
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -162,6 +217,7 @@ export function ActivitiesClient({
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActivityFormSheet
|
||||
open={sheetOpen}
|
||||
@@ -169,6 +225,14 @@ export function ActivitiesClient({
|
||||
activity={editing}
|
||||
customers={customers}
|
||||
properties={properties}
|
||||
onSuccess={() => router.refresh()}
|
||||
/>
|
||||
<DeleteConfirmDialog
|
||||
open={!!deleteTarget}
|
||||
onOpenChange={(v) => { if (!v) setDeleteTarget(null); }}
|
||||
title="Bu aktivite silinsin mi?"
|
||||
description="Bu aktivite kalıcı olarak silinecek ve geri alınamaz."
|
||||
onConfirm={doDelete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user