mission-control/app/documents/page.tsx

827 lines
30 KiB
TypeScript

"use client";
import { useState, useMemo, useCallback } from 'react';
import { DashboardLayout } from '@/components/layout/sidebar';
import { PageHeader } from '@/components/layout/page-header';
// SupabaseTest removed - using API route instead
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Textarea } from '@/components/ui/textarea';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
FileText,
Folder,
Search,
Plus,
FileCode,
FileType,
File as FileIcon,
Clock,
Tag,
Edit3,
Trash2,
Eye,
X,
Filter,
Save,
FolderPlus,
Clock3,
Hash,
LayoutGrid,
List,
} from 'lucide-react';
import { MarkdownPreviewDialog } from '@/components/MarkdownPreviewDialog';
import { cn } from '@/lib/utils';
import { useDocuments } from '@/hooks/useDocuments';
import { Document, DocumentType, Folder as FolderType, DOCUMENT_TYPE_COLORS } from '@/types/documents';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
const typeIcons: Record<DocumentType, typeof FileText> = {
markdown: FileType,
text: FileText,
pdf: FileIcon,
code: FileCode,
};
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function formatDate(dateString: string): string {
const date = new Date(dateString);
const now = new Date();
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
if (diffInHours < 1) return 'Just now';
if (diffInHours < 24) return `${Math.floor(diffInHours)}h ago`;
if (diffInHours < 48) return 'Yesterday';
if (diffInHours < 168) return date.toLocaleDateString('en-US', { weekday: 'short' });
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
// Document Editor Component
function DocumentEditor({
document,
onSave,
onCancel,
folders,
}: {
document: Document | null;
onSave: (doc: Partial<Document>) => void;
onCancel: () => void;
folders: FolderType[];
}) {
const [title, setTitle] = useState(document?.title || '');
const [content, setContent] = useState(document?.content || '');
const [folder, setFolder] = useState(document?.folder || 'General');
const [tags, setTags] = useState<string[]>(document?.tags || []);
const [tagInput, setTagInput] = useState('');
const [showPreview, setShowPreview] = useState(false);
const handleSave = () => {
if (!title.trim()) return;
onSave({
title: title.trim(),
content: content.trim(),
folder,
tags,
type: 'markdown',
});
};
const addTag = () => {
const trimmed = tagInput.trim().toLowerCase();
if (trimmed && !tags.includes(trimmed)) {
setTags([...tags, trimmed]);
setTagInput('');
}
};
const removeTag = (tagToRemove: string) => {
setTags(tags.filter((t) => t !== tagToRemove));
};
return (
<div className="flex flex-col h-full">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4">
<div className="flex items-center gap-3 flex-1">
<Input
placeholder="Document title..."
value={title}
onChange={(e) => setTitle(e.target.value)}
className="text-base sm:text-lg font-semibold bg-transparent border-0 border-b rounded-none px-0 focus-visible:ring-0 focus-visible:border-primary"
/>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setShowPreview(!showPreview)}
>
<Eye className="w-4 h-4 mr-2" />
{showPreview ? 'Edit' : 'Preview'}
</Button>
<Button variant="ghost" size="sm" onClick={onCancel}>
<X className="w-4 h-4 mr-2" />
Cancel
</Button>
<Button size="sm" onClick={handleSave} disabled={!title.trim()}>
<Save className="w-4 h-4 mr-2" />
Save
</Button>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 mb-4">
<Select value={folder} onValueChange={setFolder}>
<SelectTrigger className="w-full sm:w-[180px]">
<Folder className="w-4 h-4 mr-2" />
<SelectValue placeholder="Select folder" />
</SelectTrigger>
<SelectContent>
{folders.map((f) => (
<SelectItem key={f.id} value={f.name}>
{f.name}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="flex items-center gap-2 flex-1">
<Hash className="w-4 h-4 text-muted-foreground shrink-0" />
<div className="flex items-center gap-2 flex-wrap flex-1">
{tags.map((tag) => (
<Badge key={tag} variant="secondary" className="gap-1">
{tag}
<button
onClick={() => removeTag(tag)}
className="hover:text-destructive"
>
<X className="w-3 h-3" />
</button>
</Badge>
))}
<div className="flex items-center">
<Input
placeholder="Add tag..."
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addTag();
}
}}
className="w-24 h-7 text-xs"
/>
</div>
</div>
</div>
</div>
<div className="flex-1 min-h-[300px] sm:min-h-[400px]">
{showPreview ? (
<div className="h-full p-3 sm:p-4 border rounded-lg bg-card overflow-auto prose prose-sm dark:prose-invert max-w-none">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content || '*No content*'}</ReactMarkdown>
</div>
) : (
<Textarea
placeholder="Write your document in markdown..."
value={content}
onChange={(e) => setContent(e.target.value)}
className="h-full min-h-[300px] sm:min-h-[400px] resize-none font-mono text-sm"
/>
)}
</div>
</div>
);
}
// Document List Item Component
function DocumentListItem({
document,
onClick,
onEdit,
onDelete,
}: {
document: Document;
onClick: () => void;
onEdit: (e: React.MouseEvent) => void;
onDelete: (e: React.MouseEvent) => void;
}) {
const Icon = typeIcons[document.type];
const colorClass = DOCUMENT_TYPE_COLORS[document.type];
return (
<div
onClick={onClick}
className="flex items-start gap-3 p-3 sm:p-4 hover:bg-accent/50 transition-colors cursor-pointer group"
>
<div className={cn('p-2 rounded-lg shrink-0', colorClass)}>
<Icon className="w-4 h-4 sm:w-5 sm:h-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium text-sm sm:text-base truncate">{document.title}</h4>
<Badge variant="outline" className="text-xs shrink-0">
{document.folder}
</Badge>
</div>
<p className="text-xs sm:text-sm text-muted-foreground truncate">
{document.description || document.content.slice(0, 80).replace(/#/g, '').trim() || 'No description'}
</p>
<div className="flex items-center gap-2 mt-1">
{document.tags.slice(0, 3).map((tag) => (
<span key={tag} className="text-xs text-muted-foreground">
#{tag}
</span>
))}
{document.tags.length > 3 && (
<span className="text-xs text-muted-foreground">
+{document.tags.length - 3}
</span>
)}
</div>
</div>
<div className="hidden sm:block text-right text-xs sm:text-sm text-muted-foreground shrink-0">
<p>{formatFileSize(document.size)}</p>
<p className="text-xs">{formatDate(document.updatedAt)}</p>
</div>
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onEdit}>
<Edit3 className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive" onClick={onDelete}>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
);
}
// Document Viewer Component
function DocumentViewer({
document,
onEdit,
onClose,
}: {
document: Document;
onEdit: () => void;
onClose: () => void;
}) {
const Icon = typeIcons[document.type];
const colorClass = DOCUMENT_TYPE_COLORS[document.type];
return (
<div className="flex flex-col h-full">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-4 border-b">
<div className="flex items-center gap-3">
<div className={cn('p-2 rounded-lg shrink-0', colorClass)}>
<Icon className="w-5 h-5" />
</div>
<div className="min-w-0">
<h2 className="text-base sm:text-lg font-semibold truncate">{document.title}</h2>
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<span>{document.folder}</span>
<span></span>
<span>{formatFileSize(document.size)}</span>
<span></span>
<span>Updated {formatDate(document.updatedAt)}</span>
</div>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<Button variant="outline" size="sm" onClick={onEdit}>
<Edit3 className="w-4 h-4 mr-2" />
Edit
</Button>
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="w-4 h-4" />
</Button>
</div>
</div>
<div className="flex flex-wrap gap-2 py-2">
{document.tags.map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs">
#{tag}
</Badge>
))}
</div>
<div className="flex-1 overflow-auto py-4 px-2 prose prose-base dark:prose-invert max-w-none prose-headings:mt-4 prose-headings:mb-2 prose-p:my-2 prose-ul:my-2 prose-ol:my-2">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{document.content}</ReactMarkdown>
</div>
</div>
);
}
// Main Documents Page
export default function DocumentsPage() {
const {
documents,
folders,
isLoaded,
createDocument,
updateDocument,
deleteDocument,
createFolder,
getRecentDocuments,
getAllTags,
refreshDocuments,
error,
} = useDocuments();
const [searchQuery, setSearchQuery] = useState('');
const [selectedFolder, setSelectedFolder] = useState<string | null>(null);
const [selectedTag, setSelectedTag] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'list' | 'grid'>('list');
const [editingDocument, setEditingDocument] = useState<Document | null>(null);
const [viewingDocument, setViewingDocument] = useState<Document | null>(null);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [isNewFolderDialogOpen, setIsNewFolderDialogOpen] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [activeTab, setActiveTab] = useState('all');
// Filter documents
const filteredDocuments = useMemo(() => {
let filtered = documents;
if (searchQuery) {
const query = searchQuery.toLowerCase();
filtered = filtered.filter(
(doc) =>
doc.title.toLowerCase().includes(query) ||
doc.content.toLowerCase().includes(query) ||
doc.tags.some((tag) => tag.toLowerCase().includes(query))
);
}
if (selectedFolder) {
filtered = filtered.filter((doc) => doc.folder === selectedFolder);
}
if (selectedTag) {
filtered = filtered.filter((doc) => doc.tags.includes(selectedTag));
}
if (activeTab === 'recent') {
filtered = [...filtered].sort(
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
}
return filtered;
}, [documents, searchQuery, selectedFolder, selectedTag, activeTab]);
const recentDocuments = useMemo(() => getRecentDocuments(5), [getRecentDocuments]);
const allTags = useMemo(() => getAllTags(), [getAllTags]);
const handleCreateDocument = async (docData: Partial<Document>) => {
const result = await createDocument({
title: docData.title!,
content: docData.content || '',
type: 'markdown',
folder: docData.folder || 'General',
tags: docData.tags || [],
});
if (result) {
setIsCreateDialogOpen(false);
}
};
const handleUpdateDocument = async (docData: Partial<Document>) => {
if (editingDocument) {
const result = await updateDocument(editingDocument.id, docData);
if (result) {
setEditingDocument(null);
}
}
};
const handleDeleteDocument = async (id: string) => {
if (confirm('Are you sure you want to delete this document?')) {
const success = await deleteDocument(id);
if (success) {
if (viewingDocument?.id === id) setViewingDocument(null);
if (editingDocument?.id === id) setEditingDocument(null);
}
}
};
const handleCreateFolder = () => {
if (newFolderName.trim()) {
createFolder(newFolderName.trim());
setNewFolderName('');
setIsNewFolderDialogOpen(false);
}
};
if (!isLoaded) {
return (
<DashboardLayout>
<div className="flex flex-col items-center justify-center h-96 gap-4">
<div className="animate-pulse text-muted-foreground">Loading documents...</div>
</div>
</DashboardLayout>
);
}
// Show error state with retry option
if (error) {
return (
<DashboardLayout>
<div className="flex flex-col items-center justify-center h-96 gap-4 px-4">
<FileText className="w-12 h-12 text-destructive/50" />
<div className="text-center">
<h3 className="font-medium text-lg">Failed to load documents</h3>
<p className="text-muted-foreground text-sm max-w-md mt-2">
{error}
</p>
</div>
<div className="flex gap-2">
<Button onClick={() => window.location.reload()} variant="outline">
Retry
</Button>
</div>
</div>
</DashboardLayout>
);
}
return (
<DashboardLayout>
<div className="space-y-4 sm:space-y-6">
{/* Header */}
<PageHeader
title="Documents"
description="Manage your notes, docs, and files across projects."
>
<Button onClick={() => setIsCreateDialogOpen(true)} size="sm">
<Plus className="w-4 h-4 mr-2" />
New Document
</Button>
</PageHeader>
{/* Recent Documents Carousel */}
{recentDocuments.length > 0 && !searchQuery && !selectedFolder && !selectedTag && (
<div className="space-y-3">
<div className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<Clock3 className="w-4 h-4" />
<span>Recent Documents</span>
</div>
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 sm:mx-0 sm:px-0">
{recentDocuments.map((doc) => {
const Icon = typeIcons[doc.type];
return (
<button
key={doc.id}
onClick={() => setViewingDocument(doc)}
className="flex items-center gap-3 px-4 py-3 bg-card border rounded-lg hover:border-primary/50 transition-colors text-left shrink-0 min-w-[180px] sm:min-w-[200px]"
>
<Icon className="w-5 h-5 text-muted-foreground shrink-0" />
<div className="min-w-0">
<p className="font-medium text-sm truncate">{doc.title}</p>
<p className="text-xs text-muted-foreground">{formatDate(doc.updatedAt)}</p>
</div>
</button>
);
})}
</div>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4 sm:gap-6">
{/* Sidebar */}
<div className="lg:col-span-1 space-y-4">
{/* Folders */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-sm sm:text-base">Folders</CardTitle>
<Dialog open={isNewFolderDialogOpen} onOpenChange={setIsNewFolderDialogOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6">
<FolderPlus className="w-4 h-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>New Folder</DialogTitle>
<DialogDescription>
Create a new folder to organize your documents.
</DialogDescription>
</DialogHeader>
<Input
placeholder="Folder name..."
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreateFolder()}
/>
<DialogFooter>
<Button variant="outline" onClick={() => setIsNewFolderDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleCreateFolder} disabled={!newFolderName.trim()}>
Create
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</CardHeader>
<CardContent className="space-y-1">
<button
onClick={() => setSelectedFolder(null)}
className={cn(
'w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors',
selectedFolder === null
? 'bg-primary text-primary-foreground'
: 'hover:bg-accent'
)}
>
<span className="flex items-center gap-2">
<Folder className="w-4 h-4" />
All Documents
</span>
<span className="text-xs opacity-70">{documents.length}</span>
</button>
{folders.map((folder) => (
<button
key={folder.id}
onClick={() => setSelectedFolder(folder.name)}
className={cn(
'w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors',
selectedFolder === folder.name
? 'bg-primary text-primary-foreground'
: 'hover:bg-accent'
)}
>
<span className="flex items-center gap-2">
<Folder className="w-4 h-4" />
{folder.name}
</span>
<span className="text-xs opacity-70">{folder.count}</span>
</button>
))}
</CardContent>
</Card>
{/* Tags */}
{allTags.length > 0 && (
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm sm:text-base flex items-center gap-2">
<Tag className="w-4 h-4" />
Tags
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{allTags.map((tag) => (
<button
key={tag}
onClick={() => setSelectedTag(selectedTag === tag ? null : tag)}
className={cn(
'px-2 py-1 rounded-md text-xs transition-colors',
selectedTag === tag
? 'bg-primary text-primary-foreground'
: 'bg-secondary hover:bg-secondary/80'
)}
>
#{tag}
</button>
))}
</div>
</CardContent>
</Card>
)}
{/* Storage */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm sm:text-base">Storage</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between text-xs sm:text-sm">
<span className="text-muted-foreground">
{formatFileSize(documents.reduce((acc, d) => acc + d.size, 0))} used
</span>
<span>{documents.length} docs</span>
</div>
<div className="h-2 bg-secondary rounded-full overflow-hidden">
<div className="h-full w-[25%] bg-gradient-to-r from-blue-500 to-purple-500 rounded-full" />
</div>
<p className="text-xs text-muted-foreground">
Stored locally in your browser
</p>
</div>
</CardContent>
</Card>
</div>
{/* Main Content */}
<div className="lg:col-span-3 space-y-4">
{/* Toolbar */}
<div className="flex flex-col sm:flex-row gap-3">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search documents..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<div className="flex items-center gap-2">
{(selectedFolder || selectedTag) && (
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedFolder(null);
setSelectedTag(null);
}}
>
<Filter className="w-4 h-4 mr-2" />
Clear
<X className="w-3 h-3 ml-2" />
</Button>
)}
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'list' | 'grid')}>
<TabsList className="h-9">
<TabsTrigger value="list" className="px-3">
<List className="w-4 h-4" />
</TabsTrigger>
<TabsTrigger value="grid" className="px-3">
<LayoutGrid className="w-4 h-4" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
</div>
{/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="recent">Recent</TabsTrigger>
</TabsList>
</Tabs>
{/* Document List */}
<Card>
<CardContent className="p-0">
{filteredDocuments.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<FileText className="w-10 h-10 sm:w-12 sm:h-12 text-muted-foreground/50 mb-4" />
<h3 className="font-medium text-base sm:text-lg">No documents found</h3>
<p className="text-muted-foreground text-xs sm:text-sm max-w-xs mt-1">
{searchQuery || selectedFolder || selectedTag
? 'Try adjusting your filters or search query'
: 'Create your first document to get started'}
</p>
{!searchQuery && !selectedFolder && !selectedTag && (
<Button className="mt-4" onClick={() => setIsCreateDialogOpen(true)} size="sm">
<Plus className="w-4 h-4 mr-2" />
Create Document
</Button>
)}
</div>
) : viewMode === 'list' ? (
<div className="divide-y divide-border">
{filteredDocuments.map((doc) => (
<DocumentListItem
key={doc.id}
document={doc}
onClick={() => setViewingDocument(doc)}
onEdit={(e) => {
e.stopPropagation();
setEditingDocument(doc);
}}
onDelete={(e) => {
e.stopPropagation();
handleDeleteDocument(doc.id);
}}
/>
))}
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-3 sm:p-4">
{filteredDocuments.map((doc) => {
const Icon = typeIcons[doc.type];
const colorClass = DOCUMENT_TYPE_COLORS[doc.type];
return (
<button
key={doc.id}
onClick={() => setViewingDocument(doc)}
className="flex flex-col p-3 sm:p-4 border rounded-lg hover:border-primary/50 transition-colors text-left"
>
<div className="flex items-start justify-between mb-3">
<div className={cn('p-2 rounded-lg', colorClass)}>
<Icon className="w-4 h-4 sm:w-5 sm:h-5" />
</div>
<Badge variant="outline" className="text-xs">
{doc.folder}
</Badge>
</div>
<h4 className="font-medium text-sm truncate mb-1">{doc.title}</h4>
<p className="text-xs sm:text-sm text-muted-foreground line-clamp-2 mb-3">
{doc.description || doc.content.slice(0, 80).replace(/#/g, '').trim() || 'No description'}
</p>
<div className="flex items-center justify-between text-xs text-muted-foreground mt-auto">
<span>{formatDate(doc.updatedAt)}</span>
<span>{formatFileSize(doc.size)}</span>
</div>
{doc.tags.length > 0 && (
<div className="flex gap-1 mt-2 flex-wrap">
{doc.tags.slice(0, 2).map((tag) => (
<span key={tag} className="text-xs text-muted-foreground">
#{tag}
</span>
))}
{doc.tags.length > 2 && (
<span className="text-xs text-muted-foreground">
+{doc.tags.length - 2}
</span>
)}
</div>
)}
</button>
);
})}
</div>
)}
</CardContent>
</Card>
</div>
</div>
{/* Create Document Dialog */}
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent className="max-w-4xl h-[90vh] sm:h-[80vh] p-0">
<div className="p-4 sm:p-6 h-full overflow-hidden">
<DocumentEditor
document={null}
onSave={handleCreateDocument}
onCancel={() => setIsCreateDialogOpen(false)}
folders={folders}
/>
</div>
</DialogContent>
</Dialog>
{/* Edit Document Dialog */}
<Dialog open={!!editingDocument} onOpenChange={() => setEditingDocument(null)}>
<DialogContent className="max-w-4xl h-[90vh] sm:h-[80vh] p-0">
<div className="p-4 sm:p-6 h-full overflow-hidden">
{editingDocument && (
<DocumentEditor
document={editingDocument}
onSave={handleUpdateDocument}
onCancel={() => setEditingDocument(null)}
folders={folders}
/>
)}
</div>
</DialogContent>
</Dialog>
{/* View Document Dialog */}
<MarkdownPreviewDialog
isOpen={!!viewingDocument}
onClose={() => setViewingDocument(null)}
title={viewingDocument?.title || ''}
content={viewingDocument?.content || ''}
folder={viewingDocument?.folder}
tags={viewingDocument?.tags}
/>
</div>
</DashboardLayout>
);
}