gateway/docs/frontend-documentation/dynamic-forms-and-pagination.md

689 lines
22 KiB
Markdown

# Dynamic Forms and Pagination - Generic Frontend Patterns
This document describes the generic, reusable patterns for dynamic form generation and pagination that apply across all entity types in the frontend. These patterns enable completely backend-driven UI generation with no hardcoding.
## Table of Contents
1. [Overview](#overview)
2. [Backend Metadata System](#backend-metadata-system)
3. [Dynamic Form Generation](#dynamic-form-generation)
4. [Pagination System](#pagination-system)
5. [Search and Filtering](#search-and-filtering)
6. [Sorting](#sorting)
7. [Localization](#localization)
8. [Implementation Patterns](#implementation-patterns)
---
## Overview
The frontend uses a **completely backend-driven approach** for generating forms, tables, filters, and pagination controls. All field definitions, labels, validation rules, and UI structure come from the backend through the attribute definition system.
### Key Principles
- **No Hardcoding**: Field names, labels, types, validation rules, and options are never hardcoded
- **Backend-Driven**: All UI components generated from backend metadata
- **Entity-Agnostic**: Same patterns work for workflows, users, mandates, files, prompts, automations, etc.
- **Automatic Updates**: When backend adds new fields, frontend automatically displays them without code changes
- **Localization**: All labels and options support multiple languages from backend
### Applicable Entity Types
These patterns apply to any entity type that has:
- An attributes endpoint: `GET /api/attributes/{EntityType}`
- A list endpoint supporting pagination: `GET /api/{entities}/?pagination=...`
- CRUD operations: Create, Read, Update, Delete
Examples: `ChatWorkflow`, `User`, `Mandate`, `FileItem`, `Prompt`, `AutomationDefinition`, `ChatMessage`, `ChatLog`, etc.
---
## Backend Metadata System
### Attribute Definition Endpoint
Every entity type exposes an attributes endpoint that provides complete metadata:
```
GET /api/attributes/{EntityType}
```
**Examples:**
- `GET /api/attributes/ChatWorkflow`
- `GET /api/attributes/User`
- `GET /api/attributes/Mandate`
- `GET /api/attributes/FileItem`
- `GET /api/attributes/Prompt`
### Attribute Definition Structure
Each attribute returned from the endpoint contains:
- **`name`** - Field name (e.g., "status", "name", "email", "createdAt")
- **`type`** - Field data type (e.g., "text", "select", "integer", "timestamp", "checkbox", "email", "url")
- **`label`** - Localized field label (object with language keys: `{"en": "English Label", "fr": "French Label"}`)
- **`description`** - Field description text (for help text/tooltips)
- **`required`** - Boolean indicating if field is required for validation
- **`readonly`** - Boolean indicating if field is read-only (cannot be edited)
- **`editable`** - Boolean indicating if field can be edited (inverse of readonly)
- **`visible`** - Boolean indicating if field should be displayed in UI
- **`options`** - Array of options for select fields (each option has `value` and localized `label`)
### Response Format
The attributes endpoint returns:
```json
{
"entityType": "ChatWorkflow",
"attributes": [
{
"name": "status",
"type": "select",
"label": {"en": "Status", "fr": "Statut"},
"description": "Current status of the workflow",
"required": false,
"readonly": false,
"editable": true,
"visible": true,
"options": [
{"value": "running", "label": {"en": "Running", "fr": "En cours"}},
{"value": "completed", "label": {"en": "Completed", "fr": "Terminé"}}
]
},
{
"name": "name",
"type": "text",
"label": {"en": "Name", "fr": "Nom"},
"description": "Name of the workflow",
"required": true,
"readonly": false,
"editable": true,
"visible": true
}
// ... more attributes
]
}
```
---
## Dynamic Form Generation
Dynamic forms are generated entirely from backend attribute definitions. The same pattern works for create forms, edit forms, and inline editing.
### Form Generation Process
1. **Fetch Attribute Definitions**
- Call `GET /api/attributes/{EntityType}` when form component mounts
- Store attribute definitions in component state
2. **Filter Attributes**
- For edit forms: Filter where `visible: true` AND `editable: true`
- For create forms: Filter where `visible: true` AND `editable: true` (may exclude readonly fields)
- For display-only: Filter where `visible: true`
3. **Generate Form Fields**
- Iterate through filtered attributes
- Generate appropriate input component based on `type`
- Use `label` for field labels (localized)
- Use `description` for help text/tooltips
- Use `required` to show required indicators
4. **Handle Form Submission**
- Validate form using attribute metadata
- Send form data to appropriate endpoint (POST for create, PUT for update)
- Handle success/error responses
### Field Type to Input Component Mapping
Based on the `type` property, generate appropriate input components:
- **`text`** → Text input (single line)
- **`textarea`** → Multi-line text area
- **`select`** → Dropdown/select input with options from `options` array
- **`integer`** → Number input (integer only)
- **`float`** or **`number`** → Number input (decimal allowed)
- **`timestamp`** → Date/time picker
- **`date`** → Date picker (date only)
- **`time`** → Time picker (time only)
- **`checkbox`** → Checkbox input (boolean)
- **`email`** → Email input (with email validation)
- **`url`** → URL input (with URL validation)
- **`password`** → Password input (masked)
- **`file`** → File upload input
### Form Field Rendering Rules
For each form field:
1. **Field Label**
- Use `label[userLanguage]` or `label.en` as fallback
- Show required indicator (asterisk, etc.) if `required: true`
2. **Input Component**
- Generate based on `type` property
- For `select` fields: Populate options from `options` array, use localized labels
- For `integer`/`number` fields: Set appropriate min/max constraints if available
- For `timestamp`/`date`/`time` fields: Use appropriate date/time picker component
3. **Help Text**
- Display `description` as help text or tooltip
- Show below or next to input field
4. **Validation**
- Mark field as required if `required: true`
- Validate type (e.g., integer fields must be numbers)
- Validate select fields (value must be in options array)
- Validate email/url fields with appropriate regex patterns
5. **Disabled State**
- Disable input if `readonly: true` or `editable: false`
- Show read-only indicator if applicable
### Form Validation
Before form submission:
1. **Required Field Validation**
- Check all fields where `required: true` have non-empty values
- Show error messages for missing required fields
2. **Type Validation**
- Validate integer fields contain valid integers
- Validate number fields contain valid numbers
- Validate email fields contain valid email addresses
- Validate URL fields contain valid URLs
- Validate timestamp/date fields contain valid dates
3. **Option Validation**
- For select fields, ensure value exists in `options` array
- Show error if invalid option selected
4. **Custom Validation**
- Apply any additional validation rules from backend metadata if available
### Form Submission
On form submit:
1. **Collect Form Data**
- Gather values from all form fields
- Use attribute `name` as keys in form data object
2. **Prepare Request**
- For create: `POST /api/{entities}` with form data
- For update: `PUT /api/{entities}/{id}` with form data
- Include only changed fields (for updates) or all form data (for creates)
3. **Handle Response**
- On success: Show success message, navigate or refresh as appropriate
- On error: Display validation errors from backend, highlight invalid fields
---
## Pagination System
Pagination is a unified system that works across all list endpoints. It supports pagination, sorting, filtering, and general search in a consistent way.
### Pagination Endpoint Pattern
All list endpoints support pagination through a query parameter:
```
GET /api/{entities}/?pagination={JSON_ENCODED_PAGINATION_PARAMS}
```
**Examples:**
- `GET /api/workflows/?pagination={"page":1,"pageSize":20,"sort":[],"filters":null}`
- `GET /api/users/?pagination={"page":1,"pageSize":20,"sort":[{"field":"name","direction":"asc"}],"filters":{"status":"active"}}`
- `GET /api/files/list?pagination={"page":2,"pageSize":50,"sort":[],"filters":{"search":"report"}}`
### Pagination Request Parameters
The `pagination` query parameter is a JSON-encoded object with the following structure:
**`PaginationParams` Structure:**
- **`page`** - Current page number (1-based, minimum 1)
- **`pageSize`** - Number of items per page (minimum 1, maximum 1000)
- **`sort`** - Array of sort field configurations
- Each sort field contains:
- `field` - Field name to sort by (must match an attribute name)
- `direction` - Sort direction: "asc" or "desc"
- **`filters`** - Filter criteria dictionary (see [Search and Filtering](#search-and-filtering))
**Example:**
```json
{
"page": 1,
"pageSize": 20,
"sort": [
{"field": "lastActivity", "direction": "desc"},
{"field": "name", "direction": "asc"}
],
"filters": {
"search": "invoice",
"status": "running"
}
}
```
### Pagination Response Structure
All paginated endpoints return data in this format:
```json
{
"items": [
// Array of entity objects
],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 45,
"totalPages": 3,
"sort": [
{"field": "lastActivity", "direction": "desc"}
],
"filters": {
"search": "invoice",
"status": "running"
}
}
}
```
**`PaginationMetadata` Structure:**
- **`currentPage`** - Current page number (1-based)
- **`pageSize`** - Number of items per page
- **`totalItems`** - Total number of items across all pages (after filters applied)
- **`totalPages`** - Total number of pages (calculated from totalItems / pageSize)
- **`sort`** - Current sort configuration applied (array of SortField objects)
- **`filters`** - Current filters applied (mirrors request filters)
### Pagination UI Components
Generate pagination controls using the `pagination` metadata from response:
1. **Page Information Display**
- Show "Page X of Y" (using `currentPage` and `totalPages`)
- Show "Showing X-Y of Z items" (calculate from `currentPage`, `pageSize`, `totalItems`)
2. **Page Navigation Controls**
- Previous page button (disabled if `currentPage === 1`)
- Next page button (disabled if `currentPage === totalPages`)
- Page number buttons (show current page and adjacent pages)
- First page button (if not on first page)
- Last page button (if not on last page)
3. **Page Size Selector**
- Dropdown to change `pageSize` (common options: 10, 20, 50, 100)
- When changed, reset to page 1 and refetch
4. **Page Change Handler**
- When user clicks page number or navigation button:
- Update `page` in pagination parameters
- Preserve all filters and sort settings
- Refetch data with updated pagination
### Non-Paginated Requests
If user wants all items without pagination:
- Omit `pagination` parameter entirely: `GET /api/{entities}/`
- Backend returns all items (still wrapped in `PaginatedResponse` with `pagination: null`)
---
## Search and Filtering
The pagination system supports both general search and field-specific filtering. Both are combined in the `filters` object.
### General Search
General search searches across all text fields in the entity.
**Implementation:**
1. Display a single search input box (not field-specific)
2. When user types, update `filters.search` in pagination parameters
3. Debounce search input (wait 300-500ms after user stops typing before sending request)
4. Reset to page 1 when search query changes
5. Combine search with field-specific filters (both are in the `filters` object)
**Filter Structure:**
```json
{
"filters": {
"search": "invoice"
}
}
```
### Field-Specific Filtering
Field-specific filters allow filtering by individual fields with various operators.
**Filter Structure:**
Simple equals filter:
```json
{
"filters": {
"status": "running"
}
}
```
Filter with operator:
```json
{
"filters": {
"name": {
"operator": "contains",
"value": "workflow"
}
}
}
```
**Supported Operators:**
- **`equals`** or **`eq`** - Exact match
- **`contains`** - Substring match (case-insensitive for strings)
- **`startsWith`** - String starts with (case-insensitive)
- **`endsWith`** - String ends with (case-insensitive)
- **`gt`** - Greater than (for numbers/dates)
- **`gte`** - Greater than or equal
- **`lt`** - Less than
- **`lte`** - Less than or equal
- **`in`** - Value in list (array of values)
- **`notIn`** - Value not in list (array of values)
**Multiple Filters:**
All filters are combined with AND logic:
```json
{
"filters": {
"search": "invoice",
"status": "running",
"currentRound": {
"operator": "gt",
"value": 0
}
}
}
```
### Filter Control Generation
Generate filter controls dynamically from attribute definitions:
1. **Fetch Attribute Definitions**
- Call `GET /api/attributes/{EntityType}` when filter component mounts
- Filter attributes where `visible: true` to determine which filters to show
2. **Generate Filter UI by Type**
- **`text`** fields → Text input filter (supports "contains", "equals", "startsWith", "endsWith" operators)
- **`select`** fields → Dropdown filter with options from `options` array (use localized labels)
- **`integer`** or **`number`** fields → Number range filter (min/max inputs) or single number input with comparison operators (gt, gte, lt, lte)
- **`timestamp`** or **`date`** fields → Date range picker or single date picker with comparison operators
- **`checkbox`** fields → Boolean toggle filter (true/false/any)
3. **Filter Labels**
- Use `label` property for filter labels (localized)
- Display filter label next to filter control
4. **Apply Filters**
- When user applies filter, update `filters[fieldName]` in pagination parameters
- Support multiple filters simultaneously
- Reset to page 1 when filters change
- Refetch data with updated filters
5. **Active Filter Indicators**
- Display active filters as chips/badges showing field label and value
- Allow removing individual filters by removing them from `filters` object
- Show "Clear all filters" button when filters are active
### Combined Search and Filters
Search and field filters work together:
- Both are stored in the `filters` object
- All filters are combined with AND logic
- When any filter changes, combine all and refetch
- Preserve all active filters when sorting or changing pages
---
## Sorting
Sorting allows users to order list items by one or more fields.
### Sort Configuration
Sorting is configured in the `sort` array of pagination parameters:
```json
{
"sort": [
{"field": "lastActivity", "direction": "desc"},
{"field": "name", "direction": "asc"}
]
}
```
- Multiple sort fields create multi-level sorting (sorts by first field, then by second field for ties)
- Sort fields are applied in order (first field is primary sort, second is secondary sort, etc.)
### Sort UI Implementation
1. **Column Header Sorting**
- Make column headers clickable for sortable columns
- Show sort indicator (arrow up/down) next to column header when sorted
- Clicking a column header:
- If not currently sorted by that column: Add sort field with "asc" direction
- If sorted ascending: Change to "desc"
- If sorted descending: Remove sort field (or change to "asc" if keeping)
- Update `sort` array in pagination parameters
- Preserve all filters and search
- Refetch data with updated sort
2. **Sort Indicator Display**
- Show up arrow (↑) for ascending sort
- Show down arrow (↓) for descending sort
- Show both arrows when column is sortable but not currently sorted
- Hide sort indicators for non-sortable columns
3. **Multi-Level Sort**
- When user clicks a second column header, add it to the `sort` array
- Display multiple sort indicators if multiple columns are sorted
- Allow removing individual sort fields
### Sortable Columns
Determine which columns are sortable:
- All visible columns can potentially be sortable
- Backend handles sorting logic, so frontend can attempt to sort any field
- If backend doesn't support sorting a field, it will ignore that sort field
- Readonly fields may still be sortable (e.g., timestamps, IDs)
---
## Localization
All labels and options support multiple languages from the backend.
### Language Detection
1. **User Language Preference**
- Get user's preferred language from user settings
- Fall back to browser locale if user setting not available
- Default to "en" (English) if no language detected
2. **Label Access Pattern**
- Access localized labels from `label` object: `label[userLanguage]`
- Fall back to `label.en` if label for current language is missing
- For select options: `option.label[userLanguage]` or `option.label.en` as fallback
### Localized Components
Apply localization to:
1. **Form Fields**
- Field labels: `attribute.label[userLanguage]`
- Help text/descriptions: `attribute.description` (may be localized if backend provides)
- Select options: `option.label[userLanguage]`
- Validation error messages: Use localized messages
2. **Table Columns**
- Column headers: `attribute.label[userLanguage]`
- Cell values for select fields: `option.label[userLanguage]`
3. **Filter Controls**
- Filter labels: `attribute.label[userLanguage]`
- Filter options: `option.label[userLanguage]`
4. **Pagination Controls**
- "Page X of Y" text: Use localized strings
- "Showing X-Y of Z items" text: Use localized strings
- Button labels (Previous, Next, etc.): Use localized strings
---
## Implementation Patterns
### Pattern 1: Generic List Page Component
A reusable list page component that works for any entity type:
**Component Structure:**
1. Accept `entityType` as prop (e.g., "ChatWorkflow", "User", "Mandate")
2. Fetch attributes: `GET /api/attributes/{entityType}`
3. Fetch list data: `GET /api/{entities}/?pagination=...`
4. Generate table columns from attributes
5. Generate filter controls from attributes
6. Handle pagination, search, filtering, and sorting
7. Handle row clicks (navigate to detail page)
**State Management:**
- Store attribute definitions
- Store pagination parameters (page, pageSize, sort, filters)
- Store pagination metadata from response
- Store list items
- Store loading/error states
**User Interactions:**
- Search input → Update `filters.search`
- Filter controls → Update `filters[fieldName]`
- Column header clicks → Update `sort` array
- Page navigation → Update `page`
- Page size change → Update `pageSize`, reset to page 1
### Pattern 2: Generic Form Component
A reusable form component that works for any entity type:
**Component Structure:**
1. Accept `entityType` and `mode` as props ("create" or "edit")
2. Accept `entityId` as prop (for edit mode)
3. Fetch attributes: `GET /api/attributes/{entityType}`
4. Fetch entity data (for edit mode): `GET /api/{entities}/{id}`
5. Generate form fields from attributes
6. Handle form submission
7. Handle validation
**Form Field Generation:**
- Filter attributes: `visible: true` AND `editable: true`
- Generate input components based on `type`
- Apply labels, descriptions, required indicators
- Handle validation
**Form Submission:**
- Validate form using attribute metadata
- For create: `POST /api/{entities}` with form data
- For update: `PUT /api/{entities}/{id}` with form data
- Handle success/error responses
### Pattern 3: Generic Table Component
A reusable table component that works for any entity type:
**Component Structure:**
1. Accept `attributes` and `items` as props
2. Generate columns from attributes (filter `visible: true`)
3. Render table rows with formatted cell values
4. Handle column header clicks for sorting
5. Handle row clicks for navigation
**Cell Value Formatting:**
- Use `type` property to determine formatting
- Format select fields using options array
- Format timestamps as relative/absolute time
- Format numbers with separators
- Handle null/undefined values
### Pattern 4: Generic Filter Component
A reusable filter component that works for any entity type:
**Component Structure:**
1. Accept `attributes` and `filters` as props
2. Generate filter controls from attributes (filter `visible: true`)
3. Handle filter changes
4. Display active filters as chips/badges
5. Allow removing individual filters
**Filter Control Generation:**
- Generate appropriate filter UI based on `type`
- Use localized labels
- Support multiple operators for text/number fields
- Update filters object and trigger refetch
### Key Implementation Guidelines
1. **Never Hardcode**
- Never hardcode field names, labels, types, or validation rules
- Always fetch attribute definitions from backend
- Use attribute metadata for all UI generation
2. **Entity-Agnostic**
- Same components work for all entity types
- Pass `entityType` as parameter to determine endpoints
- Use generic prop names (attributes, items, etc.)
3. **State Management**
- Keep pagination state separate from entity data
- Preserve filters and sort when changing pages
- Reset to page 1 when filters/search change
4. **Error Handling**
- Handle API errors gracefully
- Display validation errors from backend
- Show loading states during API calls
5. **Performance**
- Debounce search input (300-500ms)
- Cache attribute definitions (don't refetch on every render)
- Use pagination to limit data fetched
6. **Accessibility**
- Use semantic HTML for forms and tables
- Provide ARIA labels for form fields
- Ensure keyboard navigation works
- Support screen readers
---
## Summary
This generic system enables:
- **Complete Backend-Driven UI**: All forms, tables, filters generated from backend metadata
- **Zero Hardcoding**: No field definitions, labels, or validation rules in frontend code
- **Automatic Updates**: New backend fields automatically appear in frontend
- **Consistent UX**: Same patterns across all entity types
- **Localization**: Multi-language support from backend metadata
- **Reusable Components**: Generic components work for all entity types
By following these patterns, the frontend becomes a thin presentation layer that dynamically renders UI based on backend metadata, ensuring consistency, maintainability, and automatic adaptation to backend changes.