feat: add Woerterbuch category + column add/delete in grid editor

- New document category "Woerterbuch" (frontend type + backend validation)
- Column delete: hover column header → red "x" button (with confirmation)
- Column add: hover column header → "+" button inserts after that column
- Both operations support undo/redo, update cell IDs and summary
- Available in both GridEditor and StepGridReview (Kombi last step)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-23 16:27:12 +01:00
parent 7a6eadde8b
commit 4e668660a7
6 changed files with 174 additions and 4 deletions

View File

@@ -12,6 +12,8 @@ interface GridTableProps {
onToggleColumnBold: (zoneIndex: number, colIndex: number) => void
onToggleRowHeader: (zoneIndex: number, rowIndex: number) => void
onNavigate: (cellId: string, direction: 'up' | 'down' | 'left' | 'right') => void
onDeleteColumn?: (zoneIndex: number, colIndex: number) => void
onAddColumn?: (zoneIndex: number, afterColIndex: number) => void
}
/** Gutter width for row numbers (px). */
@@ -32,6 +34,8 @@ export function GridTable({
onToggleColumnBold,
onToggleRowHeader,
onNavigate,
onDeleteColumn,
onAddColumn,
}: GridTableProps) {
const containerRef = useRef<HTMLDivElement>(null)
const [containerWidth, setContainerWidth] = useState(0)
@@ -245,11 +249,11 @@ export function GridTable({
{/* Header: row-number corner */}
<div className="sticky left-0 z-10 px-1 py-1.5 text-[10px] text-gray-400 dark:text-gray-500 border-b border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50" />
{/* Header: column labels with resize handles */}
{/* Header: column labels with resize handles + delete/add */}
{zone.columns.map((col, ci) => (
<div
key={col.index}
className={`relative px-2 py-1.5 text-xs font-medium border-b border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 cursor-pointer select-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 ${
className={`group/colhdr relative px-2 py-1.5 text-xs font-medium border-b border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 cursor-pointer select-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 ${
col.bold ? 'text-teal-700 dark:text-teal-300' : 'text-gray-600 dark:text-gray-400'
}`}
onClick={() => onToggleColumnBold(zone.zone_index, col.index)}
@@ -263,6 +267,36 @@ export function GridTable({
</span>
)}
</div>
{/* Delete column button (visible on hover) */}
{onDeleteColumn && numCols > 1 && (
<button
className="absolute top-0 left-0 w-4 h-4 flex items-center justify-center bg-red-500 text-white rounded-br text-[9px] leading-none opacity-0 group-hover/colhdr:opacity-100 transition-opacity z-30"
onClick={(e) => {
e.stopPropagation()
if (confirm(`Spalte "${col.label}" loeschen?`)) {
onDeleteColumn(zone.zone_index, col.index)
}
}}
title={`Spalte "${col.label}" loeschen`}
>
x
</button>
)}
{/* Add column button (visible on hover, after this column) */}
{onAddColumn && (
<button
className="absolute -right-[7px] top-0 w-[14px] h-full flex items-center justify-center text-teal-500 opacity-0 group-hover/colhdr:opacity-100 transition-opacity z-30 hover:bg-teal-100 dark:hover:bg-teal-900/40 rounded"
onClick={(e) => {
e.stopPropagation()
onAddColumn(zone.zone_index, col.index)
}}
title={`Spalte nach "${col.label}" einfuegen`}
>
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
</svg>
</button>
)}
{/* Right-edge resize handle */}
{ci < numCols - 1 && (
<div