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/studio-v2/components/worksheet-editor/EditorToolbar.tsx
BreakPilot Dev 916ecef476 feat(worksheet-editor): Add OCR import panel for grid analysis data
Add OCRImportPanel component and ocr-integration utilities to import
OCR-analyzed data from the grid detection service into the worksheet editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 23:50:35 +01:00

331 lines
11 KiB
TypeScript

'use client'
import { useRef } from 'react'
import { useTheme } from '@/lib/ThemeContext'
import { useLanguage } from '@/lib/LanguageContext'
import { useWorksheet } from '@/lib/worksheet-editor/WorksheetContext'
import type { EditorTool } from '@/app/worksheet-editor/types'
interface EditorToolbarProps {
onOpenAIGenerator: () => void
onOpenDocumentImporter: () => void
onOpenCleanupPanel?: () => void
onOpenOCRImport?: () => void
className?: string
}
interface ToolButtonProps {
tool: EditorTool
icon: React.ReactNode
label: string
isActive: boolean
onClick: () => void
isDark: boolean
}
function ToolButton({ tool, icon, label, isActive, onClick, isDark }: ToolButtonProps) {
return (
<button
onClick={onClick}
title={label}
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
isActive
? isDark
? 'bg-purple-500/30 text-purple-300 shadow-lg'
: 'bg-purple-100 text-purple-700 shadow-lg'
: isDark
? 'text-white/70 hover:bg-white/10 hover:text-white'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
}`}
>
{icon}
</button>
)
}
export function EditorToolbar({ onOpenAIGenerator, onOpenDocumentImporter, onOpenCleanupPanel, onOpenOCRImport, className = '' }: EditorToolbarProps) {
const { isDark } = useTheme()
const { t } = useLanguage()
const fileInputRef = useRef<HTMLInputElement>(null)
const {
activeTool,
setActiveTool,
canvas,
canUndo,
canRedo,
undo,
redo,
} = useWorksheet()
const handleToolClick = (tool: EditorTool) => {
setActiveTool(tool)
}
const handleImageUpload = () => {
fileInputRef.current?.click()
}
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file || !canvas) return
const reader = new FileReader()
reader.onload = (event) => {
const url = event.target?.result as string
if ((canvas as any).addImage) {
(canvas as any).addImage(url)
}
}
reader.readAsDataURL(file)
// Reset input
e.target.value = ''
}
// Glassmorphism styles
const toolbarStyle = isDark
? 'backdrop-blur-xl bg-white/10 border border-white/20'
: 'backdrop-blur-xl bg-white/70 border border-black/10 shadow-xl'
const dividerStyle = isDark
? 'border-white/20'
: 'border-slate-200'
return (
<div className={`flex flex-col gap-2 p-2 rounded-2xl ${toolbarStyle} ${className}`}>
{/* Selection Tool */}
<ToolButton
tool="select"
isActive={activeTool === 'select'}
onClick={() => handleToolClick('select')}
isDark={isDark}
label="Auswählen"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122" />
</svg>
}
/>
<div className={`border-t ${dividerStyle}`} />
{/* Text Tool */}
<ToolButton
tool="text"
isActive={activeTool === 'text'}
onClick={() => handleToolClick('text')}
isDark={isDark}
label="Text"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
}
/>
<div className={`border-t ${dividerStyle}`} />
{/* Shape Tools */}
<ToolButton
tool="rectangle"
isActive={activeTool === 'rectangle'}
onClick={() => handleToolClick('rectangle')}
isDark={isDark}
label="Rechteck"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6h16M4 18h16M4 6v12M20 6v12" />
</svg>
}
/>
<ToolButton
tool="circle"
isActive={activeTool === 'circle'}
onClick={() => handleToolClick('circle')}
isDark={isDark}
label="Kreis"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" strokeWidth={1.5} />
</svg>
}
/>
<ToolButton
tool="line"
isActive={activeTool === 'line'}
onClick={() => handleToolClick('line')}
isDark={isDark}
label="Linie"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 20L20 4" />
</svg>
}
/>
<ToolButton
tool="arrow"
isActive={activeTool === 'arrow'}
onClick={() => handleToolClick('arrow')}
isDark={isDark}
label="Pfeil"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
}
/>
<div className={`border-t ${dividerStyle}`} />
{/* Image Tools */}
<ToolButton
tool="image"
isActive={activeTool === 'image'}
onClick={handleImageUpload}
isDark={isDark}
label="Bild hochladen"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
}
/>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileChange}
className="hidden"
/>
{/* AI Image Generator */}
<ToolButton
tool="ai-image"
isActive={activeTool === 'ai-image'}
onClick={onOpenAIGenerator}
isDark={isDark}
label="KI-Bild generieren"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
}
/>
<div className={`border-t ${dividerStyle}`} />
{/* Document Import */}
<button
onClick={onOpenDocumentImporter}
title="Dokument importieren"
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
isDark
? 'text-blue-300 hover:bg-blue-500/20 hover:text-blue-200'
: 'text-blue-600 hover:bg-blue-100 hover:text-blue-700'
}`}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 3v6a1 1 0 001 1h6" />
</svg>
</button>
{/* Cleanup Panel - Handwriting Removal */}
{onOpenCleanupPanel && (
<button
onClick={onOpenCleanupPanel}
title="Arbeitsblatt bereinigen (Handschrift entfernen)"
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
isDark
? 'text-orange-300 hover:bg-orange-500/20 hover:text-orange-200'
: 'text-orange-600 hover:bg-orange-100 hover:text-orange-700'
}`}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
</svg>
</button>
)}
{/* OCR Import */}
{onOpenOCRImport && (
<button
onClick={onOpenOCRImport}
title="OCR Daten importieren (aus Grid Analyse)"
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
isDark
? 'text-green-300 hover:bg-green-500/20 hover:text-green-200'
: 'text-green-600 hover:bg-green-100 hover:text-green-700'
}`}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
</button>
)}
<div className={`border-t ${dividerStyle}`} />
{/* Table Tool */}
<ToolButton
tool="table"
isActive={activeTool === 'table'}
onClick={() => handleToolClick('table')}
isDark={isDark}
label="Tabelle"
icon={
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
}
/>
<div className={`border-t ${dividerStyle} mt-auto`} />
{/* Undo/Redo */}
<button
onClick={undo}
disabled={!canUndo}
title="Rückgängig (Ctrl+Z)"
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
canUndo
? isDark
? 'text-white/70 hover:bg-white/10 hover:text-white'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
: isDark
? 'text-white/30 cursor-not-allowed'
: 'text-slate-300 cursor-not-allowed'
}`}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
</svg>
</button>
<button
onClick={redo}
disabled={!canRedo}
title="Wiederholen (Ctrl+Y)"
className={`w-12 h-12 flex items-center justify-center rounded-xl transition-all ${
canRedo
? isDark
? 'text-white/70 hover:bg-white/10 hover:text-white'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
: isDark
? 'text-white/30 cursor-not-allowed'
: 'text-slate-300 cursor-not-allowed'
}`}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 10h-10a8 8 0 00-8 8v2M21 10l-6 6m6-6l-6-6" />
</svg>
</button>
</div>
)
}