import React, { useState, useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { Sparkles, Save, Edit3, Loader2, Info } from 'lucide-react'; import { generateProjectSection, updateProjectSection, reviewProjectSection } from '../../api/client'; import toast from 'react-hot-toast'; import CriticFeedbackPanel from './CriticFeedbackPanel'; import SectionVersionsViewer from './SectionVersionsViewer'; import { History } from 'lucide-react'; interface SectionEditorProps { projectId: string; sectionId: string; // The type/id (e.g., 'description', 'budget') sectionTitle: string; initialContent?: string; dbSectionId?: string; // If section is already in DB onSectionUpdated: () => void; } const SectionEditor: React.FC = ({ projectId, sectionId, sectionTitle, initialContent = '', dbSectionId, onSectionUpdated }) => { const [content, setContent] = useState(initialContent); const [isGenerating, setIsGenerating] = useState(false); const [isReviewing, setIsReviewing] = useState(false); // Critic state const [criticResult, setCriticResult] = useState<{isApproved: boolean, feedback: string, severity: 'low'|'medium'|'high'} | null>(null); // Auto-save state const [autoSaveStatus, setAutoSaveStatus] = useState<'idle' | 'saving' | 'saved'>('idle'); const [showVersionsModal, setShowVersionsModal] = useState(false); const textAreaRef = useRef(null); useEffect(() => { const handleInsertSuggestion = (e: any) => { if (!e.detail || !e.detail.text) return; const textToInsert = e.detail.text; const mode = e.detail.mode || 'insert'; setContent(prev => { const textarea = textAreaRef.current; if (!textarea) return prev + '\n\n' + textToInsert; const start = textarea.selectionStart; const end = textarea.selectionEnd; if (mode === 'replace') { if (start !== end) { // Replace selected text return prev.substring(0, start) + textToInsert + prev.substring(end); } else { // Replace entire text return textToInsert; } } else { // Insert at cursor return prev.substring(0, start) + textToInsert + prev.substring(start); } }); // Note: we might want to focus the textarea after insertion but React state update is async. }; window.addEventListener('insert-suggestion', handleInsertSuggestion); return () => window.removeEventListener('insert-suggestion', handleInsertSuggestion); }, []); // Listen for external updates (e.g. from AutoFix or Chat Replace) useEffect(() => { const handleExternalUpdate = (e: any) => { if (e.detail && e.detail.sectionType === sectionId && e.detail.content !== undefined) { setContent(e.detail.content); setCriticResult(null); setAutoSaveStatus('idle'); } }; window.addEventListener('external-section-update', handleExternalUpdate); return () => window.removeEventListener('external-section-update', handleExternalUpdate); }, [sectionId]); const prevInitialContentRef = useRef(initialContent); useEffect(() => { if (initialContent !== prevInitialContentRef.current) { // Update content if it hasn't been locally modified by the user if (content === prevInitialContentRef.current || content === '') { setContent(initialContent); setCriticResult(null); setAutoSaveStatus('idle'); } prevInitialContentRef.current = initialContent; } }, [initialContent, content]); useEffect(() => { setContent(initialContent); prevInitialContentRef.current = initialContent; setCriticResult(null); // Reset when section changes setAutoSaveStatus('idle'); }, [sectionId]); // Only refresh content when changing active section tab to avoid cursor jumping useEffect(() => { // Prevent auto-saving if content hasn't changed or it's not initialized in DB yet if (!dbSectionId || content === initialContent) return; setAutoSaveStatus('saving'); const timeoutId = setTimeout(async () => { try { await updateProjectSection(projectId, dbSectionId, content); setAutoSaveStatus('saved'); // We're omitting onSectionUpdated() here so it doesn't re-render entire List repeatedly } catch (error) { toast.error("Błąd auto-zapisu"); setAutoSaveStatus('idle'); } }, 1500); return () => clearTimeout(timeoutId); }, [content, projectId, dbSectionId, initialContent]); const handleGenerate = async (additionalContext?: string) => { const loadingToast = toast.loading("Trwa automatyczna optymalizacja sekcji… Prosimy o chwilę cierpliwości."); try { setIsGenerating(true); const data = await generateProjectSection(projectId, sectionId, additionalContext || "Odpowiednie technologie i plan badawczy"); setContent(data.content || ''); toast.success("Optymalizacja zakończona sukcesem.", { id: loadingToast }); setCriticResult(null); onSectionUpdated(); } catch (error) { toast.error("Przepraszamy, napotkano problem techniczny. Spróbuj wygenerować sekcję ponownie za chwilę.", { id: loadingToast, duration: 5000 }); } finally { setIsGenerating(false); } }; const handleReview = async () => { if (!content.trim()) { toast.error("Brak treści do recenzji"); return; } const loadingToast = toast.loading("Critic weryfikuje treść..."); try { setIsReviewing(true); const data = await reviewProjectSection(projectId, sectionId, content); setCriticResult({ isApproved: data.is_approved, feedback: data.feedback, severity: data.severity as 'low'|'medium'|'high' }); if(data.is_approved) { toast.success("Sekcja zweryfikowana pozytywnie!", { id: loadingToast }); onSectionUpdated(); // Może backend zmienił status na approved, odswiezmy liste } else { toast.error("Znaleziono elementy do poprawy.", { id: loadingToast }); } } catch (error) { toast.error("Przepraszamy, usługa recenzji jest w tym momencie niedostępna. Spróbuj ponownie później.", { id: loadingToast }); } finally { setIsReviewing(false); } }; return ( {/* TEXT EDITOR AREA */}
{sectionTitle} {(sectionId.toLowerCase().includes('budget') || sectionTitle.toLowerCase().includes('budżet')) && (
Wskazówka: Koszty Kwalifikowalne

Maksymalny limit na wydatki promocyjne i cateringowe zależy od konkursu (np. przy Ścieżce SMART: "Catering dla zespołu – tak, ale max 2% wartości kosztów ogólnych").

)}
{dbSectionId && autoSaveStatus !== 'idle' && (
{autoSaveStatus === 'saving' ? : } {autoSaveStatus === 'saving' ? 'Zapisywanie...' : 'Zapisano sekcję'}
)}
{isGenerating ? (

Trwa automatyczna optymalizacja sekcji… Prosimy o chwilę cierpliwości.

) : (