"use client"; import { useCallback, memo } from "react"; import { AppLink } from "../../navigation"; import { useSortable, defaultAnimateLayoutChanges } from "@dnd-kit/sortable"; import type { AnimateLayoutChanges } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { toast } from "sonner"; import type { Issue, UpdateIssueRequest } from "@multica/core/types"; import { CalendarDays } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { ActorAvatar } from "../../common/actor-avatar"; import { useUpdateIssue } from "@multica/core/issues/mutations"; import { useWorkspacePaths } from "@multica/core/paths"; import { useWorkspaceId } from "@multica/core/hooks"; import { projectListOptions } from "@multica/core/projects/queries"; import { PriorityIcon } from "./priority-icon"; import { PriorityPicker, AssigneePicker, DueDatePicker } from "./pickers"; import { PRIORITY_CONFIG } from "@multica/core/issues/config"; import { useViewStore } from "@multica/core/issues/stores/view-store-context"; import { ProgressRing } from "./progress-ring"; import type { ChildProgress } from "./list-row"; function formatDate(date: string): string { return new Date(date).toLocaleDateString("en-US", { month: "short", day: "numeric", }); } /** Stops event from bubbling to Link/drag handlers */ function PickerWrapper({ children }: { children: React.ReactNode }) { const stop = (e: React.SyntheticEvent) => { e.stopPropagation(); e.preventDefault(); }; return (
{children}
); } export const BoardCardContent = memo(function BoardCardContent({ issue, editable = false, childProgress, }: { issue: Issue; editable?: boolean; childProgress?: ChildProgress; }) { const storeProperties = useViewStore((s) => s.cardProperties); const priorityCfg = PRIORITY_CONFIG[issue.priority]; const wsId = useWorkspaceId(); const { data: projects = [] } = useQuery({ ...projectListOptions(wsId), enabled: storeProperties.project && !!issue.project_id, }); const project = issue.project_id ? projects.find((p) => p.id === issue.project_id) : undefined; const updateIssueMutation = useUpdateIssue(); const handleUpdate = useCallback( (updates: Partial) => { updateIssueMutation.mutate( { id: issue.id, ...updates }, { onError: () => toast.error("Failed to update issue") }, ); }, [issue.id, updateIssueMutation], ); const showPriority = storeProperties.priority; const showDescription = storeProperties.description && issue.description; const showAssignee = storeProperties.assignee && issue.assignee_type && issue.assignee_id; const showDueDate = storeProperties.dueDate && issue.due_date; const showProject = storeProperties.project && project; const showChildProgress = storeProperties.childProgress && childProgress; return (
{/* Row 1: Identifier */}

{issue.identifier}

{/* Row 2: Title */}

{issue.title}

{/* Sub-issue progress + project */} {(showChildProgress || showProject) && (
{showChildProgress && (
{childProgress!.done}/{childProgress!.total}
)} {showProject && ( {project!.title} )}
)} {/* Description */} {showDescription && (

{issue.description}

)} {/* Row 3: Assignee, priority badge, due date */} {(showAssignee || showPriority || showDueDate) && (
{showAssignee && (editable ? ( } /> ) : ( ))} {showPriority && (editable ? ( {priorityCfg.label} } /> ) : ( {priorityCfg.label} ))} {showDueDate && (
{editable ? ( {formatDate(issue.due_date!)} } /> ) : ( {formatDate(issue.due_date!)} )}
)}
)}
); }); const animateLayoutChanges: AnimateLayoutChanges = (args) => { const { isSorting, wasDragging } = args; if (isSorting || wasDragging) return false; return defaultAnimateLayoutChanges(args); }; export const DraggableBoardCard = memo(function DraggableBoardCard({ issue, childProgress }: { issue: Issue; childProgress?: ChildProgress }) { const p = useWorkspacePaths(); const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: issue.id, data: { status: issue.status }, animateLayoutChanges, }); const style = { transform: CSS.Transform.toString(transform), transition, }; return (
); });