This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/components/education/PDFPreviewModal.tsx
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

208 lines
8.5 KiB
TypeScript

'use client'
/**
* PDF Preview Modal for Abitur Documents
* Shows PDF iframe with metadata sidebar
*/
import { X, Download, ExternalLink, FileText, Calendar, BookOpen, Layers, Search } from 'lucide-react'
import { AbiturDokument, formatFileSize, formatDocumentTitle, FAECHER, NIVEAUS } from '@/lib/education/abitur-docs-types'
interface PDFPreviewModalProps {
document: AbiturDokument | null
onClose: () => void
backendUrl?: string
}
export function PDFPreviewModal({ document, onClose, backendUrl = '' }: PDFPreviewModalProps) {
if (!document) return null
const fachLabel = FAECHER.find(f => f.id === document.fach)?.label || document.fach
const niveauLabel = NIVEAUS.find(n => n.id === document.niveau)?.label || document.niveau
// Build PDF URL - try backend first, fall back to direct path
const pdfUrl = backendUrl
? `${backendUrl}/api/abitur-docs/${document.id}/file`
: document.file_path
const handleDownload = () => {
const link = window.document.createElement('a')
link.href = pdfUrl
link.download = document.dateiname
link.click()
}
const handleSearchInRAG = () => {
// Navigate to edu-search with document as context
window.location.href = `/education/edu-search?doc=${document.id}&search=1`
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-white rounded-2xl shadow-2xl w-[95vw] h-[90vh] max-w-7xl flex overflow-hidden">
{/* Header */}
<div className="absolute top-0 left-0 right-0 h-14 bg-white border-b border-slate-200 flex items-center justify-between px-4 z-10">
<h2 className="font-semibold text-slate-900 truncate flex items-center gap-2">
<FileText className="w-5 h-5 text-blue-600" />
{formatDocumentTitle(document)}
</h2>
<div className="flex items-center gap-2">
<button
onClick={handleSearchInRAG}
className="px-3 py-1.5 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 flex items-center gap-1.5"
title="In RAG suchen"
>
<Search className="w-4 h-4" />
RAG-Suche
</button>
<button
onClick={handleDownload}
className="px-3 py-1.5 text-sm bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 flex items-center gap-1.5"
title="Herunterladen"
>
<Download className="w-4 h-4" />
Download
</button>
<button
onClick={onClose}
className="p-2 hover:bg-slate-100 rounded-lg transition-colors"
title="Schliessen"
>
<X className="w-5 h-5 text-slate-500" />
</button>
</div>
</div>
{/* Content */}
<div className="flex w-full h-full pt-14">
{/* PDF Viewer */}
<div className="flex-1 bg-slate-100 p-4">
<iframe
src={pdfUrl}
className="w-full h-full rounded-lg border border-slate-200 bg-white"
title={document.dateiname}
/>
</div>
{/* Metadata Sidebar */}
<div className="w-80 border-l border-slate-200 bg-slate-50 p-4 overflow-y-auto">
<h3 className="font-semibold text-slate-900 mb-4">Dokument-Details</h3>
<div className="space-y-4">
{/* Fach */}
<div className="flex items-start gap-3">
<BookOpen className="w-5 h-5 text-slate-400 mt-0.5" />
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide">Fach</div>
<div className="font-medium text-slate-900">{fachLabel}</div>
</div>
</div>
{/* Jahr */}
<div className="flex items-start gap-3">
<Calendar className="w-5 h-5 text-slate-400 mt-0.5" />
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide">Jahr</div>
<div className="font-medium text-slate-900">{document.jahr}</div>
</div>
</div>
{/* Niveau */}
<div className="flex items-start gap-3">
<Layers className="w-5 h-5 text-slate-400 mt-0.5" />
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide">Niveau</div>
<div className="font-medium text-slate-900">{niveauLabel}</div>
</div>
</div>
{/* Aufgaben-Nummer */}
<div className="flex items-start gap-3">
<FileText className="w-5 h-5 text-slate-400 mt-0.5" />
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide">Aufgabe</div>
<div className="font-medium text-slate-900">
{document.aufgaben_nummer}
<span className="ml-2 px-2 py-0.5 bg-slate-200 text-slate-700 text-xs rounded-full">
{document.typ === 'erwartungshorizont' ? 'Erwartungshorizont' : 'Aufgabe'}
</span>
</div>
</div>
</div>
{/* Bundesland */}
<div className="flex items-start gap-3">
<ExternalLink className="w-5 h-5 text-slate-400 mt-0.5" />
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide">Bundesland</div>
<div className="font-medium text-slate-900 capitalize">{document.bundesland}</div>
</div>
</div>
<hr className="border-slate-200" />
{/* File Info */}
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide mb-2">Datei-Info</div>
<div className="bg-white rounded-lg border border-slate-200 p-3 text-sm space-y-2">
<div className="flex justify-between">
<span className="text-slate-500">Dateiname</span>
<span className="text-slate-900 font-mono text-xs truncate max-w-[150px]" title={document.dateiname}>
{document.dateiname}
</span>
</div>
<div className="flex justify-between">
<span className="text-slate-500">Groesse</span>
<span className="text-slate-900">{formatFileSize(document.file_size)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-500">Status</span>
<span className={`px-2 py-0.5 rounded-full text-xs ${
document.status === 'indexed'
? 'bg-green-100 text-green-700'
: document.status === 'error'
? 'bg-red-100 text-red-700'
: 'bg-yellow-100 text-yellow-700'
}`}>
{document.status === 'indexed' ? 'Indexiert' : document.status === 'error' ? 'Fehler' : 'Ausstehend'}
</span>
</div>
</div>
</div>
{/* RAG Info */}
{document.indexed && document.vector_ids.length > 0 && (
<div>
<div className="text-xs text-slate-500 uppercase tracking-wide mb-2">RAG-Index</div>
<div className="bg-purple-50 border border-purple-200 rounded-lg p-3 text-sm">
<div className="flex items-center gap-2 text-purple-700">
<Search className="w-4 h-4" />
<span>{document.vector_ids.length} Vektoren indexiert</span>
</div>
<div className="mt-2 text-xs text-purple-600">
Confidence: {(document.confidence * 100).toFixed(0)}%
</div>
</div>
</div>
)}
{/* Timestamps */}
<div className="text-xs text-slate-400 pt-2">
<div>Erstellt: {new Date(document.created_at).toLocaleString('de-DE')}</div>
<div>Aktualisiert: {new Date(document.updated_at).toLocaleString('de-DE')}</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}