"use client"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { motion } from "framer-motion"; import { Download, Pencil, Trash2, Copy, Share2, FolderInput, Star, Eye } from "lucide-react"; import toast from "react-hot-toast"; import Modal from "../ui/Modal"; import Button from "../ui/Button"; import MoveModal from "./MoveModal"; import ShareModal from "./ShareModal"; interface FileData { id: string; name: string; size: number; mimeType: string; folderId?: string | null; metadata?: { isFavorite: boolean } | null; } interface FileActionsProps { file: FileData; onClose: () => void; onRefresh: () => void; /** Position fixe (menu contextuel) ; sinon ancré sous le déclencheur (absolute). */ anchor?: { x: number; y: number }; /** Ouvrir l'aperçu depuis le menu contextuel */ onPreview?: () => void; } export default function FileActions({ file, onClose, onRefresh, anchor, onPreview }: FileActionsProps) { const [renameModal, setRenameModal] = useState(false); const [shareModal, setShareModal] = useState(false); const [moveModal, setMoveModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); const [newName, setNewName] = useState(file.name); const [loading, setLoading] = useState(false); const menuRef = useRef(null); const [pos, setPos] = useState<{ x: number; y: number } | null>(anchor ?? null); // Clamp du menu contextuel dans le viewport useLayoutEffect(() => { if (!anchor || !menuRef.current) return; const r = menuRef.current.getBoundingClientRect(); const pad = 8; const x = Math.min(anchor.x, window.innerWidth - r.width - pad); const y = Math.min(anchor.y, window.innerHeight - r.height - pad); setPos({ x: Math.max(pad, x), y: Math.max(pad, y) }); }, [anchor]); // Fermer le menu contextuel : clic ailleurs / Échap / scroll useEffect(() => { if (!anchor) return; const anyModal = renameModal || shareModal || moveModal || deleteModal; if (anyModal) return; const onDown = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose(); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); window.addEventListener("scroll", onClose, true); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); window.removeEventListener("scroll", onClose, true); }; }, [anchor, onClose, renameModal, shareModal, moveModal, deleteModal]); const handleDownload = () => { window.open(`/api/files/${file.id}/download`, "_blank"); onClose(); }; const handleToggleFavorite = async () => { try { const res = await fetch(`/api/files/${file.id}/favorite`, { method: "POST" }); if (res.ok) { const meta = await res.json().catch(() => null); toast.success(meta?.isFavorite ? "Ajouté aux favoris" : "Retiré des favoris"); onRefresh(); } } catch { toast.error("Erreur"); } onClose(); }; const handleRename = async () => { if (!newName.trim()) return; setLoading(true); try { const res = await fetch(`/api/files/${file.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newName.trim() }), }); if (res.ok) { toast.success("Fichier renommé"); onRefresh(); } else toast.error("Renommage impossible"); } catch { toast.error("Erreur"); } finally { setLoading(false); setRenameModal(false); onClose(); } }; const handleDuplicate = async () => { try { const res = await fetch(`/api/files/${file.id}/duplicate`, { method: "POST" }); if (res.ok) { toast.success("Fichier dupliqué"); onRefresh(); } else toast.error("Duplication impossible"); } catch { toast.error("Erreur"); } onClose(); }; const handleDelete = async () => { setLoading(true); try { const res = await fetch(`/api/files/${file.id}`, { method: "DELETE" }); if (res.ok) { toast.success("Fichier déplacé dans la corbeille"); onRefresh(); } else toast.error("Suppression impossible"); } catch { toast.error("Erreur"); } finally { setLoading(false); setDeleteModal(false); onClose(); } }; const handleMove = async (targetFolderId: string) => { try { const res = await fetch(`/api/files/${file.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ folderId: targetFolderId }), }); if (res.ok) { toast.success("Fichier déplacé"); onRefresh(); } else toast.error("Déplacement impossible"); } catch { toast.error("Erreur"); } setMoveModal(false); onClose(); }; const isFav = !!file.metadata?.isFavorite; type Action = { icon: typeof Download; label: string; action: () => void; danger?: boolean; sep?: boolean; hint?: string }; const actions: Action[] = [ ...(onPreview ? [{ icon: Eye, label: "Aperçu", action: () => { onPreview(); onClose(); }, hint: "↵" }] : []), { icon: Download, label: "Télécharger", action: handleDownload }, { icon: Star, label: isFav ? "Retirer des favoris" : "Ajouter aux favoris", action: handleToggleFavorite }, { icon: Share2, label: "Partager…", action: () => setShareModal(true), sep: true }, { icon: Pencil, label: "Renommer…", action: () => setRenameModal(true) }, { icon: Copy, label: "Dupliquer", action: handleDuplicate }, { icon: FolderInput, label: "Déplacer vers…", action: () => setMoveModal(true) }, { icon: Trash2, label: "Supprimer", action: () => setDeleteModal(true), danger: true, sep: true, hint: "⌫" }, ]; const anyModal = renameModal || shareModal || moveModal || deleteModal; const menu = ( e.stopPropagation()} onContextMenu={(e) => e.preventDefault()} > {actions.map((a) => (
{a.sep &&
}
))} ); return ( <> {menu} { setRenameModal(false); onClose(); }} title="Renommer"> setNewName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleRename()} onFocus={(e) => { const dot = file.name.lastIndexOf("."); e.currentTarget.setSelectionRange(0, dot > 0 ? dot : file.name.length); }} className="field mb-4" autoFocus />
{ setDeleteModal(false); onClose(); }} title="Supprimer le fichier">

{file.name} sera déplacé dans la corbeille. Vous pourrez le restaurer pendant 30 jours.

{ setShareModal(false); onClose(); }} target={{ type: "file", id: file.id, name: file.name }} /> { setMoveModal(false); onClose(); }} onSelect={handleMove} itemName={file.name} currentFolderId={file.folderId ?? "root"} /> ); }