UI Verbesserungen Gruppierung und Anwendung auf alle Seiten
This commit is contained in:
parent
aff9dcb7bd
commit
e7a79a3484
8 changed files with 241 additions and 24 deletions
|
|
@ -486,6 +486,56 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Items that live inside a group — subtle tint + left connector */
|
||||
.tr.groupedItem {
|
||||
border-left: 3px solid color-mix(in srgb, var(--color-primary, #4a6fa5) 35%, transparent);
|
||||
}
|
||||
|
||||
.tr.groupedItem:hover {
|
||||
background: color-mix(in srgb, var(--color-primary, #4a6fa5) 8%, var(--color-bg, #fff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchy: set `--row-tree-indent` on the <tr> (px). Same row shifts checkbox, actions, and every `.td`.
|
||||
* Folder rows attach this class from GroupRow.tsx; omit padding on `.folderCell` (inner strip uses `--group-indent`).
|
||||
*/
|
||||
.treeRowIndented {
|
||||
--row-tree-indent: 0px;
|
||||
}
|
||||
|
||||
.treeRowIndented > .selectColumn {
|
||||
box-sizing: border-box !important;
|
||||
padding-top: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
padding-bottom: 4px !important;
|
||||
padding-left: calc(4px + var(--row-tree-indent)) !important;
|
||||
}
|
||||
|
||||
.treeRowIndented > .actionsColumn {
|
||||
box-sizing: border-box !important;
|
||||
padding-top: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
padding-bottom: 4px !important;
|
||||
padding-left: calc(4px + var(--row-tree-indent)) !important;
|
||||
}
|
||||
|
||||
.treeRowIndented > .td {
|
||||
box-sizing: border-box !important;
|
||||
padding-top: 8px !important;
|
||||
padding-right: 12px !important;
|
||||
padding-bottom: 8px !important;
|
||||
padding-left: calc(12px + var(--row-tree-indent)) !important;
|
||||
}
|
||||
|
||||
.treeRowIndented > .folderCell:first-child {
|
||||
box-sizing: border-box !important;
|
||||
padding-left: calc(12px + var(--row-tree-indent)) !important;
|
||||
}
|
||||
|
||||
.treeRowIndented > .selectColumn + .folderCell {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Selection Column */
|
||||
.selectColumn {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -76,11 +76,15 @@ import {
|
|||
} from '../../../utils/attributeTypeMapper';
|
||||
import type { AttributeType } from '../../../utils/attributeTypeMapper';
|
||||
import { FaFilter, FaTrash } from 'react-icons/fa';
|
||||
import type { GroupBulkAction } from '../GroupingManager/GroupRow';
|
||||
import api from '../../../api';
|
||||
import { PeriodPicker } from '../../PeriodPicker';
|
||||
import type { PeriodValue } from '../../PeriodPicker';
|
||||
import { GroupFolderRow, BreadcrumbRow } from '../GroupingManager/GroupRow';
|
||||
import {
|
||||
GroupFolderRow,
|
||||
BreadcrumbRow,
|
||||
GROUP_TREE_INDENT_STEP_PX,
|
||||
type GroupBulkAction,
|
||||
} from '../GroupingManager/GroupRow';
|
||||
|
||||
/** A filter value can be a plain string, null (for empty/missing), or a
|
||||
* {value, label} object returned by FK-aware filter-values endpoints. */
|
||||
|
|
@ -3058,17 +3062,23 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
const rowId = _getRowId(row);
|
||||
const isDragging = draggedRowId === rowId;
|
||||
const willUngroup = isDragging && dragWillUngroup;
|
||||
const indentStyle: React.CSSProperties = indentLevel > 0
|
||||
? { borderLeft: `3px solid color-mix(in srgb, var(--color-primary, #4a6fa5) ${Math.min(indentLevel * 25, 60)}%, transparent)` }
|
||||
const isGrouped = indentLevel > 0;
|
||||
const rowBgStyle: React.CSSProperties = isGrouped
|
||||
? { background: 'color-mix(in srgb, var(--color-primary, #4a6fa5) 4%, transparent)' }
|
||||
: {};
|
||||
const ungroupStyle: React.CSSProperties = willUngroup
|
||||
? { borderLeft: '3px solid #d69e2e', opacity: 0.5 }
|
||||
? { outline: '2px solid #d69e2e', opacity: 0.5 }
|
||||
: {};
|
||||
/** Visual depth inside group tree (= folder depth + 1 for direct children — see `_renderGroup`). */
|
||||
const leadingIndentPx = isGrouped ? indentLevel * GROUP_TREE_INDENT_STEP_PX : 0;
|
||||
const treeIndentCss: React.CSSProperties | undefined =
|
||||
isGrouped ? { ['--row-tree-indent' as string]: `${leadingIndentPx}px` } : undefined;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={rowId || index}
|
||||
className={`${styles.tr} ${selectedIds.has(rowId) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
|
||||
style={{ ...indentStyle, ...ungroupStyle, display: hiddenByDrag ? 'none' : undefined }}
|
||||
className={`${styles.tr} ${selectedIds.has(rowId) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''} ${isGrouped ? styles.groupedItem : ''} ${isGrouped ? styles.treeRowIndented : ''}`}
|
||||
style={{ ...rowBgStyle, ...ungroupStyle, ...(treeIndentCss ?? {}), display: hiddenByDrag ? 'none' : undefined }}
|
||||
onClick={() => onRowClick?.(row, index)}
|
||||
draggable={groupingEnabled || rowDraggable}
|
||||
onDragStart={(e) => {
|
||||
|
|
@ -3111,7 +3121,10 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
{...Object.fromEntries(Object.entries(dataAttributes).map(([k, v]) => [`data-${k}`, v]))}
|
||||
>
|
||||
{selectable && (
|
||||
<td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
|
||||
<td
|
||||
className={styles.selectColumn}
|
||||
style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}
|
||||
>
|
||||
<input type="checkbox"
|
||||
checked={selectedIds.has(_getRowId(row))}
|
||||
onChange={() => handleRowSelect(row)}
|
||||
|
|
@ -3123,7 +3136,10 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
</td>
|
||||
)}
|
||||
{hasActionColumn && (
|
||||
<td className={styles.actionsColumn} style={{ width: `${currentActionsWidth}px`, minWidth: `${defaultActionsWidth}px` }}>
|
||||
<td
|
||||
className={styles.actionsColumn}
|
||||
style={{ 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) => {
|
||||
|
|
@ -3155,13 +3171,21 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
</div>
|
||||
</td>
|
||||
)}
|
||||
{detectedColumns.map(col => {
|
||||
{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 }}>
|
||||
<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>
|
||||
);
|
||||
|
|
@ -3181,9 +3205,25 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
const _visibleById = new Map<string, T>();
|
||||
displayData.forEach(row => _visibleById.set(_getRowId(row), row));
|
||||
|
||||
const _collectSelectableSubtreeIds = (n: typeof groupTree[0]): string[] => {
|
||||
const acc: string[] = [];
|
||||
for (const id of n.itemIds) {
|
||||
const r = _visibleById.get(id);
|
||||
if (!r) continue;
|
||||
if (isRowSelectable && !isRowSelectable(r)) continue;
|
||||
acc.push(id);
|
||||
}
|
||||
for (const sg of n.subGroups) acc.push(..._collectSelectableSubtreeIds(sg));
|
||||
return acc;
|
||||
};
|
||||
|
||||
const _renderGroup = (node: typeof groupTree[0], depth: number, inheritedHidden = false): React.ReactNode => {
|
||||
const visibleIds = node.itemIds.filter(id => _visibleById.has(id));
|
||||
const groupItems = visibleIds.map(id => _visibleById.get(id)!);
|
||||
const subtreeSelectableIds = _collectSelectableSubtreeIds(node);
|
||||
const subtreeSelectedCount = subtreeSelectableIds.filter(id => selectedIds.has(id)).length;
|
||||
const subtreeAllSelected = subtreeSelectableIds.length > 0 && subtreeSelectedCount === subtreeSelectableIds.length;
|
||||
const subtreePartial = subtreeSelectedCount > 0 && subtreeSelectedCount < subtreeSelectableIds.length;
|
||||
const userCollapsed = expandedGroups.has(`collapsed-${node.id}`);
|
||||
|
||||
// Visual expanded state (chevron icon, hover-expand override, drag-collapse override)
|
||||
|
|
@ -3241,7 +3281,24 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
<GroupFolderRow
|
||||
node={node}
|
||||
depth={depth}
|
||||
colSpan={_totalColSpan}
|
||||
tableCells={{
|
||||
showSelect: selectable,
|
||||
dataColumnsCount: detectedColumns.length + (hasActionColumn ? 1 : 0),
|
||||
selectClassName: styles.selectColumn,
|
||||
selectTdStyle: { width: '40px', minWidth: '40px', maxWidth: '40px' },
|
||||
}}
|
||||
subtreeSelect={selectable ? {
|
||||
checked: subtreeAllSelected,
|
||||
indeterminate: subtreePartial,
|
||||
disabled: subtreeSelectableIds.length === 0,
|
||||
onToggle: () => {
|
||||
if (subtreeSelectableIds.length === 0) return;
|
||||
const next = new Set(selectedIds);
|
||||
if (subtreeAllSelected) subtreeSelectableIds.forEach(id => next.delete(id));
|
||||
else subtreeSelectableIds.forEach(id => next.add(id));
|
||||
_notifySelection(next);
|
||||
},
|
||||
} : undefined}
|
||||
visibleCount={visibleIds.length}
|
||||
isExpanded={isExp}
|
||||
isEditing={editingGroupId === node.id}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@
|
|||
border-left: 3px solid #d69e2e;
|
||||
}
|
||||
|
||||
/* Folder subtree selection (aligned with tbody .tr.selected) */
|
||||
.groupFolderRow.folderRowSubtreeFull {
|
||||
background: rgba(124, 109, 216, 0.08);
|
||||
background: rgba(var(--color-secondary-rgb), 0.08);
|
||||
}
|
||||
|
||||
.groupFolderRow.folderRowSubtreePartial {
|
||||
background: rgba(124, 109, 216, 0.04);
|
||||
background: rgba(var(--color-secondary-rgb), 0.04);
|
||||
}
|
||||
|
||||
.folderCell {
|
||||
padding: 0 !important;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
|||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
import { useConfirm } from '../../../hooks/useConfirm';
|
||||
import styles from './GroupRow.module.css';
|
||||
import fgTableCss from '../FormGeneratorTable/FormGeneratorTable.module.css';
|
||||
import type { TableGroupNode } from '../FormGeneratorTable/FormGeneratorTable';
|
||||
import { FaFolder, FaFolderOpen, FaList, FaPen, FaPlus } from 'react-icons/fa';
|
||||
|
||||
|
|
@ -18,14 +19,36 @@ export interface GroupBulkAction {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/** Horizontal shift per nesting level — keep in sync with item rows (`FormGeneratorTable`). */
|
||||
export const GROUP_TREE_INDENT_STEP_PX = 20;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GroupFolderRow
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Folder row: optional select column, then one merged cell for folder UI (spans actions + data cols — no blank actions column). */
|
||||
export interface GroupFolderTableCells {
|
||||
showSelect: boolean;
|
||||
/** `<td colSpan>` for folder strip = `detectedColumns.length` + (1 if table has an actions column). */
|
||||
dataColumnsCount: number;
|
||||
selectClassName: string;
|
||||
selectTdStyle?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface GroupFolderRowProps {
|
||||
node: TableGroupNode;
|
||||
depth: number;
|
||||
colSpan: number;
|
||||
/** Checkbox for “whole subtree”: select / clear all selectable visible items under this folder. */
|
||||
subtreeSelect?: {
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
disabled: boolean;
|
||||
onToggle: () => void;
|
||||
};
|
||||
/** When set, use split `<td>` layout; omit single-cell colspan. */
|
||||
tableCells?: GroupFolderTableCells;
|
||||
/** Legacy single spanning cell — only used when `tableCells` is omitted. */
|
||||
colSpan?: number;
|
||||
visibleCount: number;
|
||||
isExpanded: boolean;
|
||||
isEditing: boolean;
|
||||
|
|
@ -60,6 +83,8 @@ interface GroupFolderRowProps {
|
|||
export function GroupFolderRow({
|
||||
node,
|
||||
depth,
|
||||
subtreeSelect,
|
||||
tableCells,
|
||||
colSpan,
|
||||
visibleCount,
|
||||
isExpanded,
|
||||
|
|
@ -87,8 +112,15 @@ export function GroupFolderRow({
|
|||
const { t } = useLanguage();
|
||||
const { ConfirmDialog } = useConfirm();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const subtreeCbRef = useRef<HTMLInputElement>(null);
|
||||
const totalCount = node.itemIds.length;
|
||||
|
||||
useEffect(() => {
|
||||
const el = subtreeCbRef.current;
|
||||
if (!el || !subtreeSelect) return;
|
||||
el.indeterminate = subtreeSelect.indeterminate;
|
||||
}, [subtreeSelect?.indeterminate, subtreeSelect?.checked, subtreeSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
|
|
@ -96,26 +128,55 @@ export function GroupFolderRow({
|
|||
}
|
||||
}, [isEditing]);
|
||||
|
||||
const indentPx = depth * 20;
|
||||
const indentPx = depth * GROUP_TREE_INDENT_STEP_PX;
|
||||
|
||||
const _rowClass = [
|
||||
styles.groupFolderRow,
|
||||
tableCells ? fgTableCss.treeRowIndented : '',
|
||||
isDragOver ? styles.dragOver : '',
|
||||
isDragOverFromGroup ? styles.dragOverGroup : '',
|
||||
isDraggingOut ? styles.draggingOut : '',
|
||||
subtreeSelect?.checked && !subtreeSelect?.disabled ? styles.folderRowSubtreeFull : '',
|
||||
subtreeSelect?.indeterminate && !subtreeSelect?.checked ? styles.folderRowSubtreePartial : '',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
const mergedColSpan =
|
||||
tableCells
|
||||
? tableCells.dataColumnsCount
|
||||
: (colSpan ?? 1);
|
||||
|
||||
const folderStripStyle =
|
||||
({
|
||||
'--group-indent': `${indentPx}px`,
|
||||
...(tableCells
|
||||
? { ['--row-tree-indent' as string]: `${depth * GROUP_TREE_INDENT_STEP_PX}px` }
|
||||
: {}),
|
||||
}) as React.CSSProperties;
|
||||
|
||||
const guardDragDecor = (
|
||||
e: React.DragEvent,
|
||||
relay: React.DragEventHandler | undefined,
|
||||
) => {
|
||||
const el = e.target as HTMLElement;
|
||||
if (el.closest('input, button, textarea, label')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
relay?.(e);
|
||||
};
|
||||
|
||||
const folderCells = (
|
||||
<>
|
||||
{typeof document !== 'undefined' && ReactDOM.createPortal(<ConfirmDialog />, document.body)}
|
||||
|
||||
<tr
|
||||
className={_rowClass}
|
||||
style={{ '--group-indent': `${indentPx}px`, display: hidden ? 'none' : undefined } as React.CSSProperties}
|
||||
style={{ ...folderStripStyle, display: hidden ? 'none' : undefined } as React.CSSProperties}
|
||||
draggable={!isEditing}
|
||||
onDragStart={onGroupDragStart}
|
||||
onDrag={onGroupDrag}
|
||||
onDragEnd={onGroupDragEnd}
|
||||
onDragStart={(e) => guardDragDecor(e, onGroupDragStart)}
|
||||
onDrag={(e) => guardDragDecor(e, onGroupDrag)}
|
||||
onDragEnd={(e) => guardDragDecor(e, onGroupDragEnd)}
|
||||
// item drag-over
|
||||
onDragOver={(e) => {
|
||||
// distinguish item vs group drag via dataTransfer type
|
||||
|
|
@ -135,7 +196,23 @@ export function GroupFolderRow({
|
|||
onDragLeave={() => { onItemDragLeave(); onGroupDragLeave(); }}
|
||||
onDragEnter={(e) => e.preventDefault()}
|
||||
>
|
||||
<td colSpan={colSpan} className={styles.folderCell}>
|
||||
{tableCells?.showSelect && (
|
||||
<td className={tableCells.selectClassName} style={tableCells.selectTdStyle}>
|
||||
{subtreeSelect && (
|
||||
<input
|
||||
ref={subtreeCbRef}
|
||||
type="checkbox"
|
||||
checked={subtreeSelect.checked}
|
||||
disabled={subtreeSelect.disabled}
|
||||
onChange={(e) => { e.stopPropagation(); subtreeSelect.onToggle(); }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
title={node.name ? t('Auswahl unter „{name}“', { name: node.name }) : t('Auswahl dieser Gruppe')}
|
||||
aria-label={node.name ? t('Alle sichtbaren Einträge in „{name}“ auswählen', { name: node.name }) : t('Alle sichtbaren Einträge in dieser Gruppe auswählen')}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
<td colSpan={tableCells ? mergedColSpan : (colSpan ?? 1)} className={styles.folderCell}>
|
||||
<div className={styles.folderInner}>
|
||||
{/* Indent */}
|
||||
{indentPx > 0 && <span className={styles.indent} style={{ width: indentPx }} />}
|
||||
|
|
@ -143,6 +220,7 @@ export function GroupFolderRow({
|
|||
{/* Chevron */}
|
||||
<button
|
||||
className={`${styles.chevronBtn} ${isExpanded ? styles.chevronOpen : ''}`}
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); onToggle(); }}
|
||||
title={isExpanded ? t('Zuklappen') : t('Aufklappen')}
|
||||
tabIndex={-1}
|
||||
|
|
@ -198,6 +276,7 @@ export function GroupFolderRow({
|
|||
{bulkActions.map((action, i) => (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
className={`${styles.actionBtn} ${action.variant === 'danger' ? styles.actionBtnDanger : ''}`}
|
||||
title={action.title}
|
||||
disabled={!!action.disabled}
|
||||
|
|
@ -213,8 +292,8 @@ export function GroupFolderRow({
|
|||
{/* ── Group management: rename / add-subgroup ── */}
|
||||
{!isEditing && (
|
||||
<span className={styles.mgmtActions}>
|
||||
<button onClick={(e) => { e.stopPropagation(); onRename(); }} title={t('Umbenennen')} className={styles.mgmtBtn}><FaPen /></button>
|
||||
<button onClick={(e) => { e.stopPropagation(); onAddSub(); }} title={t('Untergruppe erstellen')} className={styles.mgmtBtn}><FaPlus /></button>
|
||||
<button type="button" onClick={(e) => { e.stopPropagation(); onRename(); }} title={t('Umbenennen')} className={styles.mgmtBtn}><FaPen /></button>
|
||||
<button type="button" onClick={(e) => { e.stopPropagation(); onAddSub(); }} title={t('Untergruppe erstellen')} className={styles.mgmtBtn}><FaPlus /></button>
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
|
@ -224,6 +303,8 @@ export function GroupFolderRow({
|
|||
</tr>
|
||||
</>
|
||||
);
|
||||
|
||||
return folderCells;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
deleteFiles as deleteFilesApi,
|
||||
type FolderInfo,
|
||||
} from '../api/fileApi';
|
||||
import type { TableGroupNode } from '../api/connectionApi';
|
||||
|
||||
export interface FilePreviewResult {
|
||||
success: boolean;
|
||||
|
|
@ -73,6 +74,7 @@ export function useUserFiles() {
|
|||
totalItems: number;
|
||||
totalPages: number;
|
||||
} | null>(null);
|
||||
const [groupTree, setGroupTree] = useState<TableGroupNode[]>([]);
|
||||
const { request, isLoading: loading, error } = useApiRequest<null, UserFile[]>();
|
||||
const { checkPermission } = usePermissions();
|
||||
|
||||
|
|
@ -172,6 +174,9 @@ export function useUserFiles() {
|
|||
if (data.pagination) {
|
||||
setPagination(data.pagination);
|
||||
}
|
||||
if (Array.isArray((data as any).groupTree)) {
|
||||
setGroupTree((data as any).groupTree);
|
||||
}
|
||||
} else {
|
||||
// Handle non-paginated response (backward compatibility)
|
||||
console.log('📋 Processing non-paginated response:', {
|
||||
|
|
@ -325,6 +330,7 @@ export function useUserFiles() {
|
|||
attributes,
|
||||
permissions,
|
||||
pagination,
|
||||
groupTree,
|
||||
fetchFileById,
|
||||
generateEditFieldsFromAttributes,
|
||||
ensureAttributesLoaded
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
type AttributeDefinition,
|
||||
type PaginationParams
|
||||
} from '../api/promptApi';
|
||||
import type { TableGroupNode } from '../api/connectionApi';
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export type { Prompt, AttributeDefinition, PaginationParams };
|
||||
|
|
@ -34,6 +35,7 @@ export function usePrompts() {
|
|||
totalItems: number;
|
||||
totalPages: number;
|
||||
} | null>(null);
|
||||
const [groupTree, setGroupTree] = useState<TableGroupNode[]>([]);
|
||||
const { request, isLoading: loading, error } = useApiRequest<null, Prompt[]>();
|
||||
const { checkPermission } = usePermissions();
|
||||
|
||||
|
|
@ -99,6 +101,9 @@ export function usePrompts() {
|
|||
if (data.pagination) {
|
||||
setPagination(data.pagination);
|
||||
}
|
||||
if (Array.isArray((data as any).groupTree)) {
|
||||
setGroupTree((data as any).groupTree);
|
||||
}
|
||||
} else {
|
||||
// Handle non-paginated response (backward compatibility)
|
||||
const items = Array.isArray(data) ? data : [];
|
||||
|
|
@ -454,10 +459,11 @@ export function usePrompts() {
|
|||
attributes,
|
||||
permissions,
|
||||
pagination,
|
||||
groupTree,
|
||||
fetchPromptById,
|
||||
generateEditFieldsFromAttributes,
|
||||
generateCreateFieldsFromAttributes,
|
||||
ensureAttributesLoaded // Generic function to ensure attributes are loaded
|
||||
ensureAttributesLoaded
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const FilesPage: React.FC = () => {
|
|||
error,
|
||||
refetch: tableRefetch,
|
||||
pagination,
|
||||
groupTree,
|
||||
fetchFileById,
|
||||
updateFileOptimistically,
|
||||
} = useUserFiles();
|
||||
|
|
@ -556,7 +557,9 @@ export const FilesPage: React.FC = () => {
|
|||
handleInlineUpdate,
|
||||
updateOptimistically: updateFileOptimistically,
|
||||
previewingFiles,
|
||||
groupTree,
|
||||
}}
|
||||
groupingConfig={{ contextKey: 'files', enabled: true }}
|
||||
emptyMessage={emptyTableMessage}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const PromptsPage: React.FC = () => {
|
|||
loading,
|
||||
error,
|
||||
refetch,
|
||||
groupTree,
|
||||
fetchPromptById,
|
||||
updateOptimistically,
|
||||
} = usePrompts();
|
||||
|
|
@ -236,7 +237,9 @@ export const PromptsPage: React.FC = () => {
|
|||
handleDelete: handlePromptDelete,
|
||||
handleInlineUpdate,
|
||||
updateOptimistically,
|
||||
groupTree,
|
||||
}}
|
||||
groupingConfig={{ contextKey: 'prompts', enabled: true }}
|
||||
emptyMessage={t('Keine Prompts gefunden')}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue