import React, { useState, useEffect, useRef } from 'react'; import { api } from '../api'; export default function CardModal({ cardId, boardId, onClose }) { const [card, setCard] = useState(null); const [loading, setLoading] = useState(true); const [editing, setEditing] = useState(null); const [newComment, setNewComment] = useState(''); const [showLabelPicker, setShowLabelPicker] = useState(false); const [newChecklist, setNewChecklist] = useState(''); const modalRef = useRef(null); const commentRef = useRef(null); useEffect(() => { loadCard(); const handler = (e) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [cardId]); const loadCard = async () => { try { const data = await api.cards.get(cardId); setCard(data); } catch {} setLoading(false); }; const updateCard = async (updates) => { try { await api.cards.update(cardId, updates); loadCard(); } catch (err) { alert(err.message); } }; const addComment = async (e) => { e.preventDefault(); if (!newComment.trim()) return; try { await api.cards.addComment(cardId, newComment); setNewComment(''); loadCard(); } catch (err) { alert(err.message); } }; const toggleLabel = async (labelId) => { const current = card.labels.map(l => l.id); const next = current.includes(labelId) ? current.filter(id => id !== labelId) : [...current, labelId]; try { await api.cards.updateCardLabels(cardId, next); loadCard(); } catch {} }; const createChecklist = async (e) => { e.preventDefault(); if (!newChecklist.trim()) return; try { await api.cards.createChecklist(cardId, newChecklist); setNewChecklist(''); loadCard(); } catch {} }; const addChecklistItem = async (checklistId, text) => { if (!text.trim()) return; try { await api.cards.addChecklistItem(checklistId, text); loadCard(); } catch {} }; const toggleCheckItem = async (itemId, checked) => { try { await api.cards.updateChecklistItem(itemId, { is_checked: !checked }); loadCard(); } catch {} }; const priorityOptions = ['none', 'low', 'medium', 'high', 'urgent']; const priorityColors = { none: 'gray', low: 'green', medium: 'yellow', high: 'orange', urgent: 'red' }; if (loading) return null; if (!card) return null; return (
e.stopPropagation()} className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col"> {/* Header */}
#{card.id} {card.priority !== 'none' && ( {card.priority.toUpperCase()} )}
{/* Title */} setCard({ ...card, title: e.target.value })} onBlur={() => updateCard({ title: card.title })} className="w-full text-xl font-bold text-gray-900 border-0 p-0 focus:ring-0 mb-3" /> {/* Meta row */}
{/* Assignee */}
{/* Due Date */} updateCard({ due_date: e.target.value || null })} className="text-sm bg-gray-100 border-0 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-brand-500" /> {/* Priority */} {/* Labels */}
{showLabelPicker && (
{card._labels?.map?.(label => ( )) ||

No labels on this board

}
)}
{/* Labels display */} {card.labels?.length > 0 && (
{card.labels.map(l => ( {l.name} ))}
)}
{/* Main content */}
{/* Description */}

📝 Description