212 lines
7.2 KiB
TypeScript
212 lines
7.2 KiB
TypeScript
|
|
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||
|
|
import { useCard } from '../hooks/useCard';
|
||
|
|
import { useCardDetailMutations } from '../hooks/useCardDetailMutations';
|
||
|
|
import { useModal } from '../context/modals/useModal';
|
||
|
|
import { CardSidebar } from '../components/CardSidebar';
|
||
|
|
import { CardComments } from '../components/CardComments';
|
||
|
|
import { EditCardModal } from '../components/EditCardModal';
|
||
|
|
import { DeleteCardModal } from '../components/DeleteCardModal';
|
||
|
|
import CheckSquareIcon from '../components/icons/CheckSquareIcon';
|
||
|
|
import TagIcon from '../components/icons/TagIcon';
|
||
|
|
import Trash2Icon from '../components/icons/Trash2Icon';
|
||
|
|
import ArrowLeftIcon from '../components/icons/ArrowLeftIcon';
|
||
|
|
import Edit2Icon from '../components/icons/Edit2Icon';
|
||
|
|
|
||
|
|
export function CardDetail() {
|
||
|
|
const { id: boardId, cardId } = useParams<{ id: string; cardId: string }>();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const { card, fetchCard } = useCard(parseInt(cardId || '0'));
|
||
|
|
const {
|
||
|
|
updateCardNameAndDescription,
|
||
|
|
deleteCardWithConfirmation,
|
||
|
|
addComment,
|
||
|
|
editComment,
|
||
|
|
deleteCommentWithConfirmation,
|
||
|
|
} = useCardDetailMutations(parseInt(cardId || '0'), card, fetchCard);
|
||
|
|
|
||
|
|
const { openModal } = useModal();
|
||
|
|
|
||
|
|
const handleEditCard = () => {
|
||
|
|
if (!card) return;
|
||
|
|
|
||
|
|
openModal((props) => (
|
||
|
|
<EditCardModal
|
||
|
|
card={card}
|
||
|
|
onSave={async (name, description) => {
|
||
|
|
return await updateCardNameAndDescription(name, description);
|
||
|
|
}}
|
||
|
|
onClose={props.onClose}
|
||
|
|
/>
|
||
|
|
));
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDeleteCard = () => {
|
||
|
|
if (!card) return;
|
||
|
|
|
||
|
|
openModal((props) => (
|
||
|
|
<DeleteCardModal
|
||
|
|
cardName={card.name}
|
||
|
|
onDelete={async () => {
|
||
|
|
deleteCardWithConfirmation(() => {
|
||
|
|
props.onClose();
|
||
|
|
navigate(`/boards/${boardId}`);
|
||
|
|
});
|
||
|
|
}}
|
||
|
|
onClose={props.onClose}
|
||
|
|
/>
|
||
|
|
));
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!card) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const formatDate = (dateString: string) => {
|
||
|
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||
|
|
year: 'numeric',
|
||
|
|
month: 'short',
|
||
|
|
day: 'numeric',
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit',
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<div className="flex justify-between items-start">
|
||
|
|
<div>
|
||
|
|
<Link
|
||
|
|
to={`/boards/${boardId}`}
|
||
|
|
className="text-gray-400 hover:text-white transition-colors text-sm flex items-center gap-1"
|
||
|
|
>
|
||
|
|
<span className="w-4 h-4">
|
||
|
|
<ArrowLeftIcon />
|
||
|
|
</span>
|
||
|
|
Back to Board
|
||
|
|
</Link>
|
||
|
|
<div className="flex items-center gap-3 mt-2">
|
||
|
|
<h1 className="text-3xl font-bold text-white">{card.name}</h1>
|
||
|
|
<button
|
||
|
|
onClick={handleEditCard}
|
||
|
|
className="text-gray-400 hover:text-white transition-colors"
|
||
|
|
title="Edit card"
|
||
|
|
>
|
||
|
|
<span className="w-5 h-5">
|
||
|
|
<Edit2Icon />
|
||
|
|
</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<p className="text-gray-400 text-sm mt-1">
|
||
|
|
In list • Created {formatDate(card.created_at)}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={handleDeleteCard}
|
||
|
|
className="bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded-lg transition-colors flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<span className="w-4 h-4">
|
||
|
|
<Trash2Icon />
|
||
|
|
</span>
|
||
|
|
Delete Card
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
|
<div className="lg:col-span-2 space-y-6">
|
||
|
|
{/* Description Section */}
|
||
|
|
<div className="bg-gray-800 rounded-lg p-6">
|
||
|
|
<div className="flex justify-between items-center mb-4">
|
||
|
|
<h2 className="text-xl font-bold text-white flex items-center gap-2">Description</h2>
|
||
|
|
<button
|
||
|
|
onClick={handleEditCard}
|
||
|
|
className="text-blue-400 hover:text-blue-300 text-sm font-medium"
|
||
|
|
>
|
||
|
|
Edit
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<p className="text-gray-300 whitespace-pre-wrap">
|
||
|
|
{card.description || 'No description added yet.'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Labels Section */}
|
||
|
|
{card.labels && card.labels.length > 0 && (
|
||
|
|
<div className="bg-gray-800 rounded-lg p-6">
|
||
|
|
<h2 className="text-xl font-bold text-white flex items-center gap-2 mb-4">
|
||
|
|
<span className="w-5 h-5">
|
||
|
|
<TagIcon />
|
||
|
|
</span>
|
||
|
|
Labels
|
||
|
|
</h2>
|
||
|
|
<div className="flex flex-wrap gap-2">
|
||
|
|
{card.labels.map((label: any) => (
|
||
|
|
<span
|
||
|
|
key={label.id}
|
||
|
|
className="px-3 py-1 rounded-full text-sm font-medium"
|
||
|
|
style={{ backgroundColor: label.color, color: 'white' }}
|
||
|
|
>
|
||
|
|
{label.name}
|
||
|
|
</span>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Checklists Section */}
|
||
|
|
{card.checklists && card.checklists.length > 0 && (
|
||
|
|
<div className="bg-gray-800 rounded-lg p-6">
|
||
|
|
<h2 className="text-xl font-bold text-white flex items-center gap-2 mb-4">
|
||
|
|
<span className="w-5 h-5">
|
||
|
|
<CheckSquareIcon />
|
||
|
|
</span>
|
||
|
|
Checklists
|
||
|
|
</h2>
|
||
|
|
<div className="space-y-4">
|
||
|
|
{card.checklists.map((checklist: any) => (
|
||
|
|
<div key={checklist.id} className="border border-gray-700 rounded-lg p-4">
|
||
|
|
<h3 className="font-semibold text-white mb-3">{checklist.name}</h3>
|
||
|
|
<div className="space-y-2">
|
||
|
|
{checklist.items && checklist.items.length > 0 ? (
|
||
|
|
checklist.items.map((item: any) => (
|
||
|
|
<div
|
||
|
|
key={item.id}
|
||
|
|
className="flex items-center gap-3 p-2 bg-gray-700 rounded"
|
||
|
|
>
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={item.state === 'complete'}
|
||
|
|
readOnly
|
||
|
|
className="w-5 h-5 rounded"
|
||
|
|
/>
|
||
|
|
<span
|
||
|
|
className={`text-sm ${item.state === 'complete' ? 'text-gray-400 line-through' : 'text-white'}`}
|
||
|
|
>
|
||
|
|
{item.name}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
))
|
||
|
|
) : (
|
||
|
|
<p className="text-gray-400 text-sm">No items yet</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<CardComments
|
||
|
|
card={card}
|
||
|
|
addComment={addComment}
|
||
|
|
editComment={editComment}
|
||
|
|
deleteCommentWithConfirmation={deleteCommentWithConfirmation}
|
||
|
|
openModal={openModal}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<CardSidebar card={card} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|