Restructure upload flow: document first, then preview + naming
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 36s
CI / test-go-edu-search (push) Successful in 38s
CI / test-python-klausur (push) Failing after 2m39s
CI / test-python-agent-core (push) Successful in 33s
CI / test-nodejs-website (push) Successful in 24s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 36s
CI / test-go-edu-search (push) Successful in 38s
CI / test-python-klausur (push) Failing after 2m39s
CI / test-python-agent-core (push) Successful in 33s
CI / test-nodejs-website (push) Successful in 24s
Step 1 is now document selection (full width). After selecting a file, Step 2 shows a side-by-side layout with document preview (3/5 width, scrollable, with fullscreen modal) and session naming (2/5 width, with start button). Also adds PDF preview via blob URL before upload. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,7 @@ export default function VocabWorksheetPage() {
|
||||
// Direct file upload
|
||||
const [directFile, setDirectFile] = useState<File | null>(null)
|
||||
const [directFilePreview, setDirectFilePreview] = useState<string | null>(null)
|
||||
const [showFullPreview, setShowFullPreview] = useState(false)
|
||||
const directFileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
// PDF page selection state
|
||||
@@ -263,6 +264,7 @@ export default function VocabWorksheetPage() {
|
||||
|
||||
setDirectFile(file)
|
||||
setSelectedDocumentId(null)
|
||||
setSelectedMobileFile(null)
|
||||
|
||||
if (file.type.startsWith('image/')) {
|
||||
const reader = new FileReader()
|
||||
@@ -270,6 +272,8 @@ export default function VocabWorksheetPage() {
|
||||
setDirectFilePreview(ev.target?.result as string)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
} else if (file.type === 'application/pdf') {
|
||||
setDirectFilePreview(URL.createObjectURL(file))
|
||||
} else {
|
||||
setDirectFilePreview(null)
|
||||
}
|
||||
@@ -826,6 +830,7 @@ export default function VocabWorksheetPage() {
|
||||
setSelectedDocumentId(null)
|
||||
setDirectFile(null)
|
||||
setDirectFilePreview(null)
|
||||
setShowFullPreview(false)
|
||||
setPdfPageCount(0)
|
||||
setSelectedPages([])
|
||||
setPagesThumbnails([])
|
||||
@@ -1219,19 +1224,19 @@ export default function VocabWorksheetPage() {
|
||||
<ol className={`space-y-2 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${isDark ? 'bg-purple-500/30 text-purple-300' : 'bg-purple-200 text-purple-700'}`}>1</span>
|
||||
<span>Session benennen und Dokument (Bild oder PDF) auswaehlen</span>
|
||||
<span>Dokument (Bild oder PDF) auswaehlen</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${isDark ? 'bg-purple-500/30 text-purple-300' : 'bg-purple-200 text-purple-700'}`}>2</span>
|
||||
<span>Bei PDFs: Seiten auswaehlen die verarbeitet werden sollen</span>
|
||||
<span>Vorschau pruefen und Session benennen</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${isDark ? 'bg-purple-500/30 text-purple-300' : 'bg-purple-200 text-purple-700'}`}>3</span>
|
||||
<span>KI extrahiert automatisch Vokabeln (Englisch | Deutsch | Beispiel)</span>
|
||||
<span>Bei PDFs: Seiten auswaehlen die verarbeitet werden sollen</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${isDark ? 'bg-purple-500/30 text-purple-300' : 'bg-purple-200 text-purple-700'}`}>4</span>
|
||||
<span>Vokabeln pruefen/korrigieren und Arbeitsblatt-Typ waehlen</span>
|
||||
<span>KI extrahiert Vokabeln — pruefen, korrigieren, Arbeitsblatt-Typ waehlen</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${isDark ? 'bg-purple-500/30 text-purple-300' : 'bg-purple-200 text-purple-700'}`}>5</span>
|
||||
@@ -1240,174 +1245,215 @@ export default function VocabWorksheetPage() {
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* Two Column Layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Session Name */}
|
||||
<div className={`${glassCard} rounded-2xl p-6`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
1. Session benennen
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={sessionName}
|
||||
onChange={(e) => { setSessionName(e.target.value); setError(null) }}
|
||||
placeholder="z.B. Englisch Klasse 7 - Unit 3"
|
||||
className={`w-full px-4 py-3 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
/>
|
||||
{/* Step 1: Document Selection */}
|
||||
<div className={`${glassCard} rounded-2xl p-6`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
1. Dokument auswaehlen
|
||||
</h2>
|
||||
|
||||
<input ref={directFileInputRef} type="file" accept="image/png,image/jpeg,image/jpg,application/pdf" onChange={handleDirectFileSelect} className="hidden" />
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
{/* File Upload Button */}
|
||||
<button
|
||||
onClick={() => directFileInputRef.current?.click()}
|
||||
className={`p-4 rounded-xl border-2 border-dashed transition-all ${
|
||||
directFile
|
||||
? (isDark ? 'border-green-400/50 bg-green-500/20' : 'border-green-500 bg-green-50')
|
||||
: (isDark ? 'border-white/20 hover:border-purple-400/50' : 'border-slate-300 hover:border-purple-500')
|
||||
}`}
|
||||
>
|
||||
{directFile ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{directFile.type === 'application/pdf' ? '📄' : '🖼️'}</span>
|
||||
<div className="text-left flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{directFile.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(directFile.size)}</p>
|
||||
</div>
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`text-center ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
<span className="text-2xl block mb-1">📁</span>
|
||||
<span className="text-sm">Datei auswaehlen</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* QR Code Upload Button */}
|
||||
<button
|
||||
onClick={() => setShowQRModal(true)}
|
||||
className={`p-4 rounded-xl border-2 border-dashed transition-all ${
|
||||
selectedMobileFile
|
||||
? (isDark ? 'border-green-400/50 bg-green-500/20' : 'border-green-500 bg-green-50')
|
||||
: (isDark ? 'border-white/20 hover:border-purple-400/50' : 'border-slate-300 hover:border-purple-500')
|
||||
}`}
|
||||
>
|
||||
{selectedMobileFile ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{selectedMobileFile.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
||||
<div className="text-left flex-1 min-w-0">
|
||||
<p className={`font-medium truncate text-sm ${isDark ? 'text-white' : 'text-slate-900'}`}>{selectedMobileFile.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>vom Handy</p>
|
||||
</div>
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`text-center ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
<span className="text-2xl block mb-1">📱</span>
|
||||
<span className="text-sm">Mit Handy scannen</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Document Selection */}
|
||||
<div className={`${glassCard} rounded-2xl p-6`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
2. Dokument auswaehlen
|
||||
</h2>
|
||||
|
||||
{/* Direct Upload */}
|
||||
<input ref={directFileInputRef} type="file" accept="image/png,image/jpeg,image/jpg,application/pdf" onChange={handleDirectFileSelect} className="hidden" />
|
||||
|
||||
{/* Two upload options side by side */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
{/* File Upload Button */}
|
||||
<button
|
||||
onClick={() => directFileInputRef.current?.click()}
|
||||
className={`p-4 rounded-xl border-2 border-dashed transition-all ${
|
||||
directFile
|
||||
? (isDark ? 'border-green-400/50 bg-green-500/20' : 'border-green-500 bg-green-50')
|
||||
: (isDark ? 'border-white/20 hover:border-purple-400/50' : 'border-slate-300 hover:border-purple-500')
|
||||
}`}
|
||||
>
|
||||
{directFile ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{directFile.type === 'application/pdf' ? '📄' : '🖼️'}</span>
|
||||
<div className="text-left flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{directFile.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(directFile.size)}</p>
|
||||
{/* Mobile Uploaded Files */}
|
||||
{mobileUploadedFiles.length > 0 && !directFile && (
|
||||
<>
|
||||
<div className={`text-center text-sm mb-3 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>— Vom Handy hochgeladen —</div>
|
||||
<div className="space-y-2 max-h-32 overflow-y-auto mb-4">
|
||||
{mobileUploadedFiles.map((file) => (
|
||||
<button
|
||||
key={file.id}
|
||||
onClick={() => { setSelectedMobileFile(file); setDirectFile(null); setSelectedDocumentId(null); setError(null) }}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-xl text-left transition-all ${
|
||||
selectedMobileFile?.id === file.id
|
||||
? (isDark ? 'bg-green-500/30 border-2 border-green-400/50' : 'bg-green-100 border-2 border-green-500')
|
||||
: (isDark ? 'bg-white/5 border-2 border-transparent hover:border-white/20' : 'bg-slate-50 border-2 border-transparent hover:border-slate-200')
|
||||
}`}
|
||||
>
|
||||
<span className="text-xl">{file.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{file.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(file.size)}</p>
|
||||
</div>
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`text-center ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
<span className="text-2xl block mb-1">📁</span>
|
||||
<span className="text-sm">Datei auswaehlen</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
{selectedMobileFile?.id === file.id && (
|
||||
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* QR Code Upload Button */}
|
||||
<button
|
||||
onClick={() => setShowQRModal(true)}
|
||||
className={`p-4 rounded-xl border-2 border-dashed transition-all ${
|
||||
selectedMobileFile
|
||||
? (isDark ? 'border-green-400/50 bg-green-500/20' : 'border-green-500 bg-green-50')
|
||||
: (isDark ? 'border-white/20 hover:border-purple-400/50' : 'border-slate-300 hover:border-purple-500')
|
||||
}`}
|
||||
>
|
||||
{selectedMobileFile ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{selectedMobileFile.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
||||
<div className="text-left flex-1 min-w-0">
|
||||
<p className={`font-medium truncate text-sm ${isDark ? 'text-white' : 'text-slate-900'}`}>{selectedMobileFile.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>vom Handy</p>
|
||||
{/* Stored Documents */}
|
||||
{storedDocuments.length > 0 && !directFile && !selectedMobileFile && (
|
||||
<>
|
||||
<div className={`text-center text-sm mb-3 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>— oder aus Ihren Dokumenten —</div>
|
||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
||||
{storedDocuments.map((doc) => (
|
||||
<button
|
||||
key={doc.id}
|
||||
onClick={() => { setSelectedDocumentId(doc.id); setDirectFile(null); setSelectedMobileFile(null); setError(null) }}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-xl text-left transition-all ${
|
||||
selectedDocumentId === doc.id
|
||||
? (isDark ? 'bg-purple-500/30 border-2 border-purple-400/50' : 'bg-purple-100 border-2 border-purple-500')
|
||||
: (isDark ? 'bg-white/5 border-2 border-transparent hover:border-white/20' : 'bg-slate-50 border-2 border-transparent hover:border-slate-200')
|
||||
}`}
|
||||
>
|
||||
<span className="text-xl">{doc.type === 'application/pdf' ? '📄' : '🖼️'}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{doc.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(doc.size)}</p>
|
||||
</div>
|
||||
<svg className="w-5 h-5 text-green-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`text-center ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||||
<span className="text-2xl block mb-1">📱</span>
|
||||
<span className="text-sm">Mit Handy scannen</span>
|
||||
</div>
|
||||
{selectedDocumentId === doc.id && (
|
||||
<svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Step 2: Preview + Session Name (shown when document selected) */}
|
||||
{(directFile || selectedMobileFile || selectedDocumentId) && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
||||
{/* Document Preview */}
|
||||
<div className={`${glassCard} rounded-2xl p-6 lg:col-span-3`}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
Vorschau
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowFullPreview(true)}
|
||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
|
||||
isDark ? 'bg-white/10 hover:bg-white/20 text-white' : 'bg-slate-100 hover:bg-slate-200 text-slate-700'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
|
||||
</svg>
|
||||
Originalgroesse
|
||||
</button>
|
||||
</div>
|
||||
<div className={`max-h-[60vh] overflow-auto rounded-xl border ${isDark ? 'border-white/10' : 'border-black/10'}`}>
|
||||
{/* Image preview from file */}
|
||||
{directFile?.type.startsWith('image/') && directFilePreview && (
|
||||
<img src={directFilePreview} alt="Vorschau" className="w-full h-auto" />
|
||||
)}
|
||||
</button>
|
||||
{/* PDF preview from file */}
|
||||
{directFile?.type === 'application/pdf' && directFilePreview && (
|
||||
<iframe src={directFilePreview} className="w-full border-0 rounded-xl" style={{ height: '60vh' }} />
|
||||
)}
|
||||
{/* Mobile file preview */}
|
||||
{selectedMobileFile && !directFile && (
|
||||
selectedMobileFile.type.startsWith('image/')
|
||||
? <img src={selectedMobileFile.dataUrl} alt="Vorschau" className="w-full h-auto" />
|
||||
: <iframe src={selectedMobileFile.dataUrl} className="w-full border-0 rounded-xl" style={{ height: '60vh' }} />
|
||||
)}
|
||||
{/* Stored document preview */}
|
||||
{selectedDocumentId && !directFile && !selectedMobileFile && (() => {
|
||||
const doc = storedDocuments.find(d => d.id === selectedDocumentId)
|
||||
if (!doc?.url) return <p className={`p-8 text-center ${isDark ? 'text-white/40' : 'text-slate-400'}`}>Keine Vorschau verfuegbar</p>
|
||||
return doc.type.startsWith('image/')
|
||||
? <img src={doc.url} alt="Vorschau" className="w-full h-auto" />
|
||||
: <iframe src={doc.url} className="w-full border-0 rounded-xl" style={{ height: '60vh' }} />
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Uploaded Files */}
|
||||
{mobileUploadedFiles.length > 0 && !directFile && (
|
||||
<>
|
||||
<div className={`text-center text-sm mb-3 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>— Vom Handy hochgeladen —</div>
|
||||
<div className="space-y-2 max-h-32 overflow-y-auto mb-4">
|
||||
{mobileUploadedFiles.map((file) => (
|
||||
<button
|
||||
key={file.id}
|
||||
onClick={() => { setSelectedMobileFile(file); setDirectFile(null); setSelectedDocumentId(null); setError(null) }}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-xl text-left transition-all ${
|
||||
selectedMobileFile?.id === file.id
|
||||
? (isDark ? 'bg-green-500/30 border-2 border-green-400/50' : 'bg-green-100 border-2 border-green-500')
|
||||
: (isDark ? 'bg-white/5 border-2 border-transparent hover:border-white/20' : 'bg-slate-50 border-2 border-transparent hover:border-slate-200')
|
||||
}`}
|
||||
>
|
||||
<span className="text-xl">{file.type.startsWith('image/') ? '🖼️' : '📄'}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{file.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(file.size)}</p>
|
||||
</div>
|
||||
{selectedMobileFile?.id === file.id && (
|
||||
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Stored Documents */}
|
||||
{storedDocuments.length > 0 && !directFile && (
|
||||
<>
|
||||
<div className={`text-center text-sm mb-3 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>— oder aus Ihren Dokumenten —</div>
|
||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
||||
{storedDocuments.map((doc) => (
|
||||
<button
|
||||
key={doc.id}
|
||||
onClick={() => { setSelectedDocumentId(doc.id); setDirectFile(null); setError(null) }}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-xl text-left transition-all ${
|
||||
selectedDocumentId === doc.id
|
||||
? (isDark ? 'bg-purple-500/30 border-2 border-purple-400/50' : 'bg-purple-100 border-2 border-purple-500')
|
||||
: (isDark ? 'bg-white/5 border-2 border-transparent hover:border-white/20' : 'bg-slate-50 border-2 border-transparent hover:border-slate-200')
|
||||
}`}
|
||||
>
|
||||
<span className="text-xl">{doc.type === 'application/pdf' ? '📄' : '🖼️'}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`font-medium truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>{doc.name}</p>
|
||||
<p className={`text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>{formatFileSize(doc.size)}</p>
|
||||
</div>
|
||||
{selectedDocumentId === doc.id && (
|
||||
<svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Session Name + Start */}
|
||||
<div className={`${glassCard} rounded-2xl p-6 lg:col-span-2 flex flex-col`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
2. Session benennen
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={sessionName}
|
||||
onChange={(e) => { setSessionName(e.target.value); setError(null) }}
|
||||
placeholder="z.B. Englisch Klasse 7 - Unit 3"
|
||||
className={`w-full px-4 py-3 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500 mb-4`}
|
||||
autoFocus
|
||||
/>
|
||||
<p className={`text-sm mb-6 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
Benennen Sie die Session z.B. nach dem Schulbuch-Kapitel, damit Sie sie spaeter wiederfinden.
|
||||
</p>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!sessionName.trim()) {
|
||||
setError('Bitte geben Sie einen Session-Namen ein (z.B. "Englisch Klasse 7 - Unit 3")')
|
||||
return
|
||||
}
|
||||
startSession()
|
||||
}}
|
||||
disabled={isCreatingSession || !sessionName.trim()}
|
||||
className="w-full px-6 py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-2xl font-semibold text-lg disabled:opacity-50 hover:shadow-xl hover:shadow-purple-500/30 transition-all transform hover:scale-105"
|
||||
>
|
||||
{isCreatingSession ? 'Verarbeite...' : 'Weiter →'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Start Button */}
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!sessionName.trim()) {
|
||||
alert('Bitte geben Sie einen Session-Namen ein (z.B. "Englisch Klasse 7 - Unit 3")')
|
||||
return
|
||||
}
|
||||
if (!selectedDocumentId && !directFile && !selectedMobileFile) {
|
||||
alert('Bitte wählen Sie ein Dokument aus oder laden Sie eine Datei hoch.')
|
||||
return
|
||||
}
|
||||
startSession()
|
||||
}}
|
||||
disabled={isCreatingSession}
|
||||
className="px-8 py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-2xl font-semibold text-lg disabled:opacity-50 hover:shadow-xl hover:shadow-purple-500/30 transition-all transform hover:scale-105"
|
||||
>
|
||||
{isCreatingSession ? 'Verarbeite...' : 'Weiter →'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2019,6 +2065,40 @@ export default function VocabWorksheetPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Fullscreen Preview Modal */}
|
||||
{showFullPreview && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center" onClick={() => setShowFullPreview(false)}>
|
||||
<button
|
||||
onClick={() => setShowFullPreview(false)}
|
||||
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white z-10 transition-colors"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="max-w-[95vw] max-h-[95vh] overflow-auto" onClick={(e) => e.stopPropagation()}>
|
||||
{directFile?.type.startsWith('image/') && directFilePreview && (
|
||||
<img src={directFilePreview} alt="Original" className="max-w-none" />
|
||||
)}
|
||||
{directFile?.type === 'application/pdf' && directFilePreview && (
|
||||
<iframe src={directFilePreview} className="border-0 rounded-xl bg-white" style={{ width: '90vw', height: '90vh' }} />
|
||||
)}
|
||||
{selectedMobileFile && !directFile && (
|
||||
selectedMobileFile.type.startsWith('image/')
|
||||
? <img src={selectedMobileFile.dataUrl} alt="Original" className="max-w-none" />
|
||||
: <iframe src={selectedMobileFile.dataUrl} className="border-0 rounded-xl bg-white" style={{ width: '90vw', height: '90vh' }} />
|
||||
)}
|
||||
{selectedDocumentId && !directFile && !selectedMobileFile && (() => {
|
||||
const doc = storedDocuments.find(d => d.id === selectedDocumentId)
|
||||
if (!doc?.url) return null
|
||||
return doc.type.startsWith('image/')
|
||||
? <img src={doc.url} alt="Original" className="max-w-none" />
|
||||
: <iframe src={doc.url} className="border-0 rounded-xl bg-white" style={{ width: '90vw', height: '90vh' }} />
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* QR Code Modal */}
|
||||
{showQRModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
|
||||
Reference in New Issue
Block a user