Spaces:
Running
Running
| import React, { useState, useEffect, useMemo } from 'react'; | |
| import { motion } from 'framer-motion'; | |
| import { X, Printer, Loader2, GitCommit, FileText } from 'lucide-react'; | |
| import { previewProject } from '../../api/client'; | |
| import ReactMarkdown from 'react-markdown'; | |
| import * as Diff from 'diff'; | |
| interface LivePreviewProps { | |
| projectId: string; | |
| onClose: () => void; | |
| } | |
| const LivePreview: React.FC<LivePreviewProps> = ({ projectId, onClose }) => { | |
| const [markdownData, setMarkdownData] = useState<string>(''); | |
| const [oldMarkdownData, setOldMarkdownData] = useState<string>(''); | |
| const [loading, setLoading] = useState(true); | |
| const [viewMode, setViewMode] = useState<'current' | 'diff'>('current'); | |
| useEffect(() => { | |
| previewProject(projectId).then(res => { | |
| setMarkdownData(res.markdown || ''); | |
| setOldMarkdownData(res.old_markdown || ''); | |
| setLoading(false); | |
| }).catch(() => setLoading(false)); | |
| }, [projectId]); | |
| const handlePrint = () => { | |
| window.print(); | |
| }; | |
| const renderDiff = () => { | |
| if (!oldMarkdownData) return <ReactMarkdown>{markdownData}</ReactMarkdown>; | |
| const diffResult = Diff.diffWordsWithSpace(oldMarkdownData, markdownData); | |
| return ( | |
| <div style={{ whiteSpace: 'pre-wrap' }}> | |
| {diffResult.map((part, index) => { | |
| if (part.added) { | |
| return <span key={index} style={{ backgroundColor: '#dcfce7', color: '#166534', textDecoration: 'none' }}>{part.value}</span>; | |
| } | |
| if (part.removed) { | |
| return <del key={index} style={{ backgroundColor: '#fee2e2', color: '#991b1b', textDecoration: 'line-through' }}>{part.value}</del>; | |
| } | |
| return <span key={index}>{part.value}</span>; | |
| })} | |
| </div> | |
| ); | |
| }; | |
| return ( | |
| <div style={{ position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }}> | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95, y: 20 }} | |
| animate={{ opacity: 1, scale: 1, y: 0 }} | |
| style={{ width: '100%', maxWidth: '900px', height: '100%', background: 'var(--bg-secondary)', borderRadius: '16px', display: 'flex', flexDirection: 'column', overflow: 'hidden', border: '1px solid rgba(255,255,255,0.1)', boxShadow: '0 20px 40px rgba(0,0,0,0.3)' }} | |
| > | |
| {/* Header */} | |
| <div style={{ padding: '1.5rem 2rem', background: 'rgba(0,0,0,0.3)', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <div style={{ display: 'flex', gap: '2rem', alignItems: 'center' }}> | |
| <h2 style={{ fontSize: '1.25rem', margin: 0, color: 'var(--text-primary)' }}>Podgląd Głównego Dokumentu</h2> | |
| <div style={{ display: 'flex', background: 'rgba(0,0,0,0.5)', padding: '0.25rem', borderRadius: '8px' }} className="hide-on-print"> | |
| <button | |
| onClick={() => setViewMode('current')} | |
| style={{ padding: '0.4rem 0.8rem', background: viewMode === 'current' ? 'rgba(59, 130, 246, 0.2)' : 'transparent', color: viewMode === 'current' ? '#60A5FA' : 'var(--text-secondary)', border: 'none', borderRadius: '6px', fontSize: '0.85rem', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.4rem', transition: '0.2s' }} | |
| > | |
| <FileText size={14} /> Aktualna wersja | |
| </button> | |
| <button | |
| onClick={() => setViewMode('diff')} | |
| style={{ padding: '0.4rem 0.8rem', background: viewMode === 'diff' ? 'rgba(16, 185, 129, 0.2)' : 'transparent', color: viewMode === 'diff' ? 'var(--accent-green)' : 'var(--text-secondary)', border: 'none', borderRadius: '6px', fontSize: '0.85rem', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.4rem', transition: '0.2s' }} | |
| > | |
| <GitCommit size={14} /> Śledzenie zmian | |
| </button> | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', gap: '1rem' }} className="hide-on-print"> | |
| <button onClick={handlePrint} className="btn hover-lift" style={{ background: 'rgba(16, 185, 129, 0.1)', color: 'var(--accent-green)', border: '1px solid rgba(16, 185, 129, 0.3)', display: 'flex', alignItems: 'center', gap: '0.5rem' }}> | |
| <Printer size={16}/> Eksportuj / Drukuj | |
| </button> | |
| <button onClick={onClose} className="btn hover-lift" style={{ width: '40px', height: '40px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(239, 68, 68, 0.15)', color: '#EF4444', border: '1px solid rgba(239, 68, 68, 0.3)', borderRadius: '8px' }}> | |
| <X size={20}/> | |
| </button> | |
| </div> | |
| </div> | |
| {/* Content */} | |
| <div style={{ flex: 1, overflowY: 'auto', padding: '3rem 4rem', background: '#e5e7eb', color: '#111827' }} className="print-area"> | |
| {loading ? ( | |
| <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#6b7280' }}> | |
| <Loader2 size={40} className="spin" style={{ marginBottom: '1rem', color: 'var(--accent-green)' }}/> | |
| Generowanie pełnego dokumentu wniosku... | |
| </div> | |
| ) : ( | |
| <div style={{ maxWidth: '800px', margin: '0 auto', fontSize: '14pt', lineHeight: 1.6, fontFamily: 'serif' }} className="markdown-prose"> | |
| {markdownData ? ( | |
| viewMode === 'current' ? <ReactMarkdown>{markdownData}</ReactMarkdown> : renderDiff() | |
| ) : ( | |
| <p style={{ textAlign: 'center', color: '#6b7280' }}>Brak połączonych sekcji wniosku. Najpierw wygeneruj sekcje by uzyskać całościowy zarys.</p> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </motion.div> | |
| <style>{` | |
| @media print { | |
| body * { visibility: hidden; } | |
| .print-area, .print-area * { visibility: visible; } | |
| .print-area { position: absolute; left: 0; top: 0; width: 100vw; height: auto; background: white ; color: black ; padding: 0 ; } | |
| .hide-on-print { display: none ; } | |
| } | |
| .markdown-prose h1 { font-size: 24pt; margin-bottom: 2rem; border-bottom: 2px solid #ccc; padding-bottom: 0.5rem; } | |
| .markdown-prose h2 { font-size: 18pt; margin-top: 2rem; margin-bottom: 1rem; color: #1f2937; } | |
| .markdown-prose h3 { font-size: 14pt; margin-top: 1.5rem; margin-bottom: 0.5rem; color: #374151; } | |
| .markdown-prose p { margin-bottom: 1rem; } | |
| .markdown-prose ul { margin-left: 2rem; margin-bottom: 1rem; } | |
| .spin { animation: spin 1s linear infinite; } | |
| `}</style> | |
| </div> | |
| ); | |
| }; | |
| export default LivePreview; | |