"use client"; import { useState, useCallback, useRef } from "react"; import { useMutation } from "convex/react"; import { ArrowUpRight, MoreHorizontal, Pencil, Trash2 } from "lucide-react"; import { toast } from "@/lib/toast"; import { msg } from "@/lib/toast-messages"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import { cn } from "@/lib/utils"; interface CanvasCardProps { canvas: { _id: Id<"canvases">; name: string }; onNavigate: (id: Id<"canvases">) => void; } export default function CanvasCard({ canvas, onNavigate }: CanvasCardProps) { const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(canvas.name); const [isSaving, setIsSaving] = useState(false); const inputRef = useRef(null); const suppressCardNavigationRef = useRef(false); const saveInFlightRef = useRef(false); const updateCanvas = useMutation(api.canvases.update); const removeCanvas = useMutation(api.canvases.remove); const [deleteOpen, setDeleteOpen] = useState(false); const [deleteBusy, setDeleteBusy] = useState(false); const handleStartEdit = useCallback(() => { suppressCardNavigationRef.current = true; setEditName(canvas.name); setIsEditing(true); setTimeout(() => { inputRef.current?.select(); setTimeout(() => { suppressCardNavigationRef.current = false; }, 0); }, 0); }, [canvas.name]); const handleSave = useCallback(async () => { const trimmedName = editName.trim(); if (!trimmedName) { const { title, desc } = msg.dashboard.renameEmpty; toast.error(title, desc); return; } if (trimmedName === canvas.name) { setIsEditing(false); return; } if (saveInFlightRef.current) return; saveInFlightRef.current = true; setIsSaving(true); try { await updateCanvas({ canvasId: canvas._id, name: trimmedName }); toast.success(msg.dashboard.renameSuccess.title); setIsEditing(false); } catch { toast.error(msg.dashboard.renameFailed.title); } finally { setIsSaving(false); saveInFlightRef.current = false; } }, [editName, canvas.name, canvas._id, updateCanvas]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleSave(); } else if (e.key === "Escape") { e.preventDefault(); setIsEditing(false); setEditName(canvas.name); } }, [handleSave, canvas.name] ); // Prevent duplicate toast: only save on blur if still in editing mode const handleBlur = useCallback(() => { if (!isEditing) return; handleSave(); }, [isEditing, handleSave]); const handleCardClick = useCallback(() => { if (suppressCardNavigationRef.current) return; if (!isEditing) { onNavigate(canvas._id); } }, [isEditing, onNavigate, canvas._id]); const handleDelete = useCallback(async () => { setDeleteBusy(true); try { await removeCanvas({ canvasId: canvas._id }); toast.success(msg.dashboard.deleteSuccess.title); setDeleteOpen(false); } catch { toast.error(msg.dashboard.deleteFailed.title); } finally { setDeleteBusy(false); } }, [canvas._id, removeCanvas]); return ( <>
{/* Avatar */}
{canvas.name.slice(0, 1).toUpperCase()}
{/* Content */}
{isEditing ? ( setEditName(e.target.value)} onKeyDown={handleKeyDown} onBlur={handleBlur} disabled={isSaving} autoFocus onClick={(e) => e.stopPropagation()} className="h-auto border bg-transparent px-1.5 py-0.5 text-sm font-medium focus-visible:ring-1" /> ) : (

{canvas.name}

)}

Canvas

{/* Actions - positioned to not overlap with content */} {!isEditing && (
Umbenennen { setDeleteOpen(true); }} > Löschen
)}
Arbeitsbereich löschen?

„{canvas.name}“ und alle Knoten werden dauerhaft gelöscht. Das lässt sich nicht rückgängig machen.

); }