import { useState } from "react"; import { formatDistanceToNow } from "date-fns"; import { motion, useReducedMotion } from "framer-motion"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { fadeSlideUp, springs, subtleHover } from "@/lib/motion"; import { Trash2, Copy, FileDown, FileSpreadsheet, Bookmark } from "lucide-react"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, ContextMenuSeparator, } from "@/components/ui/context-menu"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import type { BoardMeta } from "@/types/board"; import { useAppStore } from "@/stores/app-store"; import { useBoardStore } from "@/stores/board-store"; import { useToastStore } from "@/stores/toast-store"; import { deleteBoard, loadBoard, saveBoard, saveTemplate } from "@/lib/storage"; import { exportBoardAsJson, exportBoardAsCsv } from "@/lib/import-export"; import type { BoardTemplate } from "@/types/template"; interface BoardCardProps { board: BoardMeta; sortable?: boolean; } export function BoardCard({ board, sortable = false }: BoardCardProps) { const [confirmDelete, setConfirmDelete] = useState(false); const prefersReducedMotion = useReducedMotion(); const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: board.id, disabled: !sortable, }); const addToast = useToastStore((s) => s.addToast); const setView = useAppStore((s) => s.setView); const addRecentBoard = useAppStore((s) => s.addRecentBoard); const refreshBoards = useAppStore((s) => s.refreshBoards); const openBoard = useBoardStore((s) => s.openBoard); const relativeTime = formatDistanceToNow(new Date(board.updatedAt), { addSuffix: true, }); async function handleOpen() { await openBoard(board.id); setView({ type: "board", boardId: board.id }); addRecentBoard(board.id); } async function handleDelete() { await deleteBoard(board.id); await refreshBoards(); setConfirmDelete(false); addToast(`"${board.title}" deleted`, "info"); } async function handleDuplicate() { const original = await loadBoard(board.id); const { ulid } = await import("ulid"); const ts = new Date().toISOString(); const duplicated = { ...original, id: ulid(), title: `${original.title} (copy)`, createdAt: ts, updatedAt: ts, }; await saveBoard(duplicated); await refreshBoards(); addToast(`"${board.title}" duplicated`, "success"); } function downloadBlob(content: string, filename: string, mimeType: string) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } async function handleExportJson() { const full = await loadBoard(board.id); const json = exportBoardAsJson(full); const safeName = board.title.replace(/[^a-zA-Z0-9-_]/g, "_"); downloadBlob(json, `${safeName}.json`, "application/json"); addToast("Board exported as JSON", "success"); } async function handleExportCsv() { const full = await loadBoard(board.id); const csv = exportBoardAsCsv(full); const safeName = board.title.replace(/[^a-zA-Z0-9-_]/g, "_"); downloadBlob(csv, `${safeName}.csv`, "text/csv"); addToast("Board exported as CSV", "success"); } async function handleSaveAsTemplate() { const full = await loadBoard(board.id); const { ulid } = await import("ulid"); const template: BoardTemplate = { id: ulid(), name: full.title, color: full.color, columns: full.columns.map((c) => ({ title: c.title, width: c.width, color: c.color, wipLimit: c.wipLimit, })), labels: full.labels, settings: full.settings, }; await saveTemplate(template); addToast(`Template "${full.title}" saved`, "success"); } if (isDragging) { return (
{/* Invisible clone to maintain grid row height */}

 

 

 

); } return ( Duplicate Save as Template Export as JSON Export as CSV setConfirmDelete(true)} > Delete {/* Delete confirmation dialog */} Delete Board Are you sure you want to delete “{board.title}”? This action cannot be undone. ); }