'use client';
import React from 'react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { KanbanItem, isKanbanEpic, isKanbanStory, isKanbanTask, getKanbanItemTitle } from '@/types/kanban';
import { FolderKanban, FileText, CheckSquare } from 'lucide-react';
interface IssueCardProps {
issue: KanbanItem;
}
export const IssueCard = React.memo(function IssueCard({ issue }: IssueCardProps) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: issue.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const priorityColors = {
Low: 'bg-gray-100 text-gray-700',
Medium: 'bg-blue-100 text-blue-700',
High: 'bg-orange-100 text-orange-700',
Critical: 'bg-red-100 text-red-700',
};
// Type icon components - type-safe with discriminated union
const getTypeIcon = () => {
switch (issue.type) {
case 'Epic':
return ;
case 'Story':
return ;
case 'Task':
return ;
default:
return null;
}
};
// Parent breadcrumb (for Story and Task) - type-safe with type guards
const renderParentBreadcrumb = () => {
// Story shows parent Epic - TypeScript knows epicId exists
if (isKanbanStory(issue)) {
return (
Epic: {issue.epicId}
);
}
// Task shows parent Story - TypeScript knows storyId exists
if (isKanbanTask(issue)) {
return (
Story: {issue.storyId}
);
}
return null;
};
// Child count badge (for Epic and Story) - type-safe with type guards
const renderChildCount = () => {
// Epic shows number of stories - TypeScript knows childCount exists
if (isKanbanEpic(issue) && issue.childCount && issue.childCount > 0) {
return (
{issue.childCount} stories
);
}
// Story shows number of tasks - TypeScript knows childCount exists
if (isKanbanStory(issue) && issue.childCount && issue.childCount > 0) {
return (
{issue.childCount} tasks
);
}
return null;
};
// Get display title - type-safe helper function
const displayTitle = getKanbanItemTitle(issue);
return (
{/* Header: Type icon + Child count */}
{getTypeIcon()}
{issue.type}
{renderChildCount()}
{/* Parent breadcrumb */}
{renderParentBreadcrumb()}
{/* Title - type-safe */}
{displayTitle}
{/* Description (if available) - type-safe */}
{issue.description && (
{issue.description}
)}
{/* Footer: Priority + Hours - type-safe */}
{issue.priority}
{issue.estimatedHours && (
{issue.estimatedHours}h
)}
);
});