fix(erp): enable erp pages and menu items
This commit is contained in:
225
front-end-agency/components/documentos/DocumentSidebar.tsx
Normal file
225
front-end-agency/components/documentos/DocumentSidebar.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Document, DocumentActivity, docApi } from '@/lib/api-docs';
|
||||
import {
|
||||
ClockIcon,
|
||||
DocumentIcon,
|
||||
PlusIcon,
|
||||
MagnifyingGlassIcon,
|
||||
ChevronRightIcon,
|
||||
HomeIcon,
|
||||
ArrowUpIcon,
|
||||
FolderIcon,
|
||||
FolderOpenIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
interface DocumentSidebarProps {
|
||||
documentId: string;
|
||||
onNavigate: (doc: Document) => void;
|
||||
}
|
||||
|
||||
export default function DocumentSidebar({ documentId, onNavigate }: DocumentSidebarProps) {
|
||||
const [currentDoc, setCurrentDoc] = useState<Document | null>(null);
|
||||
const [children, setChildren] = useState<Document[]>([]);
|
||||
const [parentDoc, setParentDoc] = useState<Document | null>(null);
|
||||
const [rootPages, setRootPages] = useState<Document[]>([]);
|
||||
const [activities, setActivities] = useState<DocumentActivity[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<'subpages' | 'activities'>('subpages');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentId) {
|
||||
fetchData();
|
||||
}
|
||||
}, [documentId]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
console.log('🔍 Sidebar fetching data for:', documentId);
|
||||
|
||||
const fetchSafely = async (promise: Promise<any>) => {
|
||||
try { return await promise; }
|
||||
catch (e) { console.warn('Sidebar part failed:', e); return null; }
|
||||
};
|
||||
|
||||
try {
|
||||
const [doc, roots, logs, subpages] = await Promise.all([
|
||||
docApi.getDocument(documentId),
|
||||
fetchSafely(docApi.getDocuments()),
|
||||
fetchSafely(docApi.getActivities(documentId)),
|
||||
fetchSafely(docApi.getSubpages(documentId))
|
||||
]);
|
||||
|
||||
if (doc) {
|
||||
setCurrentDoc(doc);
|
||||
setChildren(subpages || []);
|
||||
if (doc.parent_id) {
|
||||
const parent = await fetchSafely(docApi.getDocument(doc.parent_id));
|
||||
setParentDoc(parent);
|
||||
} else {
|
||||
setParentDoc(null);
|
||||
}
|
||||
}
|
||||
|
||||
setRootPages(roots || []);
|
||||
setActivities(logs || []);
|
||||
} catch (e) {
|
||||
console.error('❌ Sidebar critical error:', e);
|
||||
toast.error('Erro ao carregar estrutura');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateSubpage = async () => {
|
||||
try {
|
||||
const newDoc = await docApi.createDocument({
|
||||
title: 'Nova Subpágina',
|
||||
parent_id: documentId as any,
|
||||
content: '{"type":"doc","content":[{"type":"paragraph"}]}',
|
||||
status: 'published'
|
||||
});
|
||||
toast.success('Página criada!');
|
||||
// Refresh local para aparecer imediatamente
|
||||
setChildren(prev => [...prev, newDoc]);
|
||||
onNavigate(newDoc);
|
||||
} catch (e) {
|
||||
toast.error('Erro ao criar subpágina');
|
||||
}
|
||||
};
|
||||
|
||||
const filteredChildren = children.filter(p => p.title.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
const rootWikis = rootPages.filter(p => !p.parent_id);
|
||||
|
||||
return (
|
||||
<div className="w-72 border-r border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 flex flex-col h-full overflow-hidden shrink-0">
|
||||
{/* Tabs de Navegação */}
|
||||
<div className="flex border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950">
|
||||
<button
|
||||
onClick={() => setActiveTab('subpages')}
|
||||
className={`flex-1 px-4 py-4 text-[10px] font-black uppercase tracking-widest transition-all ${activeTab === 'subpages' ? 'text-brand-500 bg-brand-50/10 border-b-2 border-brand-500' : 'text-zinc-500'}`}
|
||||
>
|
||||
Estrutura
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('activities')}
|
||||
className={`flex-1 px-4 py-4 text-[10px] font-black uppercase tracking-widest transition-all ${activeTab === 'activities' ? 'text-brand-500 bg-brand-50/10 border-b-2 border-brand-500' : 'text-zinc-500'}`}
|
||||
>
|
||||
Histórico
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-6">
|
||||
{activeTab === 'subpages' ? (
|
||||
<>
|
||||
{/* Status do Nível Atual */}
|
||||
<div className="space-y-3">
|
||||
{parentDoc && (
|
||||
<button
|
||||
onClick={() => onNavigate(parentDoc)}
|
||||
className="w-full flex items-center gap-2 p-2 px-3 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-900 text-zinc-400 hover:text-brand-500 transition-all group border border-dashed border-zinc-200 dark:border-zinc-800"
|
||||
>
|
||||
<ArrowUpIcon className="w-3 h-3 transition-transform group-hover:-translate-y-0.5" />
|
||||
<span className="text-[9px] font-black uppercase tracking-widest truncate">Pai: {parentDoc.title}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="p-3 rounded-xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-sm flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-brand-50 dark:bg-brand-500/10 flex items-center justify-center text-brand-500">
|
||||
<FolderOpenIcon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[10px] font-black text-zinc-400 uppercase tracking-tighter">Você está em</p>
|
||||
<p className="text-xs font-bold text-zinc-900 dark:text-white truncate">{currentDoc?.title || 'Sem título'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<MagnifyingGlassIcon className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 transition-colors ${loading ? 'text-brand-500 animate-pulse' : 'text-zinc-400'}`} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar subpáginas..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl pl-10 pr-4 py-2 text-xs outline-none focus:ring-2 ring-brand-500/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Subpages (Children) */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between px-2 mb-2">
|
||||
<p className="text-[10px] font-black text-zinc-400 uppercase tracking-widest">Subpáginas ({children.length})</p>
|
||||
<button
|
||||
onClick={handleCreateSubpage}
|
||||
className="p-1 text-zinc-400 hover:text-brand-500 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-md shadow-sm transition-all"
|
||||
>
|
||||
<PlusIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{filteredChildren.length === 0 && (
|
||||
<div className="text-center py-6 border border-dashed border-zinc-200 dark:border-zinc-800 rounded-xl">
|
||||
<p className="text-[10px] text-zinc-400 uppercase font-black">Nenhuma subpágina</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredChildren.map(page => (
|
||||
<button
|
||||
key={page.id}
|
||||
onClick={() => onNavigate(page)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all group border border-transparent hover:border-zinc-100 dark:hover:border-zinc-800 ${documentId === page.id ? 'bg-brand-500 text-white shadow-lg' : 'hover:bg-white dark:hover:bg-zinc-900 text-zinc-600 dark:text-zinc-400'}`}
|
||||
>
|
||||
<DocumentIcon className={`w-4 h-4 ${documentId === page.id ? 'text-white' : 'text-zinc-300 group-hover:text-brand-500'}`} />
|
||||
<span className="text-xs font-bold truncate flex-1 text-left">{page.title || 'Subpágina'}</span>
|
||||
<ChevronRightIcon className={`w-3 h-3 opacity-0 group-hover:opacity-100 ${documentId === page.id ? 'text-white' : ''}`} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Root wikis */}
|
||||
{!searchTerm && (
|
||||
<div className="pt-4 border-t border-zinc-100 dark:border-zinc-800 space-y-1">
|
||||
<p className="px-3 text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-3">Todas as Wikis</p>
|
||||
{rootWikis.map(page => (
|
||||
<button
|
||||
key={page.id}
|
||||
onClick={() => onNavigate(page)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-xl transition-all group ${documentId === page.id ? 'text-brand-500 font-bold' : 'text-zinc-500 hover:bg-white dark:hover:bg-zinc-900'}`}
|
||||
>
|
||||
<HomeIcon className="w-3.5 h-3.5" />
|
||||
<span className="text-[11px] font-semibold truncate flex-1 text-left">{page.title || 'Início'}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{activities.map(log => (
|
||||
<div key={log.id} className="flex gap-4">
|
||||
<div className="w-8 h-8 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-[10px] font-black text-zinc-400 shrink-0">
|
||||
{log.user_name?.[0]?.toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0 pt-0.5">
|
||||
<p className="text-[11px] text-zinc-900 dark:text-zinc-100 leading-tight">
|
||||
<span className="font-bold">{log.user_name}</span> {log.description}
|
||||
</p>
|
||||
<span className="text-[9px] text-zinc-400 uppercase font-black block mt-1">
|
||||
{format(parseISO(log.created_at), "dd MMM, HH:mm", { locale: ptBR })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user