diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx index 737cd26..f8b39ec 100644 --- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx +++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx @@ -81,6 +81,15 @@ import api from '../../../api'; import { PeriodPicker } from '../../PeriodPicker'; import type { PeriodValue } from '../../PeriodPicker'; import { TableViewsBar, groupLevelsToApiPayload, type GroupByLevelSpec } from '../TableViewsBar'; +import { + listTableViews, + getTableView, + createTableView, + updateTableView, + deleteTableView, + type TableListViewRow, + type TableViewConfig, +} from '../../../api/tableViewApi'; function groupLevelsFromViewConfig(raw: unknown): GroupByLevelSpec[] { if (!Array.isArray(raw)) return []; @@ -91,15 +100,6 @@ function groupLevelsFromViewConfig(raw: unknown): GroupByLevelSpec[] { })) .filter((l) => l.field); } -import { - listTableViews, - getTableView, - createTableView, - updateTableView, - deleteTableView, - type TableListViewRow, - type TableViewConfig, -} from '../../../api/tableViewApi'; function collapseLocalStorageKey(contextKey: string) { return `porta_table_collapse_${contextKey}`; @@ -2650,6 +2650,81 @@ export function FormGeneratorTable>({ batchActions.length > 0 || (selectable && selectedIds.size > 0); + const _renderDataRow = (row: T, index: number) => { + const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, index) : {}; + const rowId = _getRowId(row); + return ( + onRowClick?.(row, index)} + draggable={rowDraggable} + onDragStart={(e) => { + if (rowDraggable && onRowDragStart) onRowDragStart(e, row); + }} + onDragEnd={() => {}} + {...Object.fromEntries(Object.entries(dataAttributes).map(([k, v]) => [`data-${k}`, v]))} + > + {selectable && ( + + handleRowSelect(row)} + onClick={(e) => e.stopPropagation()} + disabled={isRowSelectable && !isRowSelectable(row)} + title={isRowSelectable && !isRowSelectable(row) ? t('Dieses Element kann nicht ausgewählt werden') : t('Dieses Element auswählen')} + style={{ opacity: isRowSelectable && !isRowSelectable(row) ? 0.4 : 1, cursor: isRowSelectable && !isRowSelectable(row) ? 'not-allowed' : 'pointer' }} + /> + + )} + {hasActionColumn && ( + +
{ if (el) actionButtonsRefs.current.set(index, el); else actionButtonsRefs.current.delete(index); }} + className={`${styles.actionButtons} ${shouldWrapActionButtons ? styles.actionButtonsWrap : ''}`}> + {actionButtons.map((ab, ai) => { + if (ab.visible && !ab.visible(row, hookData)) return null; + const abTitle = typeof ab.title === 'function' ? ab.title(row) : ab.title; + let dis: boolean | { disabled: boolean; message?: string } = false; + if (ab.disabled) { dis = ab.disabled(row, hookData); } + else if (row._permissions) { + if (ab.type === 'edit' && row._permissions.canUpdate === false) dis = true; + else if (ab.type === 'delete' && row._permissions.canDelete === false) dis = true; + } + const isLd = ab.loading ? ab.loading(row) : false; + const isProc = ab.isProcessing ? ab.isProcessing(row) : false; + const bp = { row, disabled: dis, loading: isLd, className: [compact ? actionBtnStyles.compact : '', ab.className ?? ''].filter(Boolean).join(' '), title: abTitle, idField: ab.idField ?? 'id', nameField: ab.nameField ?? 'name', typeField: ab.typeField ?? 'type', contentField: ab.contentField ?? 'content', operationName: ab.operationName, loadingStateName: ab.loadingStateName }; + switch (ab.type) { + case 'edit': return ; + case 'delete': return ; + case 'view': return {})} isViewing={isProc} hookData={hookData} />; + case 'copy': return ; + default: return null; + } + })} + {customActions.map((ca) => ( + + ))} +
+ + )} + {detectedColumns.map((col) => { + const cv = row[col.key]; + const cCls = col.cellClassName ? col.cellClassName(cv, row) : ''; + const aStyle = _columnAlignStyle(col); + return ( + + {formatCellValue(cv, col, row)} + + ); + })} + + ); + }; + return (
>({ ); })} - {isExpanded && groupRows.map((row, rowIndex) => { + {isExpanded && groupRows.map((row) => { const globalIndex = displayData.indexOf(row); - const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, globalIndex) : {}; - return ( - onRowClick?.(row, globalIndex)} - draggable={rowDraggable} - onDragStart={rowDraggable && onRowDragStart ? (e) => onRowDragStart(e, row) : undefined} - {...Object.fromEntries( - Object.entries(dataAttributes).map(([key, value]) => [`data-${key}`, value]) - )} - > - {selectable && ( - - handleRowSelect(row)} - onClick={(e) => e.stopPropagation()} - disabled={isRowSelectable && !isRowSelectable(row)} - title={ - isRowSelectable && !isRowSelectable(row) - ? t('Dieses Element kann nicht ausgewählt werden') - : t('Dieses Element auswählen') - } - style={{ - opacity: isRowSelectable && !isRowSelectable(row) ? 0.4 : 1, - cursor: isRowSelectable && !isRowSelectable(row) ? 'not-allowed' : 'pointer' - }} - /> - - )} - {hasActionColumn && ( - -
{ - if (el) { - actionButtonsRefs.current.set(globalIndex, el); - } else { - actionButtonsRefs.current.delete(globalIndex); - } - }} - className={`${styles.actionButtons} ${shouldWrapActionButtons ? styles.actionButtonsWrap : ''}`} - > - {actionButtons.map((actionButton, actionIndex) => { - if (actionButton.visible && !actionButton.visible(row, hookData)) return null; - const actionTitle = typeof actionButton.title === 'function' - ? actionButton.title(row) - : actionButton.title; - let disabledResult: boolean | { disabled: boolean; message?: string } = false; - if (actionButton.disabled) { - disabledResult = actionButton.disabled(row, hookData); - } else if (row._permissions) { - if (actionButton.type === 'edit' && row._permissions.canUpdate === false) { - disabledResult = true; - } else if (actionButton.type === 'delete' && row._permissions.canDelete === false) { - disabledResult = true; - } - } - const isLoading = actionButton.loading ? actionButton.loading(row) : false; - const isProcessing = actionButton.isProcessing ? actionButton.isProcessing(row) : false; - const baseProps = { - row, disabled: disabledResult, loading: isLoading, - className: [compact ? actionBtnStyles.compact : '', actionButton.className ?? ''].filter(Boolean).join(' '), - title: actionTitle, - idField: actionButton.idField ?? 'id', nameField: actionButton.nameField ?? 'name', - typeField: actionButton.typeField ?? 'type', contentField: actionButton.contentField ?? 'content', - operationName: actionButton.operationName, loadingStateName: actionButton.loadingStateName - }; - switch (actionButton.type) { - case 'edit': - return ; - case 'delete': - return ; - case 'view': - return {})} isViewing={isProcessing} hookData={hookData} />; - case 'copy': - return ; - default: - return null; - } - })} - {customActions.map((customAction) => ( - - ))} -
- - )} - {detectedColumns.map(column => { - const cellValue = row[column.key]; - const customClassName = column.cellClassName ? column.cellClassName(cellValue, row) : ''; - const combinedClassName = `${styles.td} ${customClassName}`.trim(); - const alignStyle = _columnAlignStyle(column); - return ( - - {formatCellValue(cellValue, column, row)} - - ); - })} - - ); + return _renderDataRow(row, globalIndex); })} ); @@ -3276,97 +3244,6 @@ export function FormGeneratorTable>({ // Total colspan for group/breadcrumb/ungrouped rows const _totalColSpan = (selectable ? 1 : 0) + (hasActionColumn ? 1 : 0) + detectedColumns.length; - // ── Helper: render a single data row ────────────────── - const _renderDataRow = (row: T, index: number) => { - const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, index) : {}; - const rowId = _getRowId(row); - - return ( - onRowClick?.(row, index)} - draggable={rowDraggable} - onDragStart={(e) => { - if (rowDraggable && onRowDragStart) onRowDragStart(e, row); - }} - onDragEnd={() => {}} - {...Object.fromEntries(Object.entries(dataAttributes).map(([k, v]) => [`data-${k}`, v]))} - > - {selectable && ( - - handleRowSelect(row)} - onClick={(e) => e.stopPropagation()} - disabled={isRowSelectable && !isRowSelectable(row)} - title={isRowSelectable && !isRowSelectable(row) ? t('Dieses Element kann nicht ausgewählt werden') : t('Dieses Element auswählen')} - style={{ opacity: isRowSelectable && !isRowSelectable(row) ? 0.4 : 1, cursor: isRowSelectable && !isRowSelectable(row) ? 'not-allowed' : 'pointer' }} - /> - - )} - {hasActionColumn && ( - -
{ if (el) actionButtonsRefs.current.set(index, el); else actionButtonsRefs.current.delete(index); }} - className={`${styles.actionButtons} ${shouldWrapActionButtons ? styles.actionButtonsWrap : ''}`}> - {actionButtons.map((ab, ai) => { - if (ab.visible && !ab.visible(row, hookData)) return null; - const abTitle = typeof ab.title === 'function' ? ab.title(row) : ab.title; - let dis: boolean | { disabled: boolean; message?: string } = false; - if (ab.disabled) { dis = ab.disabled(row, hookData); } - else if (row._permissions) { - if (ab.type === 'edit' && row._permissions.canUpdate === false) dis = true; - else if (ab.type === 'delete' && row._permissions.canDelete === false) dis = true; - } - const isLd = ab.loading ? ab.loading(row) : false; - const isProc = ab.isProcessing ? ab.isProcessing(row) : false; - const bp = { row, disabled: dis, loading: isLd, className: [compact ? actionBtnStyles.compact : '', ab.className ?? ''].filter(Boolean).join(' '), title: abTitle, idField: ab.idField ?? 'id', nameField: ab.nameField ?? 'name', typeField: ab.typeField ?? 'type', contentField: ab.contentField ?? 'content', operationName: ab.operationName, loadingStateName: ab.loadingStateName }; - switch (ab.type) { - case 'edit': return ; - case 'delete': return ; - case 'view': return {})} isViewing={isProc} hookData={hookData} />; - case 'copy': return ; - default: return null; - } - })} - {customActions.map((ca) => ( - - ))} -
- - )} - {detectedColumns.map((col) => { - const cv = row[col.key]; - const cCls = col.cellClassName ? col.cellClassName(cv, row) : ''; - const aStyle = _columnAlignStyle(col); - return ( - - {formatCellValue(cv, col, row)} - - ); - })} - - ); - }; - // ── Strategy B view grouping: insert collapsible group headers ── if (effectiveGroupLayout && effectiveGroupLayout.bands.length > 0) { const rows: React.ReactNode[] = []; diff --git a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx index 8e31414..fa1087c 100644 --- a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx +++ b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx @@ -405,10 +405,12 @@ export function FormGeneratorTree({ onSelectionChange, onRefresh, onSendToChat, + allowCreateFolder = true, className, }: FormGeneratorTreeProps) { const { t } = useLanguage(); - const { confirm, ConfirmDialog } = useConfirm(); + const { confirm } = useConfirm(); + const { prompt, PromptDialog } = usePrompt(); const [nodes, setNodes] = useState[]>([]); const [expandedIds, setExpandedIds] = useState>(new Set()); const [selectedIds, setSelectedIds] = useState>(new Set()); @@ -695,6 +697,7 @@ export function FormGeneratorTree({ if (ownership === 'shared') return; if (draggingIds.size === 0) return; if (draggingIds.has(node.id)) return; + if (node.type !== 'folder') return; e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOverId(node.id);