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
{/* Checklists */}
✅ Checklists
{card.checklists?.map(cl => (
))}
{/* Comments */}
💬 Comments ({card.comments?.length || 0})
{card.comments?.map(comment => (
{comment.name?.charAt(0)?.toUpperCase() || '?'}
{comment.name}
{new Date(comment.created_at).toLocaleString()}
{comment.is_email_reply && 📧 email}
{comment.content}
))}
{/* Sidebar */}
{/* Time Tracking */}
{/* Activity */}
📜 Activity
{card.activity?.map(a => (
{a.name?.charAt(0)?.toUpperCase()}
{a.name} {a.action}
{new Date(a.created_at).toLocaleString()}
))}
{/* Actions */}
);
}
function ChecklistItemAdd({ checklistId, onAdd }) {
const [text, setText] = useState('');
const [show, setShow] = useState(false);
if (!show) return ;
return (
);
}