"use client"; import { useRouter } from "next/navigation"; import { motion } from "framer-motion"; import { Folder, FolderOpen, MoreVertical, Pencil, Trash2, FolderInput, ChevronRight, Share2, Download } from "lucide-react"; import { useState, useRef, useEffect, memo } from "react"; import toast from "react-hot-toast"; import Modal from "../ui/Modal"; import Button from "../ui/Button"; import SharedBadge from "../ui/SharedBadge"; import MoveModal from "../files/MoveModal"; import ShareModal from "../files/ShareModal"; import { DND_FOLDER, handleInternalDrop, hasInternalDrag, notifyFoldersChanged, readFolderId, setDragGhost } from "../files/dnd"; interface FolderData { id: string; name: string; parentId?: string | null; sharedSpaceId?: string | null; _count?: { files: number; children: number }; } interface FolderCardProps { folder: FolderData; onRefresh: () => void; viewMode: "grid" | "list"; /** Navigation personnalisée (espaces partagés) */ onOpen?: (folder: FolderData) => void; focusable?: boolean; } function FolderCardInner({ folder, onRefresh, viewMode, onOpen, focusable }: FolderCardProps) { const router = useRouter(); const [showMenu, setShowMenu] = useState(false); const [renameModal, setRenameModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); const [moveModal, setMoveModal] = useState(false); const [shareModal, setShareModal] = useState(false); const [newName, setNewName] = useState(folder.name); const [loading, setLoading] = useState(false); const [dragOver, setDragOver] = useState(false); const [dragging, setDragging] = useState(false); const menuRef = useRef(null); const overCount = useRef(0); useEffect(() => { if (!showMenu) return; function handleClick(e: MouseEvent) { if (menuRef.current && !menuRef.current.contains(e.target as Node)) setShowMenu(false); } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [showMenu]); const open = () => (onOpen ? onOpen(folder) : router.push(`/dashboard/folder/${folder.id}`)); const handleMove = async (targetFolderId: string) => { try { const res = await fetch(`/api/folders/${folder.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ parentId: targetFolderId }), }); if (res.ok) { toast.success("Dossier déplacé"); notifyFoldersChanged(); onRefresh(); } else { const d = await res.json().catch(() => ({})); toast.error(d.error || "Déplacement impossible"); } } catch { toast.error("Erreur"); } setMoveModal(false); }; const handleRename = async () => { if (!newName.trim()) return; setLoading(true); try { const res = await fetch(`/api/folders/${folder.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newName.trim() }), }); if (res.ok) { toast.success("Dossier renommé"); notifyFoldersChanged(); onRefresh(); } else toast.error("Renommage impossible"); } catch { toast.error("Erreur"); } finally { setLoading(false); setRenameModal(false); } }; const handleDelete = async () => { setLoading(true); try { const res = await fetch(`/api/folders/${folder.id}`, { method: "DELETE" }); if (res.ok) { toast.success("Dossier supprimé"); notifyFoldersChanged(); onRefresh(); } else toast.error("Suppression impossible"); } catch { toast.error("Erreur"); } finally { setLoading(false); setDeleteModal(false); } }; /* ---- DnD : source ---- */ const onDragStart = (e: React.DragEvent) => { e.dataTransfer.setData(DND_FOLDER, folder.id); e.dataTransfer.setData("text/plain", folder.name); e.dataTransfer.effectAllowed = "move"; setDragGhost(e, `📁 ${folder.name}`); setDragging(true); }; /* ---- DnD : cible ---- */ const acceptable = (e: React.DragEvent) => hasInternalDrag(e.dataTransfer) && !dragging; const onDragEnter = (e: React.DragEvent) => { if (!acceptable(e)) return; e.preventDefault(); overCount.current++; setDragOver(true); }; const onDragOver = (e: React.DragEvent) => { if (!acceptable(e)) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; }; const onDragLeave = (e: React.DragEvent) => { if (!acceptable(e)) return; overCount.current = Math.max(0, overCount.current - 1); if (overCount.current === 0) setDragOver(false); }; const onDrop = async (e: React.DragEvent) => { if (!acceptable(e)) return; e.preventDefault(); e.stopPropagation(); overCount.current = 0; setDragOver(false); if (readFolderId(e.dataTransfer) === folder.id) return; const moved = await handleInternalDrop(e.dataTransfer, folder.id); if (moved) onRefresh(); }; const dndProps = { draggable: true, onDragStart, onDragEnd: () => setDragging(false), onDragEnter, onDragOver, onDragLeave, onDrop, "data-folder-id": folder.id, tabIndex: focusable ? 0 : -1, onKeyDown: (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); open(); } }, } as const; const menuItems = [ { icon: Download, label: "Télécharger en ZIP", onClick: () => window.open(`/api/folders/${folder.id}/download`, "_blank") }, { icon: Share2, label: "Partager…", onClick: () => setShareModal(true) }, { icon: Pencil, label: "Renommer…", onClick: () => setRenameModal(true) }, { icon: FolderInput, label: "Déplacer vers…", onClick: () => setMoveModal(true) }, { icon: Trash2, label: "Supprimer", onClick: () => setDeleteModal(true), danger: true }, ]; const Menu = () => showMenu ? ( {menuItems.map((it, i) => (
{i === menuItems.length - 1 &&
}
))} ) : null; const modals = ( <> setRenameModal(false)} title="Renommer le dossier"> setNewName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleRename()} className="field mb-4" autoFocus onFocus={(e) => e.currentTarget.select()} />
setDeleteModal(false)} title="Supprimer le dossier">

Supprimer {folder.name} {folder._count ? <> ({folder._count.files} fichier{folder._count.files !== 1 ? "s" : ""}, {folder._count.children} sous-dossier{folder._count.children !== 1 ? "s" : ""}) : null} ? Les fichiers qu'il contient seront placés dans la corbeille et restaurables pendant 30 jours.

setMoveModal(false)} onSelect={handleMove} excludeId={folder.id} itemName={folder.name} currentFolderId={folder.parentId ?? "root"} /> setShareModal(false)} target={{ type: "folder", id: folder.id, name: folder.name }} /> ); const count = folder._count; const countLabel = count ? [count.files ? `${count.files} fich.` : null, count.children ? `${count.children} doss.` : null].filter(Boolean).join(" · ") || "Vide" : ""; /* ================= LISTE ================= */ if (viewMode === "list") { return ( <>
{dragOver ? : }
{folder.name} {folder.sharedSpaceId && }
Dossier {countLabel}
{modals} ); } /* ================= GRILLE ================= */ return ( <>
{dragOver ? : }

{folder.name}

{folder.sharedSpaceId && }
{count &&

{countLabel}

}
{modals} ); } const FolderCard = memo(FolderCardInner); export default FolderCard;