import React, { useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; import { useLanguage } from '../../../providers/language/LanguageContext'; import { useConfirm } from '../../../hooks/useConfirm'; import styles from './GroupRow.module.css'; import type { TableGroupNode } from '../FormGeneratorTable/FormGeneratorTable'; import { FaFolder, FaFolderOpen, FaList, FaPen, FaPlus } from 'react-icons/fa'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface GroupBulkAction { icon?: React.ReactNode; title?: string; variant?: 'default' | 'danger'; onClick: () => void; disabled?: boolean; } // --------------------------------------------------------------------------- // GroupFolderRow // --------------------------------------------------------------------------- interface GroupFolderRowProps { node: TableGroupNode; depth: number; colSpan: number; visibleCount: number; isExpanded: boolean; isEditing: boolean; /** True while an ITEM is dragged over this row (drop item into group). */ isDragOver: boolean; /** True while a GROUP is dragged over this row (nest group inside). */ isDragOverFromGroup: boolean; bulkActions?: GroupBulkAction[]; onToggle: () => void; onEditCommit: (name: string) => void; onEditCancel: () => void; onRename: () => void; onAddSub: () => void; // Item drag-drop onItemDragOver: (e: React.DragEvent) => void; onItemDrop: (e: React.DragEvent) => void; onItemDragLeave: () => void; // Group drag (this row is draggable) onGroupDragStart: (e: React.DragEvent) => void; onGroupDragEnd: () => void; onGroupDrag?: (e: React.DragEvent) => void; /** True while this group is being dragged leftward to pop out one level */ isDraggingOut?: boolean; /** Hide this row via display:none (keeps it in DOM so drag operations don't break) */ hidden?: boolean; // Group drop (another group dropped onto this) onGroupDragOver: (e: React.DragEvent) => void; onGroupDrop: (e: React.DragEvent) => void; onGroupDragLeave: () => void; } export function GroupFolderRow({ node, depth, colSpan, visibleCount, isExpanded, isEditing, isDragOver, isDragOverFromGroup, isDraggingOut, hidden, bulkActions = [], onToggle, onEditCommit, onEditCancel, onRename, onAddSub, onItemDragOver, onItemDrop, onItemDragLeave, onGroupDragStart, onGroupDragEnd, onGroupDrag, onGroupDragOver, onGroupDrop, onGroupDragLeave, }: GroupFolderRowProps) { const { t } = useLanguage(); const { ConfirmDialog } = useConfirm(); const inputRef = useRef(null); const totalCount = node.itemIds.length; useEffect(() => { if (isEditing && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [isEditing]); const indentPx = depth * 20; const _rowClass = [ styles.groupFolderRow, isDragOver ? styles.dragOver : '', isDragOverFromGroup ? styles.dragOverGroup : '', isDraggingOut ? styles.draggingOut : '', ].filter(Boolean).join(' '); return ( <> {typeof document !== 'undefined' && ReactDOM.createPortal(, document.body)} { // distinguish item vs group drag via dataTransfer type if (e.dataTransfer.types.includes('application/porta-group')) { onGroupDragOver(e); } else { onItemDragOver(e); } }} onDrop={(e) => { if (e.dataTransfer.types.includes('application/porta-group')) { onGroupDrop(e); } else { onItemDrop(e); } }} onDragLeave={() => { onItemDragLeave(); onGroupDragLeave(); }} onDragEnter={(e) => e.preventDefault()} >
{/* Indent */} {indentPx > 0 && } {/* Chevron */} {/* Folder icon */} {isExpanded ? : } {/* Name / inline input */} {isEditing ? ( { if (e.key === 'Enter') onEditCommit(e.currentTarget.value); if (e.key === 'Escape') onEditCancel(); }} onBlur={(e) => onEditCommit(e.target.value)} /> ) : ( { e.stopPropagation(); onToggle(); }}> {node.name || {t('(Unbenannt)')}} )} {/* Item count badge */} {!isEditing && ( {visibleCount < totalCount && totalCount > 0 ? `${visibleCount} / ${totalCount}` : String(totalCount)} )} {/* Drop hint */} {(isDragOver || isDragOverFromGroup) && ( {isDragOverFromGroup ? t('Als Untergruppe ablegen') : t('Hierher ziehen')} )} {/* ── Bulk actions (delete all, custom batch) right after badge ── */} {!isEditing && bulkActions.length > 0 && ( <> {bulkActions.map((action, i) => ( ))} )} {/* ── Group management: rename / add-subgroup ── */} {!isEditing && ( )}
); } // --------------------------------------------------------------------------- // BreadcrumbRow // --------------------------------------------------------------------------- interface BreadcrumbRowProps { groupName: string; totalItems: number; colSpan: number; onBack: () => void; } export function BreadcrumbRow({ groupName, totalItems, colSpan, onBack }: BreadcrumbRowProps) { const { t } = useLanguage(); return (
{groupName} {totalItems > 0 && ( ({totalItems} {t('Einträge')}) )}
); } // --------------------------------------------------------------------------- // UngroupedRow — also a drop zone for removing items/groups from groups // --------------------------------------------------------------------------- interface UngroupedRowProps { count: number; colSpan: number; isDragOver?: boolean; onDragOver?: (e: React.DragEvent) => void; onDrop?: (e: React.DragEvent) => void; onDragLeave?: () => void; } export function UngroupedRow({ count, colSpan, isDragOver, onDragOver, onDrop, onDragLeave }: UngroupedRowProps) { const { t } = useLanguage(); return ( e.preventDefault()} > {t('Nicht zugeordnet')} {count} {isDragOver && {t('Aus Gruppe entfernen')}} ); }