Merge pull request #77 from valueonag/feat/demo-system-readieness
merge fixes
This commit is contained in:
commit
77f4693f4b
2 changed files with 90 additions and 210 deletions
|
|
@ -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<T extends Record<string, any>>({
|
|||
batchActions.length > 0 ||
|
||||
(selectable && selectedIds.size > 0);
|
||||
|
||||
const _renderDataRow = (row: T, index: number) => {
|
||||
const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, index) : {};
|
||||
const rowId = _getRowId(row);
|
||||
return (
|
||||
<tr
|
||||
key={rowId || index}
|
||||
className={`${styles.tr} ${selectedIds.has(rowId) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
|
||||
onClick={() => 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 && (
|
||||
<td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
|
||||
<input type="checkbox"
|
||||
checked={selectedIds.has(rowId)}
|
||||
onChange={() => 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' }}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{hasActionColumn && (
|
||||
<td className={styles.actionsColumn} style={compact ? undefined : { width: `${currentActionsWidth}px`, minWidth: `${defaultActionsWidth}px` }}>
|
||||
<div ref={(el) => { 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 <EditActionButton key={`a-${ai}`} {...bp} onEdit={ab.onAction} hookData={hookData} />;
|
||||
case 'delete': return <DeleteActionButton key={`a-${ai}`} {...bp} containerRef={{ current: actionButtonsRefs.current.get(index) || null }} hookData={hookData} />;
|
||||
case 'view': return <ViewActionButton key={`a-${ai}`} {...bp} onView={ab.onAction || (() => {})} isViewing={isProc} hookData={hookData} />;
|
||||
case 'copy': return <CopyActionButton key={`a-${ai}`} {...bp} onCopy={ab.onAction} isCopying={isProc} contentField={ab.contentField} />;
|
||||
default: return null;
|
||||
}
|
||||
})}
|
||||
{customActions.map((ca) => (
|
||||
<CustomActionButton key={`ca-${ca.id}`} row={row} id={ca.id} icon={ca.icon}
|
||||
onClick={ca.onClick} visible={ca.visible} disabled={ca.disabled}
|
||||
loading={ca.loading} title={ca.title} className={ca.className}
|
||||
hookData={hookData} idField={ca.idField ?? 'id'} />
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
{detectedColumns.map((col) => {
|
||||
const cv = row[col.key];
|
||||
const cCls = col.cellClassName ? col.cellClassName(cv, row) : '';
|
||||
const aStyle = _columnAlignStyle(col);
|
||||
return (
|
||||
<td key={col.key} className={`${styles.td} ${cCls}`.trim()}
|
||||
style={{ width: columnWidths[col.key] || col.width || 150, minWidth: columnWidths[col.key] || col.width || 150, maxWidth: columnWidths[col.key] || col.width || 150, ...aStyle }}>
|
||||
{formatCellValue(cv, col, row)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.formGeneratorTable} ${compact ? styles.compactMode : ''} ${useSectionsGroupLayout ? styles.formGeneratorTableSectionsRoot : ''} ${className}`}
|
||||
|
|
@ -3157,116 +3232,9 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
);
|
||||
})}
|
||||
</tr>
|
||||
{isExpanded && groupRows.map((row, rowIndex) => {
|
||||
{isExpanded && groupRows.map((row) => {
|
||||
const globalIndex = displayData.indexOf(row);
|
||||
const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, globalIndex) : {};
|
||||
return (
|
||||
<tr
|
||||
key={`${groupKey}-row-${rowIndex}`}
|
||||
className={`${styles.tr} ${selectedIds.has(_getRowId(row)) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
|
||||
onClick={() => 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 && (
|
||||
<td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.has(_getRowId(row))}
|
||||
onChange={() => 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'
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{hasActionColumn && (
|
||||
<td
|
||||
className={styles.actionsColumn}
|
||||
style={compact ? undefined : { width: `${currentActionsWidth}px`, minWidth: `${defaultActionsWidth}px` }}
|
||||
>
|
||||
<div
|
||||
ref={(el) => {
|
||||
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 <EditActionButton key={`action-${actionIndex}`} {...baseProps} onEdit={actionButton.onAction} hookData={hookData} />;
|
||||
case 'delete':
|
||||
return <DeleteActionButton key={`action-${actionIndex}`} {...baseProps} containerRef={{ current: actionButtonsRefs.current.get(globalIndex) || null }} hookData={hookData} />;
|
||||
case 'view':
|
||||
return <ViewActionButton key={`action-${actionIndex}`} {...baseProps} onView={actionButton.onAction || (() => {})} isViewing={isProcessing} hookData={hookData} />;
|
||||
case 'copy':
|
||||
return <CopyActionButton key={`action-${actionIndex}`} {...baseProps} onCopy={actionButton.onAction} isCopying={isProcessing} contentField={actionButton.contentField} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
{customActions.map((customAction) => (
|
||||
<CustomActionButton key={`custom-${customAction.id}`} row={row} id={customAction.id} icon={customAction.icon}
|
||||
onClick={customAction.onClick} visible={customAction.visible} disabled={customAction.disabled}
|
||||
loading={customAction.loading} title={customAction.title} className={customAction.className}
|
||||
hookData={hookData} idField={customAction.idField ?? 'id'} />
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
{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 (
|
||||
<td key={column.key} className={combinedClassName}
|
||||
style={{ width: columnWidths[column.key] || column.width || 150, minWidth: columnWidths[column.key] || column.width || 150, maxWidth: columnWidths[column.key] || column.width || 150, ...alignStyle }}>
|
||||
{formatCellValue(cellValue, column, row)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
return _renderDataRow(row, globalIndex);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -3276,97 +3244,6 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
// 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 (
|
||||
<tr
|
||||
key={rowId || index}
|
||||
className={`${styles.tr} ${selectedIds.has(rowId) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
|
||||
onClick={() => 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 && (
|
||||
<td
|
||||
className={styles.selectColumn}
|
||||
style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}
|
||||
>
|
||||
<input type="checkbox"
|
||||
checked={selectedIds.has(_getRowId(row))}
|
||||
onChange={() => 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' }}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{hasActionColumn && (
|
||||
<td
|
||||
className={styles.actionsColumn}
|
||||
style={compact ? undefined : { width: `${currentActionsWidth}px`, minWidth: `${defaultActionsWidth}px` }}
|
||||
>
|
||||
<div ref={(el) => { 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 <EditActionButton key={`a-${ai}`} {...bp} onEdit={ab.onAction} hookData={hookData} />;
|
||||
case 'delete': return <DeleteActionButton key={`a-${ai}`} {...bp} containerRef={{ current: actionButtonsRefs.current.get(index) || null }} hookData={hookData} />;
|
||||
case 'view': return <ViewActionButton key={`a-${ai}`} {...bp} onView={ab.onAction || (() => {})} isViewing={isProc} hookData={hookData} />;
|
||||
case 'copy': return <CopyActionButton key={`a-${ai}`} {...bp} onCopy={ab.onAction} isCopying={isProc} contentField={ab.contentField} />;
|
||||
default: return null;
|
||||
}
|
||||
})}
|
||||
{customActions.map((ca) => (
|
||||
<CustomActionButton key={`ca-${ca.id}`} row={row} id={ca.id} icon={ca.icon}
|
||||
onClick={ca.onClick} visible={ca.visible} disabled={ca.disabled}
|
||||
loading={ca.loading} title={ca.title} className={ca.className}
|
||||
hookData={hookData} idField={ca.idField ?? 'id'} />
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
{detectedColumns.map((col) => {
|
||||
const cv = row[col.key];
|
||||
const cCls = col.cellClassName ? col.cellClassName(cv, row) : '';
|
||||
const aStyle = _columnAlignStyle(col);
|
||||
return (
|
||||
<td
|
||||
key={col.key}
|
||||
className={`${styles.td} ${cCls}`.trim()}
|
||||
style={{
|
||||
width: columnWidths[col.key] || col.width || 150,
|
||||
minWidth: columnWidths[col.key] || col.width || 150,
|
||||
maxWidth: columnWidths[col.key] || col.width || 150,
|
||||
...aStyle,
|
||||
}}
|
||||
>
|
||||
{formatCellValue(cv, col, row)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Strategy B view grouping: insert collapsible group headers ──
|
||||
if (effectiveGroupLayout && effectiveGroupLayout.bands.length > 0) {
|
||||
const rows: React.ReactNode[] = [];
|
||||
|
|
|
|||
|
|
@ -405,10 +405,12 @@ export function FormGeneratorTree<T = any>({
|
|||
onSelectionChange,
|
||||
onRefresh,
|
||||
onSendToChat,
|
||||
allowCreateFolder = true,
|
||||
className,
|
||||
}: FormGeneratorTreeProps<T>) {
|
||||
const { t } = useLanguage();
|
||||
const { confirm, ConfirmDialog } = useConfirm();
|
||||
const { confirm } = useConfirm();
|
||||
const { prompt, PromptDialog } = usePrompt();
|
||||
const [nodes, setNodes] = useState<TreeNode<T>[]>([]);
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
|
|
@ -695,6 +697,7 @@ export function FormGeneratorTree<T = any>({
|
|||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue