fix(erp): enable erp pages and menu items
This commit is contained in:
199
front-end-agency/components/documentos/DocumentEditor.tsx
Normal file
199
front-end-agency/components/documentos/DocumentEditor.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Document, docApi } from '@/lib/api-docs';
|
||||
import {
|
||||
XMarkIcon,
|
||||
CheckIcon,
|
||||
ArrowLeftIcon,
|
||||
Bars3BottomLeftIcon,
|
||||
HashtagIcon,
|
||||
CloudArrowUpIcon,
|
||||
SparklesIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
import NotionEditor from './NotionEditor';
|
||||
import DocumentSidebar from './DocumentSidebar';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
interface DocumentEditorProps {
|
||||
initialDocument: Partial<Document> | null;
|
||||
onSave: (doc: Partial<Document>) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function DocumentEditor({ initialDocument, onSave, onCancel }: DocumentEditorProps) {
|
||||
const [document, setDocument] = useState<Partial<Document> | null>(initialDocument);
|
||||
const [title, setTitle] = useState(initialDocument?.title || '');
|
||||
const [content, setContent] = useState(initialDocument?.content || '[]');
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// Refs para controle fino de salvamento
|
||||
const saveTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const lastSaved = useRef({ title: initialDocument?.title || '', content: initialDocument?.content || '[]' });
|
||||
|
||||
useEffect(() => {
|
||||
if (initialDocument) {
|
||||
setDocument(initialDocument);
|
||||
setTitle(initialDocument.title || '');
|
||||
setContent(initialDocument.content || '[]');
|
||||
lastSaved.current = {
|
||||
title: initialDocument.title || '',
|
||||
content: initialDocument.content || '[]'
|
||||
};
|
||||
}
|
||||
}, [initialDocument]);
|
||||
|
||||
// Função de Auto-Save Robusta
|
||||
const autoSave = useCallback(async (newTitle: string, newContent: string) => {
|
||||
if (!document?.id) return;
|
||||
|
||||
setSaving(true);
|
||||
console.log('💾 Inactivity detected. Saving document...', document.id);
|
||||
|
||||
try {
|
||||
await docApi.updateDocument(document.id, {
|
||||
title: newTitle,
|
||||
content: newContent,
|
||||
status: 'published'
|
||||
});
|
||||
// Atualiza o ref do último salvo para evitar loop
|
||||
lastSaved.current = { title: newTitle, content: newContent };
|
||||
console.log('✅ Document saved successfully');
|
||||
} catch (e) {
|
||||
console.error('❌ Auto-save failed', e);
|
||||
toast.error('Erro ao salvar automaticamente');
|
||||
} finally {
|
||||
// Delay visual para o feedback de "Salvo"
|
||||
setTimeout(() => setSaving(false), 800);
|
||||
}
|
||||
}, [document?.id]);
|
||||
|
||||
// Trigger de auto-save com debounce de 1 segundo
|
||||
useEffect(() => {
|
||||
if (!document?.id) return;
|
||||
|
||||
// Verifica se houve mudança real em relação ao último salvo
|
||||
if (title === lastSaved.current.title && content === lastSaved.current.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (saveTimeout.current) clearTimeout(saveTimeout.current);
|
||||
|
||||
saveTimeout.current = setTimeout(() => {
|
||||
autoSave(title, content);
|
||||
}, 1000); // Salva após 1 segundo de inatividade
|
||||
|
||||
return () => {
|
||||
if (saveTimeout.current) clearTimeout(saveTimeout.current);
|
||||
};
|
||||
}, [title, content, document?.id, autoSave]);
|
||||
|
||||
const navigateToDoc = async (doc: Document) => {
|
||||
// Antes de navegar, salva o atual se necessário
|
||||
if (title !== lastSaved.current.title || content !== lastSaved.current.content) {
|
||||
await autoSave(title, content);
|
||||
}
|
||||
|
||||
setDocument(doc);
|
||||
setTitle(doc.title);
|
||||
setContent(doc.content);
|
||||
lastSaved.current = { title: doc.title, content: doc.content };
|
||||
toast.success(`Abrindo: ${doc.title || 'Untitled'}`, { duration: 1000, position: 'bottom-center' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[60] bg-white dark:bg-zinc-950 flex flex-col">
|
||||
{/* Header Clean */}
|
||||
<header className="h-16 border-b border-zinc-200 dark:border-zinc-800 px-6 flex items-center justify-between bg-white dark:bg-zinc-950 z-20 shrink-0">
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (title !== lastSaved.current.title || content !== lastSaved.current.content) {
|
||||
await autoSave(title, content);
|
||||
}
|
||||
onCancel();
|
||||
}}
|
||||
className="p-2 text-zinc-400 hover:text-zinc-900 dark:hover:text-white rounded-xl hover:bg-zinc-100 dark:hover:bg-zinc-900 transition-all"
|
||||
title="Back to list"
|
||||
>
|
||||
<ArrowLeftIcon className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowSidebar(!showSidebar)}
|
||||
className={`p-2 rounded-xl transition-all ${showSidebar ? 'text-brand-500 bg-brand-50/50 dark:bg-brand-500/10' : 'text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-900'}`}
|
||||
title={showSidebar ? "Hide Navigation" : "Show Navigation"}
|
||||
>
|
||||
<Bars3BottomLeftIcon className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="h-4 w-px bg-zinc-200 dark:bg-zinc-800 mx-2" />
|
||||
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<HashtagIcon className="w-4 h-4 text-zinc-300" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Untitled Document"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="text-lg font-bold bg-transparent border-none outline-none text-zinc-900 dark:text-white placeholder:text-zinc-300 dark:placeholder:text-zinc-600 w-full max-w-xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-zinc-50 dark:bg-zinc-900 border border-zinc-100 dark:border-zinc-800">
|
||||
{saving ? (
|
||||
<div className="flex items-center gap-2 text-brand-500">
|
||||
<div className="w-1.5 h-1.5 bg-brand-500 rounded-full animate-pulse" />
|
||||
<span className="text-[10px] font-black uppercase tracking-widest leading-none">Saving...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 text-emerald-500">
|
||||
<CheckIcon className="w-3.5 h-3.5" />
|
||||
<span className="text-[10px] font-black uppercase tracking-widest text-zinc-500 dark:text-zinc-400 leading-none">Sync Saved</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{/* Lateral Sidebar (Navegação) */}
|
||||
{showSidebar && document?.id && (
|
||||
<DocumentSidebar
|
||||
key={document.id} // Re-render sidebar on doc change to ensure fresh data
|
||||
documentId={document.id}
|
||||
onNavigate={navigateToDoc}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Área do Editor */}
|
||||
<main className="flex-1 overflow-y-auto bg-white dark:bg-zinc-950 flex flex-col items-center custom-scrollbar">
|
||||
<div className="w-full max-w-[850px] px-12 md:px-24 py-16 animate-in slide-in-from-bottom-2 duration-500">
|
||||
{/* Title Hero Display */}
|
||||
<div className="mb-14 group">
|
||||
<h1 className="text-5xl font-black text-zinc-900 dark:text-white tracking-tighter leading-tight">
|
||||
{title || 'Untitled'}
|
||||
</h1>
|
||||
<div className="mt-6 flex items-center gap-4 text-zinc-400">
|
||||
<SparklesIcon className="w-4 h-4 text-brand-500" />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-5 h-5 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-bold text-white uppercase">Me</div>
|
||||
<span className="text-[10px] font-black uppercase tracking-widest">Editing Mode • Real-time Sync</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NotionEditor
|
||||
documentId={document?.id}
|
||||
initialContent={content}
|
||||
onChange={setContent}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user