kanban-app/frontend/src/components/CardPreviewModal.tsx
2026-02-27 22:25:34 +03:00

155 lines
6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CardWithDetails } from '../types/kanban';
import { Link } from 'react-router-dom';
interface CardPreviewModalProps {
card: CardWithDetails;
onClose: () => void;
}
export function CardPreviewModal({ card, onClose }: CardPreviewModalProps) {
return (
<div className="bg-gray-800 rounded-lg p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
<div className="flex justify-between items-start mb-4">
<Link
to={`/boards/${card.board_id}/cards/${card.id}`}
className="text-2xl font-bold text-blue-400 hover:text-blue-300 hover:underline transition-colors"
>
{card.name}
</Link>
<button
onClick={onClose}
className="text-gray-400 hover:text-white transition-colors text-2xl leading-none"
>
×
</button>
</div>
{/* Labels */}
{card.labels && card.labels.length > 0 && (
<div className="mb-4">
<div className="flex flex-wrap gap-2">
{card.labels.map((label) => (
<span
key={label.id}
className="px-3 py-1 rounded-full text-sm font-medium text-white"
style={{ backgroundColor: label.color }}
>
{label.name}
</span>
))}
</div>
</div>
)}
{/* Description */}
{card.description && (
<div className="mb-4">
<h3 className="text-lg font-semibold text-white mb-2">Description</h3>
<p className="text-gray-300 whitespace-pre-wrap">{card.description}</p>
</div>
)}
{/* Checklists */}
{card.checklists && card.checklists.length > 0 && (
<div className="mb-4">
<h3 className="text-lg font-semibold text-white mb-2">Checklists</h3>
<div className="space-y-3">
{card.checklists.map((checklist) => {
const completedItems = checklist.items.filter(
(item) => item.state === 'complete'
).length;
const totalItems = checklist.items.length;
const progress = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
return (
<div key={checklist.id} className="bg-gray-700 rounded-lg p-4">
<div className="flex justify-between items-center mb-3">
<h4 className="font-semibold text-white">{checklist.name}</h4>
<span className="text-sm text-gray-400">
{completedItems}/{totalItems}
</span>
</div>
<div className="w-full h-2 bg-gray-600 rounded-full overflow-hidden mb-3">
<div
className="h-full bg-green-500 transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
{/* Checklist Items */}
<div className="space-y-2">
{checklist.items.map((item) => (
<div key={item.id} className="flex items-start gap-2 text-sm">
<div
className={`w-4 h-4 mt-0.5 rounded border-2 flex items-center justify-center flex-shrink-0 ${
item.state === 'complete'
? 'bg-green-500 border-green-500'
: 'border-gray-500'
}`}
>
{item.state === 'complete' && (
<svg
className="w-3 h-3 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="3"
d="M5 13l4 4L19 7"
/>
</svg>
)}
</div>
<span
className={`text-gray-300 ${
item.state === 'complete' ? 'line-through text-gray-500' : ''
}`}
>
{item.name}
</span>
</div>
))}
</div>
</div>
);
})}
</div>
</div>
)}
{/* Comments Preview */}
{card.comments && card.comments.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-white mb-2">Comments</h3>
<div className="space-y-3 max-h-64 overflow-y-auto">
{card.comments.slice(0, 3).map((comment) => (
<div key={comment.id} className="bg-gray-700 rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center text-white font-bold text-xs">
{comment.user?.username.charAt(0).toUpperCase() || '?'}
</div>
<div>
<p className="text-white text-sm font-medium">
{comment.user?.username || 'Unknown'}
</p>
<p className="text-gray-400 text-xs">
{new Date(comment.created_at).toLocaleDateString()}
</p>
</div>
</div>
<p className="text-gray-300 text-sm line-clamp-3">{comment.text}</p>
</div>
))}
{card.comments.length > 3 && (
<p className="text-gray-400 text-sm text-center">
+{card.comments.length - 3} more comments
</p>
)}
</div>
</div>
)}
</div>
);
}