wiki/b-reference/frontend-nyla/formgenerator.md
2026-04-21 00:50:21 +02:00

12 KiB

FormGenerator -- Referenz

Uebersicht

Der FormGenerator besteht aus mehreren Komponenten:

Komponente Datei Zweck
FormGeneratorTable components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx Generische Datentabelle mit Pagination, Sortierung, Filterung, Suche, Selektion, Bulk-Actions
FormGeneratorControls components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx Toolbar: Suche, Pagination, Batch-Action-Buttons, "Select All Filtered"-Banner
FormGeneratorForm components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx Dynamische Formulare aus Backend-Attribut-Definitionen
FormGeneratorList components/FormGenerator/FormGeneratorList/FormGeneratorList.tsx Listen-Darstellung

Diese Referenz fokussiert auf FormGeneratorTable und das zugehoerige Backend-API-Pattern.


Page Layout Chain (Pflicht)

FormGeneratorTable rendert intern mit flex:1; min-height:0; overflow:hidden. Damit die Tabelle die volle Seitenbreite/-hoehe ausnuetzt und nicht abgeschnitten wird, muss die Eltern-Hierarchie eine bounded height chain liefern. Andernfalls passiert eines davon:

  • Tabelle ist 0 px hoch (kollabiert) -- nur Toolbar sichtbar.
  • Tabelle waechst ueber den Viewport hinaus, horizontaler Scroll fehlt -- letzte Spalten werden abgeschnitten.
  • Page-Container hat max-width -> Tabelle ist auf z.B. 800 px begrenzt.

Korrektes Pattern (verwendet PromptsPage, TrusteePositionsView, TrusteeDataTablesView)

import adminStyles from '../../admin/Admin.module.css';

export const MyPage: React.FC = () => (
  <div className={`${adminStyles.adminPage} ${adminStyles.adminPageFill}`}>
    <div className={adminStyles.pageHeader}>...</div>

    <div className={adminStyles.tableContainer}>
      <FormGeneratorTable {...props} />
    </div>
  </div>
);

Die drei CSS-Klassen aus frontend_nyla/src/pages/admin/Admin.module.css:

Klasse Wirkung
.adminPage display:flex; flex-direction:column; width:100%; box-sizing:border-box; flex:0 0 auto (Default fuer Standard-Seiten ohne Tabelle)
.adminPageFill flex:1 1 auto; min-height:0; overflow:hidden -- aktiviert die bounded height chain
.tableContainer flex:1; min-height:0; overflow:hidden; display:flex; flex-direction:column -- direktes Eltern-Element der Tabelle

FeatureView/MainLayout liefern bereits height:100% mit min-height:0 -- Page-Komponenten brauchen also nur adminPage adminPageFill und einen tableContainer direkt um die FormGeneratorTable.

Bei Tabs / Wrapper-Komponenten

Wenn die Tabelle in einem Tab-Wrapper (z.B. TrusteeDataTab) gemountet wird, muss jeder Wrapper die Flex-Chain weiterreichen. Pattern:

const _rootStyle: React.CSSProperties = {
  display: 'flex',
  flexDirection: 'column',
  flex: 1,
  minHeight: 0,
  width: '100%',
};
const _tableWrapStyle: React.CSSProperties = {
  flex: 1,
  minHeight: 0,
  display: 'flex',
  flexDirection: 'column',
  width: '100%',
};

Eine Toolbar oberhalb der Tabelle bekommt flexShrink: 0, der Tabellenwrapper flex: 1; minHeight: 0.

Anti-Patterns (verursachen das "Tabelle abgeschnitten"-Bug)

  • Outer-Wrapper mit max-width: 800px (z.B. .expenseImportSection aus TrusteeViews.module.css) -- begrenzt die Breite.
  • Outer <div> ohne flex:1; min-height:0 -- Tabelle kollabiert auf 0 px Hoehe.
  • .adminPage ohne .adminPageFill -- Default ist flex:0 0 auto, das innere .tableContainer flex:1 hat keinen Platz.
  • Padding auf einem Zwischen-Wrapper -- bricht das Box-Sizing der Chain.

FormGeneratorTable -- Props

Datenanbindung

Prop Typ Beschreibung
data T[] Anzuzeigende Datensaetze (aktuelle Seite)
columns ColumnConfig[] Spaltendefinitionen (key, label, type, sortable, filterable, searchable, filterOptions, width)
apiEndpoint string? Backend-Endpunkt fuer automatische Filter-/ID-Abfragen
hookData { refetch, pagination, fetchFilterValues? } Daten-Hook mit Refetch-Callback und Pagination-Metadata
supportsBackendPagination boolean Aktiviert serverseitige Pagination, Sortierung und Filterung

Interaktion

Prop Typ Beschreibung
selectable boolean Aktiviert Zeilen-Selektion (Checkboxen)
onSelectionChange (selectedIds: Set<string>) => void Callback bei Aenderung der Selektion
idField string Feld fuer eindeutige ID (Default: "id")
isRowSelectable (row: T) => boolean Bestimmt, ob eine Zeile selektierbar ist
batchActions BatchAction[] Bulk-Aktionen fuer selektierte Zeilen
actionButtons ActionButton[] Zeilen-Aktionen (Edit, Delete etc.)

BatchAction Interface

interface BatchAction<T> {
  label: string;
  icon?: React.ReactNode;
  loading?: boolean;
  onClick: (selectedRows: T[]) => void | Promise<void>;
  isApplicable?: (row: T) => boolean;
}

isApplicable ermoeglicht pro Action zu bestimmen, welche der selektierten Zeilen fuer diese Aktion qualifizieren. Der Button zeigt dann (applicableCount/totalSelected) und ist disabled wenn applicableCount === 0.


Unified Filter API

Prinzip

Alle Daten-Endpunkte unterstuetzen drei Modi ueber Query-Parameter auf dem gleichen Endpunkt:

Modus Query-Parameter Response Zweck
Normal (default) pagination={...} { items: T[], pagination: PaginationMetadata } Paginierte Datenliste
filterValues mode=filterValues&column=xxx&pagination={crossFilters} string[] Distinct-Werte fuer Spaltenfilter-Dropdown
ids mode=ids&pagination={filters} string[] Alle IDs der gefilterten Datensaetze

Kein separater /filter-values-Endpunkt. Alle alten /filter-values-Sub-Pfade wurden entfernt (Stand 2026-04-13).

Cross-Filtering

Bei mode=filterValues werden alle aktiven Filter ausser dem angeforderten Column-Filter angewendet (Cross-Filtering). So zeigt z.B. der Status-Filter nur Werte an, die bei den aktuell gesetzten Mandant- und Typ-Filtern vorkommen.

Beispiele

# Normale Liste (Seite 1, 20 Eintraege)
GET /api/users/?pagination={"page":1,"pageSize":20,"sort":[{"field":"name","direction":"asc"}]}

# Filter-Werte fuer Spalte "status" (mit Cross-Filter auf mandateLabel)
GET /api/users/?mode=filterValues&column=status&pagination={"filters":{"mandateLabel":"Demo AG"}}

# Alle IDs der gefilterten Ansicht (fuer "Select All Filtered")
GET /api/users/?mode=ids&pagination={"filters":{"status":"active"},"search":"admin"}

Backend-Implementierung

Zentrale Hilfsfunktionen in gateway/modules/routes/routeHelpers.py:

Funktion Zweck
handleFilterValuesMode(db, modelClass, column, ...) SQL-basierte Distinct-Werte via DB-Connector
handleIdsMode(db, modelClass, ...) SQL-basierte ID-Liste via DB-Connector
handleFilterValuesInMemory(items, column, ...) In-Memory Distinct-Werte (fuer enriched/aggregierte Listen)
handleIdsInMemory(items, ...) In-Memory ID-Liste
parseCrossFilterPagination(column, paginationJson) Parsed Pagination-JSON und entfernt den angeforderten Column-Filter
_applyFiltersAndSort(items, paginationParams) In-Memory Filterung und Sortierung
_extractDistinctValues(items, column, ...) Distinct-Werte aus Item-Liste extrahieren
paginateInMemory(items, paginationParams) In-Memory Paginierung

Route-Integration Pattern

@router.get("/", response_model=PaginatedResponse[MyModel])
def list_items(
    request: Request,
    pagination: Optional[str] = Query(None),
    mode: Optional[str] = Query(None, description="'filterValues' or 'ids'"),
    column: Optional[str] = Query(None, description="Column key"),
    context: RequestContext = Depends(getRequestContext),
):
    if mode in ("filterValues", "ids"):
        from modules.routes.routeHelpers import handleFilterValuesInMemory, handleIdsInMemory
        items = _buildFullList(context)
        if mode == "filterValues":
            if not column:
                raise HTTPException(status_code=400, detail="column required")
            return handleFilterValuesInMemory(items, column, pagination)
        return handleIdsInMemory(items, pagination)

    # Normal paginated list...

Selektion

ID-basierte Selektion

Die Selektion arbeitet mit IDs (nicht Zeilen-Indizes). Das idField-Prop bestimmt, welches Feld als eindeutiger Schluessel verwendet wird (Default: "id").

State Typ Beschreibung
selectedIds Set<string> Menge der selektierten IDs

Selektion-Reset

Die Selektion wird automatisch zurueckgesetzt bei:

  • Seitenwechsel (currentPage)
  • Seitengroesse-Aenderung (pageSize)
  • Filter-Aenderung
  • Suchbegriff-Aenderung
  • Sortier-Aenderung

Select All Filtered

Wenn apiEndpoint und supportsBackendPagination gesetzt sind, zeigt die Toolbar einen "Alle X Eintraege auswaehlen"-Button. Dieser ruft GET {apiEndpoint}?mode=ids&pagination={currentFilters} auf und fuegt alle zurueckgegebenen IDs zur Selektion hinzu.

Ein "Auswahl aufheben"-Button erscheint, wenn "Select All Filtered" aktiv ist.

Row-Level Delete

Wenn eine Zeile geloescht wird (via actionButtons Delete), wird deren ID automatisch aus selectedIds entfernt.


FilterValuesList (Spaltenfilter-Dropdown)

Wenn ein filterbarer Spalten-Header geklickt wird, laedt das Dropdown die Distinct-Werte:

  1. column.filterOptions (statische Enum) -- wird direkt verwendet, kein Backend-Call
  2. hookData.fetchFilterValues(columnKey, crossFilters) -- falls im Hook bereitgestellt
  3. GET {apiEndpoint}?mode=filterValues&column=xxx&pagination={currentFilters} -- automatisch

Bei mehr als 10 Werten erscheint ein Suchfeld (Client-seitige Filterung der geladenen Werte).

Boolean-Spalten rendern als "Ja"/"Nein". Datum-Spalten rendern als Range-Picker.


Migrierte Endpunkte (Stand 2026-04-13)

Alle folgenden Endpunkte unterstuetzen mode=filterValues und mode=ids:

Core Routes

Route-Datei Endpunkt Methode
routeDataUsers.py GET /api/users/ SQL + In-Memory (je nach Scope)
routeDataConnections.py GET /api/connections/ In-Memory
routeDataPrompts.py GET /api/prompts/ In-Memory
routeInvitations.py GET /api/invitations/ In-Memory
routeSubscription.py GET /api/subscriptions/admin/all In-Memory
routeAdminFeatures.py GET /api/admin/features/instances In-Memory
routeAdminFeatures.py GET /api/admin/features/templates/roles In-Memory
routeAdminFeatures.py GET /api/admin/features/instances/{id}/users In-Memory
routeAdminRbacRules.py GET /api/admin/rbac/roles In-Memory
routeDataMandates.py GET /api/mandates/ SQL + In-Memory (je nach Scope)
routeDataMandates.py GET /api/mandates/{id}/users In-Memory
routeDataFiles.py GET /api/files/list SQL + In-Memory Fallback
routeWorkflowDashboard.py GET /api/automation/dashboard/ (Runs) Enriched + SQL
routeWorkflowDashboard.py GET /api/automation/dashboard/workflows Enriched + SQL
routeBilling.py GET /api/billing/view/users/transactions Billing-Interface

Feature Routes

Route-Datei Endpunkt Methode
routeFeatureTrustee.py GET /api/trustee/{id}/documents RBAC SQL + In-Memory Fallback
routeFeatureTrustee.py GET /api/trustee/{id}/positions RBAC SQL + In-Memory Fallback
routeFeatureRealEstate.py GET /api/realestate/{id}/projects In-Memory
routeFeatureRealEstate.py GET /api/realestate/{id}/parcels In-Memory