292 lines
12 KiB
TypeScript
292 lines
12 KiB
TypeScript
import { useParams, Link, useNavigate } from 'react-router-dom';
|
|
import { useState } from 'react';
|
|
import { WidePageLayout } from '../components/WidePageLayout';
|
|
import RichTextContent from '../components/RichTextContent';
|
|
import useWikiDetail from '../hooks/useWikiDetail';
|
|
import Edit2Icon from '../components/icons/Edit2Icon';
|
|
import Trash2Icon from '../components/icons/Trash2Icon';
|
|
import ChevronRightIcon from '../components/icons/ChevronRightIcon';
|
|
import ChevronDownIcon from '../components/icons/ChevronDownIcon';
|
|
|
|
export function WikiDetail() {
|
|
const { id: boardId, wikiId } = useParams<{ id: string; wikiId: string }>();
|
|
const navigate = useNavigate();
|
|
const { wiki, deleteWiki } = useWikiDetail(wikiId || '0');
|
|
const [isContentExpanded, setIsContentExpanded] = useState(false);
|
|
|
|
const handleDelete = async () => {
|
|
if (!wiki) return;
|
|
|
|
if (confirm(`Are you sure you want to delete wiki "${wiki.name}"?`)) {
|
|
try {
|
|
await deleteWiki();
|
|
navigate(`/boards/${boardId}/wikis`);
|
|
} catch {
|
|
// Error is already handled by the hook with toast
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!wiki) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<WidePageLayout>
|
|
<div className="text-center py-12">
|
|
<div className="text-gray-400">Loading wiki...</div>
|
|
</div>
|
|
</WidePageLayout>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const hasContent = wiki.content && Array.isArray(wiki.content) && wiki.content.length > 0;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<WidePageLayout>
|
|
<div>
|
|
<Link
|
|
to={`/boards/${boardId}/wikis`}
|
|
className="text-gray-400 hover:text-white transition-colors text-sm"
|
|
>
|
|
← Back to Wikis
|
|
</Link>
|
|
<div className="flex items-center justify-between mt-2">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white">{wiki.name}</h1>
|
|
{wiki.slug && <span className="text-gray-400 text-sm">/{wiki.slug}</span>}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Link
|
|
to={`/boards/${boardId}/wikis/${wiki.id}/edit`}
|
|
className="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
|
|
>
|
|
<span className="w-4 h-4">
|
|
<Edit2Icon />
|
|
</span>
|
|
Edit
|
|
</Link>
|
|
<button
|
|
onClick={handleDelete}
|
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
|
|
>
|
|
<span className="w-4 h-4">
|
|
<Trash2Icon />
|
|
</span>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</WidePageLayout>
|
|
|
|
{/* Content */}
|
|
<WidePageLayout>
|
|
<div className="bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
|
|
<div className="p-6">
|
|
{/* Summary */}
|
|
{wiki.summary && (
|
|
<div className="mb-6 pb-6 border-b border-gray-700">
|
|
<h2 className="text-lg font-semibold text-white mb-2">Summary</h2>
|
|
<p className="text-gray-300">{wiki.summary}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Rich Text Content - Collapsible */}
|
|
{hasContent && (
|
|
<div className="mb-6 pb-6 border-b border-gray-700">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<h2 className="text-lg font-semibold text-white">Content</h2>
|
|
<button
|
|
onClick={() => setIsContentExpanded(!isContentExpanded)}
|
|
className="text-blue-500 hover:text-blue-400 text-sm font-medium flex items-center gap-1 transition-colors"
|
|
>
|
|
{isContentExpanded ? (
|
|
<>
|
|
<span className="w-4 h-4">
|
|
<ChevronDownIcon />
|
|
</span>
|
|
Collapse
|
|
</>
|
|
) : (
|
|
<>
|
|
<span className="w-4 h-4">
|
|
<ChevronRightIcon />
|
|
</span>
|
|
Expand
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative">
|
|
{isContentExpanded ? (
|
|
<RichTextContent content={wiki.content} />
|
|
) : (
|
|
<>
|
|
<div className="max-h-[200px] overflow-hidden relative">
|
|
<RichTextContent content={wiki.content} />
|
|
</div>
|
|
<div className="h-24 bg-gradient-to-t from-gray-800 to-transparent absolute bottom-0 left-0 right-0 pointer-events-none -mt-20" />
|
|
<div className="mt-4 pt-4">
|
|
<button
|
|
onClick={() => setIsContentExpanded(true)}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg text-sm font-medium transition-colors"
|
|
>
|
|
Read More
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tags */}
|
|
{wiki.tags && wiki.tags.length > 0 && (
|
|
<div className="mb-6 pb-6 border-b border-gray-700">
|
|
<h2 className="text-lg font-semibold text-white mb-3">Tags</h2>
|
|
<div className="flex flex-wrap gap-2">
|
|
{wiki.tags.map((tag, index) => (
|
|
<span
|
|
key={index}
|
|
className="px-3 py-1 bg-blue-600 text-white text-sm rounded-full"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Metadata */}
|
|
<div className="pt-6 border-t border-gray-700">
|
|
<h2 className="text-lg font-semibold text-white mb-3">Metadata</h2>
|
|
<div className="space-y-2 text-sm">
|
|
{wiki.category && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-gray-400 w-24">Category:</span>
|
|
<span className="px-2 py-1 bg-gray-700 text-gray-300 rounded text-sm">
|
|
{wiki.category}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{wiki.created_by && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-gray-400 w-24">Created By:</span>
|
|
<span className="text-gray-300">User #{wiki.created_by}</span>
|
|
</div>
|
|
)}
|
|
{wiki.updated_by && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-gray-400 w-24">Updated By:</span>
|
|
<span className="text-gray-300">User #{wiki.updated_by}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-gray-400 w-24">Created:</span>
|
|
<span className="text-gray-300">
|
|
{wiki.created_at ? new Date(wiki.created_at).toLocaleString() : 'N/A'}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-gray-400 w-24">Updated:</span>
|
|
<span className="text-gray-300">
|
|
{wiki.updated_at ? new Date(wiki.updated_at).toLocaleString() : 'N/A'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Linked Entities Section */}
|
|
<div className="pt-6 border-t border-gray-700 mt-6">
|
|
<h2 className="text-lg font-semibold text-white mb-3">Linked Entities</h2>
|
|
|
|
{/* Linked Cards */}
|
|
{wiki.linked_cards && wiki.linked_cards.length > 0 && (
|
|
<div className="mb-6">
|
|
<h3 className="text-md font-medium text-gray-300 mb-3">Linked Cards</h3>
|
|
<div className="space-y-2">
|
|
{wiki.linked_cards.map((card) => (
|
|
<Link
|
|
key={card.id}
|
|
to={`/boards/${boardId}/cards/${card.id}`}
|
|
className="block bg-gray-700 hover:bg-gray-600 border border-gray-600 hover:border-gray-500 rounded-lg p-3 transition-colors"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex-1">
|
|
<h4 className="text-white font-medium">{card.name}</h4>
|
|
{card.description && (
|
|
<p className="text-gray-400 text-sm mt-1 line-clamp-2">
|
|
{card.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<span className="w-4 h-4 text-gray-400">
|
|
<ChevronRightIcon />
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Linked Epics */}
|
|
{wiki.linked_epics && wiki.linked_epics.length > 0 && (
|
|
<div className="mb-6">
|
|
<h3 className="text-md font-medium text-gray-300 mb-3">Linked Epics</h3>
|
|
<div className="space-y-2">
|
|
{wiki.linked_epics.map((epic) => (
|
|
<Link
|
|
key={epic.id}
|
|
to={`/boards/${boardId}/epics/${epic.id}`}
|
|
className="block bg-gray-700 hover:bg-gray-600 border border-gray-600 hover:border-gray-500 rounded-lg p-3 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
{epic.color && (
|
|
<div
|
|
className="w-4 h-4 rounded-full flex-shrink-0"
|
|
style={{ backgroundColor: epic.color }}
|
|
/>
|
|
)}
|
|
<div className="flex-1">
|
|
<h4 className="text-white font-medium">{epic.name}</h4>
|
|
{epic.description && (
|
|
<p className="text-gray-400 text-sm mt-1 line-clamp-2">
|
|
{epic.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
{epic.metrics && (
|
|
<div className="text-gray-400 text-sm flex-shrink-0">
|
|
{epic.metrics.card_count !== undefined && (
|
|
<span>{epic.metrics.card_count} cards</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* No linked entities */}
|
|
{(!wiki.linked_cards || wiki.linked_cards.length === 0) &&
|
|
(!wiki.linked_epics || wiki.linked_epics.length === 0) && (
|
|
<div className="text-center py-8 text-gray-400">
|
|
<div className="text-4xl mb-2">🔗</div>
|
|
<p>This wiki has no linked entities yet</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</WidePageLayout>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default WikiDetail;
|