// ======================================== // IssueCard Component // ======================================== // Card component for displaying issues with actions import { useState } from 'react'; import { useIntl } from 'react-intl'; import { AlertCircle, AlertTriangle, Info, MoreVertical, Edit, Trash2, ExternalLink, CheckCircle, Clock, XCircle, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Card } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/Dropdown'; import type { DraggableProvidedDragHandleProps, DraggableProvidedDraggableProps } from '@hello-pangea/dnd'; import type { Issue } from '@/lib/api'; // ========== Types ========== export interface IssueCardProps { issue: Issue; onEdit?: (issue: Issue) => void; onDelete?: (issue: Issue) => void; onClick?: (issue: Issue) => void; onStatusChange?: (issue: Issue, status: Issue['status']) => void; className?: string; compact?: boolean; showActions?: boolean; draggableProps?: DraggableProvidedDraggableProps; dragHandleProps?: DraggableProvidedDragHandleProps | null; innerRef?: React.Ref; } // ========== Priority Helpers ========== // Priority icon and color configuration (without labels for i18n) const priorityVariantConfig: Record = { critical: { icon: AlertCircle, color: 'destructive' }, high: { icon: AlertTriangle, color: 'warning' }, medium: { icon: Info, color: 'info' }, low: { icon: Info, color: 'secondary' }, }; // Priority label keys for i18n const priorityLabelKeys: Record = { critical: 'issues.priority.critical', high: 'issues.priority.high', medium: 'issues.priority.medium', low: 'issues.priority.low', }; // Status icon and color configuration (without labels for i18n) const statusVariantConfig: Record = { open: { icon: AlertCircle, color: 'info' }, in_progress: { icon: Clock, color: 'warning' }, resolved: { icon: CheckCircle, color: 'success' }, closed: { icon: XCircle, color: 'muted' }, completed: { icon: CheckCircle, color: 'success' }, }; // Status label keys for i18n const statusLabelKeys: Record = { open: 'issues.status.open', in_progress: 'issues.status.inProgress', resolved: 'issues.status.resolved', closed: 'issues.status.closed', completed: 'issues.status.completed', }; // ========== Priority Badge ========== export function PriorityBadge({ priority }: { priority: Issue['priority'] }) { const { formatMessage } = useIntl(); const config = priorityVariantConfig[priority]; // Defensive check: handle unknown priority values if (!config) { return ( {priority} ); } const Icon = config.icon; const label = priorityLabelKeys[priority] ? formatMessage({ id: priorityLabelKeys[priority] }) : priority; return ( {label} ); } // ========== Status Badge ========== export function StatusBadge({ status }: { status: Issue['status'] }) { const { formatMessage } = useIntl(); const config = statusVariantConfig[status]; // Defensive check: handle unknown status values if (!config) { return ( {status} ); } const Icon = config.icon; const label = statusLabelKeys[status] ? formatMessage({ id: statusLabelKeys[status] }) : status; return ( {label} ); } // ========== Main IssueCard Component ========== export function IssueCard({ issue, onEdit, onDelete, onClick, onStatusChange, className, compact = false, showActions = true, draggableProps, dragHandleProps, innerRef, }: IssueCardProps) { const { formatMessage } = useIntl(); const [isMenuOpen, setIsMenuOpen] = useState(false); const handleClick = () => { if (!isMenuOpen) { onClick?.(issue); } }; const handleEdit = (e: React.MouseEvent) => { e.stopPropagation(); setIsMenuOpen(false); onEdit?.(issue); }; const handleDelete = (e: React.MouseEvent) => { e.stopPropagation(); setIsMenuOpen(false); onDelete?.(issue); }; if (compact) { return (

{issue.title}

#{issue.id}

); } return ( {/* Header */}

{issue.title}

#{issue.id}

{showActions && ( {formatMessage({ id: 'issues.actions.edit' })} onStatusChange?.(issue, 'in_progress')}> {formatMessage({ id: 'issues.actions.startProgress' })} onStatusChange?.(issue, 'resolved')}> {formatMessage({ id: 'issues.actions.markResolved' })} {formatMessage({ id: 'issues.actions.delete' })} )}
{/* Context Preview */} {issue.context && (

{issue.context}

)} {/* Labels */} {issue.labels && issue.labels.length > 0 && (
{issue.labels.slice(0, 3).map((label) => ( {label} ))} {issue.labels.length > 3 && ( +{issue.labels.length - 3} )}
)} {/* Footer */}
{/* Solutions Count */} {issue.solutions && issue.solutions.length > 0 && (
{issue.solutions.length} {formatMessage( { id: 'issues.card.solutions' }, { count: issue.solutions.length } )}
)}
); } export default IssueCard;