import React, { useState, useEffect, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { OnboardingTour } from '../onboarding/OnboardingTour'; import { ArrowLeft, Download, Clock, Building, CheckCircle, ShieldAlert, Activity, PanelLeftClose, PanelLeftOpen, Sparkles, Database, TrendingUp } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import SectionList from './SectionList'; import SectionEditor from './SectionEditor'; import LivePreview from './LivePreview'; import ProjectQAPanel from './ProjectQAPanel'; import ProjectChatPanel from './ProjectChatPanel'; import FinalDocumentPanel from './FinalDocumentPanel'; import ProjectResourcesPanel from './ProjectResourcesPanel'; import ProjectAuditPanel from './ProjectAuditPanel'; import AIGeneratorPanel from './AIGeneratorPanel'; import { ExportModal } from './ExportModal'; import DocumentUploadPanel from './DocumentUploadPanel'; import MatchingGrantsWidget from './MatchingGrantsWidget'; import { getProjectSections } from '../../api/client'; interface WorkspaceProps { project: any; statusLabel: string; onRefresh: () => void; } const ProjectWorkspace: React.FC = ({ project, statusLabel, onRefresh }) => { const navigate = useNavigate(); // Zmienne stanu logiki roboczej const [activeMainTab, setActiveMainTab] = useState('overview'); // overview, sections, final, verify, audit const previousTabRef = useRef('overview'); const [activeSectionId, setActiveSectionId] = useState(null); const [sectionsData, setSectionsData] = useState([]); const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [isExportModalOpen, setIsExportModalOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isSidebarOpen, setIsSidebarOpen] = useState(true); useEffect(() => { if (activeMainTab !== 'generator') { previousTabRef.current = activeMainTab; } }, [activeMainTab]); const loadSections = async () => { try { setIsLoading(true); const data = await getProjectSections(project.id); setSectionsData(data); if (data.length > 0) { setActiveSectionId(prev => { if (prev && data.find((s: any) => s.section_type === prev)) return prev; return data[0].section_type; }); } } catch (err) { console.error("Failed to load sections", err); } finally { setIsLoading(false); } }; useEffect(() => { if(project.id) loadSections(); const handleRefresh = () => loadSections(); window.addEventListener('refresh-sections', handleRefresh); return () => { window.removeEventListener('refresh-sections', handleRefresh); }; }, [project.id]); useEffect(() => { let timeoutId: ReturnType; if (!isLoading && sectionsData.length > 0) { const hasSeenTourWorkspace = localStorage.getItem('has_seen_tour_workspace'); if (!hasSeenTourWorkspace) { timeoutId = setTimeout(() => setRunTour(true), 1000); } } return () => { if (timeoutId) clearTimeout(timeoutId); }; }, [isLoading, sectionsData.length]); useEffect(() => { // Pozwala zakładce audytu nawigować do konkretnej sekcji const handleNavigateToSection = (e: Event) => { const custom = e as CustomEvent; if (custom.detail?.sectionType) { const target = custom.detail.sectionType; const targetClean = target ? target.trim().toLowerCase() : ""; // Szukamy pasującej sekcji w załadowanych danych przy użyciu systemu punktacji let bestMatch = null; let highestScore = 0; sectionsData.forEach(s => { if (!s || !targetClean) return; const st = s.section_type?.toLowerCase() || ""; const stClean = s.section_type?.replace(/_/g, ' ').toLowerCase() || ""; const title = s.title?.trim().toLowerCase() || ""; let score = 0; if (st === targetClean || title === targetClean) { score = 100; } else if (stClean === targetClean) { score = 90; } else if ((st && targetClean.includes(st)) || (stClean && targetClean.includes(stClean))) { score = 80; } else if (title && targetClean && title.includes(targetClean)) { score = 70; } else if (targetClean && title && targetClean.includes(title)) { score = title.length >= 4 ? 60 : 0; } else if (stClean && targetClean && stClean.includes(targetClean)) { score = 50; } else if (targetClean && stClean && targetClean.includes(stClean)) { score = stClean.length >= 4 ? 40 : 0; } else if (targetClean && title) { const targetWords = targetClean.split(/\s+/).filter((w: string) => w.length >= 4); for (const w of targetWords) { if (title.includes(w) || (stClean && stClean.includes(w))) { score = 30; break; } } } if (score > highestScore) { highestScore = score; bestMatch = s; } }); if (bestMatch) { setActiveSectionId(bestMatch.section_type); setActiveMainTab('sections'); } else { // Fallback jeśli nie znaleziono import('react-hot-toast').then(toast => { toast.default.error(`Nie znaleziono pasującej sekcji: ${target}`); }); } } }; const handleSwitchToSections = () => { setActiveMainTab('sections'); }; window.addEventListener('navigate-to-section', handleNavigateToSection); window.addEventListener('switch-to-sections-tab', handleSwitchToSections); return () => { window.removeEventListener('navigate-to-section', handleNavigateToSection); window.removeEventListener('switch-to-sections-tab', handleSwitchToSections); }; }, [sectionsData]); const [runTour, setRunTour] = useState(false); const workspaceTourSteps = [ { target: '.tour-step-sections', content: 'Gdy utworzysz projekt, z lewej masz listę sekcji potrzebną do wniosku. Kliknij pierwszą z góry by uruchomić edytor.', placement: 'right' as const, disableBeacon: true }, { target: '.tour-step-qa', content: 'Nie wiesz jak wypełnić trudną sekcję? Ten asystent zasilany danymi Regulaminu i Wtyczką RAG rozwiąże każdy dylemat.', placement: 'left' as const } ]; const currentDbSection = sectionsData.find(s => s.section_type === activeSectionId); const activeSectionTitle = currentDbSection?.title || 'Brak tytułu'; const completedList = sectionsData.filter(s => s.is_approved).map(s => s.section_type); const isFullyApproved = sectionsData.length > 0 && sectionsData.every(s => s.is_approved && s.content && s.content.trim() !== ""); const hasUnapproved = sectionsData.some(s => !s.is_approved && s.content && s.content.length > 50); const renderMainTabContent = () => { switch(activeMainTab) { case 'overview': return (

Podsumowanie projektu

Metadane klienta i wniosku

Nazwa firmy / Opis{project.external_context?.company_data?.name || 'Brak wpisanego opisu firmy'}
NIP Firmy{project.external_context?.company_data?.nip || 'Brak NIP'}
Data utworzenia projektu{new Date(project.created_at).toLocaleDateString('pl-PL')}
Program dotacyjny{project.program_name || 'Brak wybranego programu'}
Maksymalna kwota dofinansowania{project.external_context?.grant_amount ? project.external_context.grant_amount : (project.grant_amount_max ? `${project.grant_amount_max} PLN` : 'Nie określono')}
Status
{statusLabel}

Postęp Wniosku

{sectionsData.filter(s => s.content && s.content.length > 50).length}
Gotowe sekcje dokumentu
z {sectionsData.length} wszystkich sekcji

Weryfikacja

{isFullyApproved ? (

Wszystkie wypełnione sekcje zostały zatwierdzone przez doradcę. Dokument jest spójny.

) : hasUnapproved ? (

Projekt zawiera sekcje bez ostatecznej akceptacji doradcy. Sprawdź je przed wygenerowaniem PDF.

) : (

Rozpocznij wypełnianie wniosku, aby sprawdzić jego poprawność.

)}
{/* Widget: Pasujące nabory */}

Pasujące Nabory

); case 'sections': return (
{isLoading ? (
) : ( {activeSectionId && ( )} )}
); case 'final': return (
); case 'verify': return (

Weryfikator Projektu

Pełne podsumowanie statusu zatwierdzenia przez doradcę na każdym etapie pisania dokumentacji.

{sectionsData.length === 0 &&

Brak sekcji.

}
{sectionsData.map(s => { const hasContent = s.content && s.content.length > 50; return (

{s.title}

{hasContent ? 'Sekcja posiada treść' : 'Brak treści / Wymaga uzupełnienia'}
{s.is_approved ? ( ZATWIERDZONE ) : hasContent ? ( OCZEKUJE NA WERYFIKACJĘ ) : ( PUSTE )}
) })}
); case 'audit': return (
); case 'resources': return (
); case 'generator': return (
{ // Najpierw odśwież dane projektu... onRefresh(); // ...potem przenieś na poprzednią zakładkę setTimeout(() => { loadSections(); setActiveMainTab(previousTabRef.current); }, 800); }} />
); case 'documents': return (
); } }; return (
{/* CONTEXTUAL TOP BAR (STICKY) */}
{/* Górny pasek - nazwa i powrót */}
navigate('/projects')} style={{ width: '36px', height: '36px', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', transition: '0.2s', border: '1px solid rgba(255,255,255,0.1)', flexShrink: 0 }} className="hover-lift">

{project.title}

{(project.external_context?.company_data?.name || project.external_context?.company_data?.nip) && (
{project.external_context?.company_data?.name || `NIP: ${project.external_context?.company_data?.nip}`}
)}
{/* Pasek zakładek */}
{/* DYNAMIC MAIN CONTENT */}
{renderMainTabContent()}
{isPreviewOpen && ( setIsPreviewOpen(false)} /> )} setIsExportModalOpen(false)} projectId={project.id} /> s.section_type === activeSectionId)?.title || activeSectionId : undefined} /> {runTour && ( { setRunTour(false); localStorage.setItem('has_seen_tour_workspace', 'true'); }} /> )}
); }; export default ProjectWorkspace;