Specification for Creating a Master Data Management (MDM) Table Screen in Fuuz
Example - for Columns with no image data to keep consistent height
Design Philosophy
Master Data Management Principles
Master Data screens in the Fuuz Application Designer serve as the primary interface for managing critical business entities (customers, products, locations, equipment, users, etc.). The design standard is built on four core principles:
1. Consistency First: All master data screens follow identical patterns for predictable user experience
2. Filter-Then-Act: Users filter to find data, then act upon selections
3. Preserve User Context: Layouts remain stable; users customize views via tool panels
4. Relationship Clarity: Table structures clearly articulate data relationships
Purpose of Master Data Screens
Master data screens are **NOT** for:
- High-frequency transactional data entry
- Real-time operational dashboards
- Complex multi-entity workflows
Master data screens **ARE** for:
- Creating, reading, updating, and deleting reference data
- Managing organizational entities that change infrequently
- Maintaining data quality and consistency
- Bulk operations on similar records
Target Users
- System administrators
- Data stewards
- Department managers
- Power users with data management responsibilities
---
Template Architecture
Container Hierarchy
The standard template uses a three-level container architecture with a resizable filter panel:
ROOT (Screen)
└── ContainerOuter (Horizontal flex container)
├── ResizableFilterPanel (0-300px, collapsible)
│ └── ContainerFilterOuter
│ ├── ContainerFilterHeader (Action buttons)
│ └── ContainerFilterBody (Scrollable filters)
│ └── Form1 (Filter form)
└── ContainerBody (Flexible main area)
├── ContainerTableHeader (Action toolbar)
└── Table1 (Master data table)
Container Specifications
ContainerOuter
Purpose: Primary horizontal layout container
Properties:
- `width: 100%`, `height: 0px` (with flexGrow: true)
- `flexDirection: row`
- `justifyContent: flex-start`
- `alignItems: stretch`
- `flexGrow: true`, `flexShrink: false`
Why: Creates a horizontal split between filter panel and main content area, both filling viewport height.
ResizableFilterPanel
Purpose: Collapsible, resizable filter panel
Properties:
- `handle: "right"`
- `defaultSize: 300` (pixels)
- `minSize: 0` (fully collapsed)
- `maxSize: 300` (maximum width)
User Control: Users can drag to resize or collapse completely to maximize table viewing area.
Why: Provides flexible filtering without permanently consuming screen space. Power users can expand for complex filters; casual users can collapse for maximum data visibility.
ContainerFilterOuter
Purpose: Filter panel content wrapper
Properties:
- `width: 100%`, `height: 100%`
- `flexDirection: column`
- `justifyContent: flex-start`
- `flexGrow: true`, `flexShrink: true`
Children:
- ContainerFilterHeader (action buttons)
- ContainerFilterBody (scrollable filter inputs)
Purpose: Filter panel action buttons
Properties:
- `height: 60px` (fixed)
- `width: 100%`
- `flexDirection: row`
- `justifyContent: flex-start`
- `alignItems: center`
- `padding: 4`
Content: Action buttons for Search, Clear, Create, etc.
ContainerFilterBody
Purpose: Scrollable filter inputs area
Properties:
- `height: 0px` (with flexGrow: true)
- `width: 100%`
- `flexGrow: true`
- `overflow-y: auto`
- `overflow-x: hidden`
- `padding: 6`
Content: Form with filter inputs arranged vertically
ContainerBody
Purpose: Main content area for table
Properties:
- `width: 0px` (with flexGrow: true)
- `height: 100%`
- `flexDirection: column`
- `flexGrow: true`, `flexShrink: false`
- `borderLeft: "0.5px solid #5b30df"` (visual separator)
Children:
- ContainerTableHeader (toolbar with actions)
- Table1 (main data table)
Purpose: Table action toolbar
Properties:
- `height: 60px` (fixed)
- `width: 100%`
- `flexDirection: row`
- `justifyContent: space-between`
- `alignItems: center`
- `padding: 4`
- `borderBottom: "0.5px solid rgba(0, 0, 0, 0.12)"`
Content: Table-level actions (bulk operations, menu actions, etc.)
---
Layout Principles
Horizontal Split Pattern
Master data screens use a horizontal split layout:
Left Panel (Filter):
- Resizable from 0-300px
- Collapses completely when not needed
- Contains all search/filter controls
- Scrolls independently if filters exceed viewport
Right Panel (Data):
- Fills remaining horizontal space
- Contains table and table actions
- Table fills vertical space completely
- Table scrolls independently
Responsive Behavior
Filter Panel:
- Default 300px on large screens (>1600px)
- Default 250px on medium screens (1200-1600px)
- Default collapsed on small screens (<1200px)
- User preference persists across sessions
Table Area:
- Always fills remaining space
- Columns auto-size based on available width
- Horizontal scroll enabled if columns exceed width
- Tool panel accessible via sidebar
Thought Process for Layout
When Building Master Data Screens:
1. Start with the Data Model
- What entity are you managing?
- What fields are essential for identification?
- What relationships exist?
2. Define Filter Strategy
- What are the most common search patterns?
- Which fields need filtering?
- How can you minimize filter count?
3. Plan Column Layout
- Selection column needed? (Add first, 40px)
- Actions needed? (Add second, 40px)
- What's the primary identifier? (Add third, make it linkTarget)
- What additional context is needed?
- Where do audit fields go? (Always last)
4. Consider Relationships
- Is this 1:many (consider masterDetail)?
- Is this many:many (avoid masterDetail, use separate screen)?
- Do actions need to navigate to related entities?
Filter Panel Standards
Filter Organization Hierarchy
Filters must be organized in a specific order for consistency and usability:
1. SelectInput Filters (Top Priority)
- Maximum 4 select inputs
- Most commonly used selections first
- Example: Department, Location, Status, Type
2. Switch/Checkbox Filters (Secondary)
- Maximum 3 switches/checkboxes
- If more needed, use side-by-side container layout
- Example: Active Only, Include Deleted, Show Archived
3. GraphQL Advanced Filters (Last Resort)
- For power users needing complex queries
- Placed at bottom of filter panel
- Allows ad-hoc filtering without cluttering UI
Standard Configuration:
{
"type": "select",
"height": "80px",
"width": "100%",
"variant": "outlined",
"labelFontSize": 12,
"dataFontSize": 14,
"dialogMode": "never",
"isClearable": true,
"searchPredicate": "contains",
"stateTracking": "full"
}
Key Properties:
- height: "80px" (consistent vertical spacing)
- isClearable: true (allow filter reset)
- dialogMode: "never" (inline dropdown, no modal)
- stateTracking: "full" (maintain filter state)
Predicate Filtering:
Most filter inputs should NOT use predicates that hide data (e.g., active only), UNLESS:
- The data is truly inaccessible (deleted records)
- Business rules prohibit access (compliance)
- The data volume makes unfiltered results unusable
Why: Users may need to access deprecated or inactive data for reporting, auditing, or reactivation purposes.
Exception Example:
{
"additionalFilter": {
"_and": [
{
"deleted": { "_eq": false }
}
]
}
}
Switch/Checkbox Filters
Standard Configuration:
{
"type": "switch",
"width": "100%",
"label": "Active Only",
"dataPath": "activeOnly"
}
Side-by-Side Layout for Multiple Switches:
ContainerSwitches (flexDirection: row)
├── Container (width: 50%)
│ └── Switch 1
└── Container (width: 50%)
└── Switch 2
Why: Switches take less vertical space than selects, but more than 3 can clutter. Side-by-side layout reduces vertical scroll.
GraphQL Advanced Filters
Standard Configuration:
{
"type": "graphqlBuilder",
"width": "100%",
"model": "YourDataModel",
"dataPath": "advancedFilter"
}
Placement: Always last in filter panel
Purpose: Allows power users to construct complex queries without adding dozens of individual filter inputs
When to Use:
- Screen has been in use and users request specific complex filters
- Data model has many optional filter points
- User base includes technical users comfortable with GraphQL
Filter Actions
Search Button
Configuration:
{
"icon": { "icon": "magnifying-glass" },
"text": "Search",
"action": [
{
"type": "transformation",
"transformation": "$components.Table1.fn.search()"
}
]
}
Behavior: Applies all filter values and refreshes table
Why Icon + Text: "Search" is universal and explicit; icon reinforces action
Clear/Reset Button
Configuration:
{
"icon": { "icon": "eraser" },
"text": "Clear",
"action": [
{
"type": "transformation",
"transformation": "$components.Form1.fn.reset()"
}
]
}
Behavior: Resets all filter inputs to default/empty
Why: Users need quick escape from complex filter combinations
Auto-Search Configuration
Critical Setting: Auto-search must be **DISABLED** on master data tables
Table Query Property:
{
"query": {
"autoLoad": false
}
}
Why:
- Master data tables can contain thousands or millions of records
- Unfiltered loads impact performance
- Users should explicitly choose their filter criteria
- Prevents accidental full-table queries
Table Configuration
Essential Table Properties
Core Settings
{
"width": "100%",
"height": "0px",
"flexGrow": true,
"selectable": "single",
"showToolPanels": true,
"showStatusBar": true,
"enableGrouping": true,
"enableCharts": false,
"showColumnMenu": false,
"enableRowDragging": false,
"disableColumnDragging": true,
"disableDragLeaveHidesColumns": true,
"masterDetail": false,
"hideTableHeader": false,
"rowMultiSelectWithClick": false
}
Property Explanations
selectable:
- "none" - No row selection
- "single" - Select one row at a time (most common)
- "multiple" - Select multiple rows (for bulk operations)
showToolPanels: true
- Users access column visibility, filters, and grouping via sidebar
- Provides power-user capabilities without cluttering UI
showStatusBar: true
- Displays row count, selected rows, aggregations
- Essential for data management tasks
enableGrouping: true
- Allows users to drag columns to group by
- Useful for hierarchical data analysis
enableCharts: false
- Charts are for dashboards, not master data management
- Keeps focus on data editing/viewing
showColumnMenu: false (globally disabled)
- Individual columns can enable as needed
- Prevents UI clutter from excessive menu icons
enableRowDragging: false
- Master data order should not be user-draggable
- Order comes from sort configuration or data model
disableColumnDragging: true
- Maintains consistent column layout across all users
- Users can still reorder via tool panel
disableDragLeaveHidesColumns: true
- Prevents accidental column hiding
- Users explicitly hide via tool panel
masterDetail: false (by default)
- Enable only for clear 1:many relationships
- See [Master-Detail Pattern](#master-detail-pattern) section
Query Configuration
{
"query": {
"api": "Application",
"model": "YourDataModel",
"dataPath": "edges",
"autoLoad": false,
"fields": ["id"],
"dataSubscription": {
"enabled": false
}
}
}
autoLoad: false - Users must explicitly search
dataSubscription.enabled: false - Master data doesn't need real-time updates
Default Sort Configuration
{
"defaultSort": [
{
"field": "TableColumn1",
"direction": "asc"
}
]
}
Best Practice:
- Sort by the first data column (primary identifier)
- Use ascending order for text/names
- Users can override via column headers
Why: Predictable initial order reduces confusion and support requests
Column Standards
Column Layout Order
Master data tables follow a strict left-to-right column order:
1. Selection Column (if selectable enabled)
2. Actions Menu Column (if row actions needed)
3. Primary Identifier Column (with linkTarget if applicable)
4. Additional Data Columns (context and details)
5. Audit Columns (CreatedAt, CreatedBy, UpdatedAt, UpdatedBy)
Column 1: Selection Column
When to Include: Table has `selectable: "single"` or `selectable: "multiple"`
Configuration:
{
"label": " ",
"format": "text",
"sortable": false,
"dataPath": "na",
"width": "40px",
"showColumnSuppressMenu": false,
"editable": false,
"style": {
"alignItems": "center"
}
}
Key Properties:
- label: Empty space (checkboxes appear automatically)
- dataPath: "na" or empty (no data binding)
- width: "40px" (just enough for checkbox)
- sortable: false (no point sorting checkboxes)
- showColumnSuppressMenu: false (no menu needed)
Why: Dedicated column prevents checkbox overlap with data and provides consistent click target
Column 2: Actions Menu Column
When to Include: Rows require multiple actions (Edit, Delete, Inactivate, etc.)
Configuration:
{
"label": " ",
"format": "menu",
"sortable": false,
"dataPath": "actions",
"width": "40px",
"menuIcon": "ellipsis-v",
"iconSize": "small",
"showColumnSuppressMenu": false,
"actions": [
{
"text": "Edit",
"icon": {
"icon": "pencil",
"color": "warning",
"size": "small"
}
},
{
"text": "Delete",
"icon": {
"icon": "trash",
"color": "error",
"size": "small"
}
}
]
}
Action Types:
1. Information/View: Read-only details
- Icon: "circle-info", Color: "info"
- Opens modal with full record details
2. Edit: Modify record
- Icon: "pencil", Color: "warning"
- Opens form or navigates to edit screen
3. Delete: Remove record
- Icon: "trash", Color: "error"
- Requires confirmation dialog
4. Inactivate/Deactivate: Soft delete
- Icon: "ban", Color: "warning"
- Changes status without deleting
5. Custom Actions: Business-specific
- Choose appropriate icon and color
- Follow consistent patterns
Why: Consolidates row-level actions in one menu, saving horizontal space
Column 3: Primary Identifier Column
Purpose: The main field users search for and identify records by
Configuration with LinkTarget:
{
"label": "Name",
"format": "text",
"sortable": true,
"dataPath": "name",
"width": "200px",
"showColumnSuppressMenu": true,
"linkTarget": {
"screenId": "screen_id_here",
"query": {
"__transform": "{ \"id\": rowData.id }"
}
}
}
Configuration without LinkTarget:
{
"label": "Name",
"format": "text",
"sortable": true,
"dataPath": "name",
"width": "200px",
"showColumnSuppressMenu": true
}
When to Use LinkTarget:
- A detail form/screen exists for this entity
- Users frequently need to view/edit full record details
- Clicking the identifier is the primary navigation pattern
When NOT to Use LinkTarget:
- All editing happens inline or via menu actions
- No detail screen exists
- Navigation would be confusing
Why: Primary identifier should always be first data column for quick scanning and consistent UX
Additional Data Columns
Standard Column Configuration:
{
"label": "Field Label",
"format": "text",
"sortable": true,
"dataPath": "fieldName",
"width": "150px",
"showColumnSuppressMenu": true
}
Column Format Types:
| Format | Use Case | Example |
|--------|----------|---------|
| text | Plain text data | Names, descriptions, IDs |
| number | Numeric values | Quantities, counts |
| currency | Money values | Prices, costs |
| date | Date values | Birth dates, due dates |
| datetime | Date with time | Timestamps, appointments |
| boolean | True/false | Active status, flags |
| select | Dropdown options | Status, type, category |
| lookup | Related entity | Customer, location |
| menu | Row actions | Edit, delete, etc. |
Width Guidelines:
- Short IDs/codes: 80-100px
- Names: 150-200px
- Descriptions: 200-300px
- Dates: 100-120px
- Numbers: 80-120px
- Let remaining columns auto-size
Sortable Property:
- Enable for most data columns
- Disable for: selection column, actions column, calculated fields without sort support
showColumnSuppressMenu:
- Enable for data columns users might want to filter/sort/customize
- Disable for: selection column, actions column
Audit Columns
Always place audit fields at the far right (end of all data columns)
Standard Audit Column Set:
1. Created At
{
"label": "Created",
"format": "datetime",
"sortable": true,
"dataPath": "createdAt",
"width": "140px",
"timezone": "tenant",
"dateFormat": "MM/DD/YYYY HH:mm"
}
2. Created By
{
"label": "Created By",
"format": "lookup",
"sortable": true,
"dataPath": "createdBy.fullName",
"width": "150px",
"query": {
"fields": ["createdBy.fullName"]
}
}
3. Updated At
{
"label": "Updated",
"format": "datetime",
"sortable": true,
"dataPath": "updatedAt",
"width": "140px",
"timezone": "tenant",
"dateFormat": "MM/DD/YYYY HH:mm"
}
4. Updated By
{
"label": "Updated By",
"format": "lookup",
"sortable": true,
"dataPath": "updatedBy.fullName",
"width": "150px",
"query": {
"fields": ["updatedBy.fullName"]
}
}
Critical Requirements:
Timezone: Always use "Fuuz Setting"
- Displays dates in user's configured timezone
- Prevents confusion from UTC timestamps
Date Format: Use consistent format
- Recommended: "MM/DD/YYYY HH:mm" for US audiences
- Adjust for regional requirements
- Be consistent across all tables
User Fields: Use "fullName"
- More user-friendly than "email" or "username"
- Requires query.fields: ["createdBy.fullName"] or Concat firstName & " " & lastName
Why Last: Audit information is important but not primary. Users scan left-to-right for business data, check audit info when needed.
Action Patterns
Icon vs Text Guidelines
Use ICONS ONLY when the function is universally recognized:
✅ Use Icons (with tooltip):
- CREATE (layer-plus, plus, file-plus)
- DELETE (trash, trash-can)
- SEARCH (magnifying-glass, search)
- REFRESH (arrows-rotate, sync)
- FILTER (filter, filter-list)
- EXPORT (download, file-export)
- IMPORT (upload, file-import)
❌ Use Text Labels for:
- Business-specific actions ("Approve", "Reject")
- Complex operations ("Generate Report", "Sync with ERP")
- Ambiguous operations ("Process", "Submit")
- Custom workflows
Best Practice: When in doubt, use text. Clarity beats brevity.
Actions that operate on the table as a whole go in ContainerTableHeader:
Standard Pattern:
[Create] [Bulk Edit] [Export] [Menu Actions ▼]
Create Action:
{
"icon": { "icon": "layer-plus", "color": "success" },
"useIconButton": true,
"action": [
{
"type": "form",
"fields": [...]
},
{
"type": "mutation",
"api": "Application"
},
{
"type": "transformation",
"transformation": "$components.Table1.fn.search()"
}
]
}
Why Icon: "Create" is universal, icon is recognized, saves space
Bulk Edit Action:
{
"text": "Bulk Edit",
"disabled": {
"__transform": "$count($components.Table1.state.selectedRows) = 0"
},
"action": [
{
"type": "form",
"fields": [...]
},
{
"type": "mutation",
"api": "Application"
}
]
}
Why Text: "Bulk Edit" is specific business logic, text is clearer
Use for multiple custom actions to reduce toolbar clutter:
{
"type": "menuButton",
"text": "Actions",
"icon": { "icon": "ellipsis-v" },
"items": [
{
"text": "Generate Report",
"icon": { "icon": "file-chart-column" }
},
{
"text": "Export to Excel",
"icon": { "icon": "file-excel" }
},
{
"text": "Sync with ERP",
"icon": { "icon": "arrows-rotate" }
}
]
}
When to Use:
- More than 3 custom actions
- Actions used less frequently
- Need to reduce visual clutter
When NOT to Use:
- Primary actions (Create, Search)
- Actions used constantly
- Only 1-2 actions available
Master-Detail Pattern
When to Use Master-Detail
Master-detail view shows child records within the parent row, expandable inline.
✅ Use Master-Detail For:
- Clear 1:many relationships
- Child data is simple and few columns
- Users frequently need to see children in context
- Children don't require complex editing
Examples:
- Customer → Orders (simple order list)
- Equipment → Maintenance Records
- Location → Sub-Locations
- Product → SKU Variants (if simple)
❌ Avoid Master-Detail For:
- Many:many relationships (use separate screen)
- Many:1 relationships (doesn't make sense)
- Complex child data with many fields
- Children require complex forms
- Performance concerns (thousands of children)
Master-Detail Configuration
Enable in Table:
Configure Detail View:
{
"masterDetail": true,
"detailCellRendererParams": {
"detailGridOptions": {
"columnDefs": [
{
"field": "childField1",
"headerName": "Field 1"
},
{
"field": "childField2",
"headerName": "Field 2"
}
]
},
"getDetailRowData": {
"__transform": "function($params) { $params.successCallback(rowData.children) }"
}
}
}
Best Practices:
- Keep child columns to 3-5 maximum
- Don't nest master-detail (no grandchildren)
- Consider pagination for large child sets
- Provide way to navigate to full child management screen
Developer Checklist
Before Deployment
Use this checklist for every master data table screen:
Layout Structure
- [ ] ContainerOuter set with `flexDirection: row`, `height: 0px`, `flexGrow: true`
- [ ] ResizableFilterPanel configured with `maxSize: 300`, `minSize: 0`
- [ ] ContainerFilterOuter properly nested with header and body
- [ ] ContainerBody set with `width: 0px`, `flexGrow: true`
- [ ] ContainerTableHeader configured for action buttons
- [ ] All containers properly nested
Filter Panel
- [ ] Filters ordered: Select inputs first (max 4), then switches (max 3), then GraphQL advanced
- [ ] Side-by-side containers used if more than 3 switches needed
- [ ] Select inputs have `isClearable: true`, `dialogMode: "never"`
- [ ] Predicate filters reviewed (avoid hiding accessible data)
- [ ] Search button configured to call `$components.Table1.fn.search()`
- [ ] Clear button configured to call `$components.Form1.fn.reset()`
- [ ] All filter inputs bound to Form1 with proper dataPaths
Table Configuration
- [ ] Table `autoLoad: false` (users must explicitly search)
- [ ] Table `showToolPanels: true` (user customization)
- [ ] Table `showStatusBar: true` (row count, aggregations)
- [ ] Table `enableCharts: false` (not for master data)
- [ ] Table `showColumnMenu: false` (globally disabled)
- [ ] Table `enableRowDragging: false` (no user reordering)
- [ ] Table `disableColumnDragging: true` (consistent layout)
- [ ] Table `disableDragLeaveHidesColumns: true` (prevent accidents)
- [ ] Table `masterDetail` set appropriately (only for 1:many)
- [ ] Default sort configured on first data column
Column Configuration
- [ ] Selection column added if `selectable` enabled (40px, first position)
- [ ] Actions menu column added if needed (40px, second position)
- [ ] Primary identifier column configured (third position, linkTarget if applicable)
- [ ] Additional data columns use appropriate formats
- [ ] Audit columns placed at far right (CreatedAt, CreatedBy, UpdatedAt, UpdatedBy)
- [ ] Date columns have `timezone: "tenant"` and proper format
- [ ] User fields use `fullName` with proper query.fields
- [ ] Column widths set appropriately (selection/actions: 40px, names: 150-200px)
- [ ] Selection and actions columns have `sortable: false`, `showColumnSuppressMenu: false`
- [ ] Data columns have `sortable: true`, `showColumnSuppressMenu: true` as appropriate
- [ ] LinkTarget removed from columns where not used (avoid confusion)
Actions
- [ ] Icons used only for universal actions (CREATE, DELETE, SEARCH)
- [ ] Text labels used for business-specific actions
- [ ] Create action configured with form → mutation → table refresh
- [ ] Menu button used for 3+ custom actions
- [ ] Row action menu configured with appropriate actions (Edit, Delete, etc.)
- [ ] Bulk actions disabled when no rows selected
- [ ] All actions provide user feedback (success messages, confirmations)
Testing
- [ ] Tested with no filters applied (should show "no data" or prompt to search)
- [ ] Tested Search button with various filter combinations
- [ ] Tested Clear button resets all filters
- [ ] Verified filter panel collapses to 0px
- [ ] Verified filter panel expands to 300px max
- [ ] Tested table sorting on each sortable column
- [ ] Verified selection column checkboxes work properly
- [ ] Tested actions menu on multiple rows
- [ ] Verified linkTarget navigation (if used)
- [ ] Tested master-detail expansion (if used)
- [ ] Verified audit fields display correct timezone and format
- [ ] Tested tool panel for column visibility and customization
- [ ] Verified status bar shows correct row counts
- [ ] Tested Create action form and mutation
- [ ] Tested Edit action (if configured)
- [ ] Tested Delete action with confirmation
Data Quality
- [ ] Verified query.fields includes all displayed columns
- [ ] Verified lookup fields include proper relationship paths
- [ ] Tested with large data sets (performance)
- [ ] Verified no predicate filters hide needed data
- [ ] Confirmed data model supports configured columns
Best Practices
Filter Strategy
Start Minimal, Expand Based on Usage
Initial Deployment:
- Add 2-3 most obvious filters (status, type, location)
- Include GraphQL advanced filters for power users
- Monitor user feedback and usage patterns
After 30-60 Days:
- Review filter usage analytics
- Add filters users repeatedly request
- Optimize filter order based on usage frequency
- Remove filters no one uses
Why: Overloading with filters reduces usability. Let real usage patterns guide optimization.
Progressive Disclosure
For complex filtering needs:
1. Basic Filters: Always visible (status, type)
2. Advanced Filters: Collapsible section or GraphQL builder
3. Saved Filter Sets: Allow users to save combinations
Optimize for Large Datasets
When Tables Contain >10,000 Records:
1. Require Initial Filter:
- Don't allow unfiltered searches
- Prompt user to select department, location, date range, etc.
2. Implement Pagination:
- Server-side pagination for large result sets
- Show page size options (25, 50, 100)
3. Limit Initial Columns:
- Load essential columns only
- Use lazy loading for detail columns
- Let users add columns via tool panel
4. Use Virtualization:
- Tables automatically virtualize rows
- Ensure columns aren't overly complex (avoid heavy rendering)
Column Width Strategy
Auto-Sizing:
- Let most columns auto-size
- Fix width only for: selection (40px), actions (40px), icons, short codes
Resizable:
- All data columns should be resizable
- Users can adjust via drag or tool panel
Best Practice:
{
"width": "150px", // Initial suggestion
"resizable": true, // User can adjust
"flex": 1 // Or use flex for proportional sizing
}
Relationship Management
When to Use Separate Screens
Create a separate detail/form screen when:
- Entity has >10 fields
- Complex relationships (many:many)
- Requires multi-step process
- Needs different user permissions
- Has its own child entities
Navigation Pattern:
Master Table → LinkTarget → Detail Form
→ Related Entity Tabs
→ Back to List Button
When to Edit Inline
Use inline editing (table cell editing) when:
- Quick status changes
- Single field updates
- Bulk editing scenarios
- Simple lookup changes
Configuration:
{
"editable": true,
"cellEditor": "select",
"cellEditorParams": {
"values": ["Active", "Inactive", "Pending"]
}
}
Audit Trail Requirements
Essential for Master Data
All master data tables should include audit fields because:
1. Compliance: Who changed what and when
2. Troubleshooting: Track down data quality issues
3. Security: Detect unauthorized changes
4. Support: Answer "when did this change?" questions
Display Best Practices
- Always include all four: CreatedAt, CreatedBy, UpdatedAt, UpdatedBy
- Use fullName for "By" fields (more readable than email)
- Use tenant timezone (respects user location)
- Consistent date format across all tables
- Place at far right (important but not primary)
Action Organization
Table Header vs Row Actions
Table Header Actions (ContainerTableHeader):
- Create new record
- Bulk operations (edit, delete, export)
- Table-wide operations (refresh, settings)
- Report generation
Row Actions (Actions menu column):
- Edit single record
- Delete single record
- View details
- Record-specific operations (activate, approve)
Why Separate: Clear separation between table-level and record-level operations reduces user errors
Common Patterns
Pattern 1: Simple Master Data (CRUD)
Use Case: Basic reference data management (departments, locations, categories)
Structure:
Filter Panel:
├── Search by Name (SelectInput)
├── Active Only (Switch)
└── Search Button
Table:
├── Selection Column (40px)
├── Actions Menu Column (40px)
│ ├── Edit
│ └── Delete
├── Name (primary, linkTarget)
├── Description
├── Status
├── CreatedAt
└── UpdatedAt
Key Points:
- Minimal filters (name, status)
- LinkTarget to detail form
- Standard actions (edit, delete)
- Audit fields for tracking
Pattern 2: Hierarchical Master Data
Use Case: Data with parent-child relationships (locations with sub-locations, org structure)
Structure:
Filter Panel:
├── Parent Entity (SelectInput)
├── Level (SelectInput)
├── Active Only (Switch)
└── Search Button
Table:
├── Master-Detail Enabled
├── Selection Column (40px)
├── Actions Menu Column (40px)
├── Name (primary)
├── Parent
├── Level
├── ChildCount
└── Audit Columns
Master-Detail View:
└── Child Records (inline table)
Key Points:
- Use master-detail for 1:many
- Show parent context
- Display child count
- Allow navigation to child management
Pattern 3: Complex Master Data with Relationships
Use Case: Entities with multiple relationships (products with suppliers, categories, SKUs)
Structure:
Filter Panel:
├── Category (SelectInput)
├── Supplier (SelectInput)
├── Status (SelectInput)
├── Active Only (Switch)
├── In Stock Only (Switch)
└── Search Button
Table:
├── Selection Column (40px)
├── Actions Menu Column (40px)
│ ├── Edit
│ ├── View SKUs
│ ├── Manage Suppliers
│ └── Delete
├── Product Code (primary, linkTarget)
├── Description
├── Category
├── Primary Supplier
├── Status
├── Quantity on Hand
└── Audit Columns
Key Points:
- Multiple relationship filters
- Actions navigate to related entity screens
- Don't use master-detail (too complex)
- LinkTarget to full detail form with tabs
Use Case: Managing users, contacts, employees
Structure:
Filter Panel:
├── Department (SelectInput)
├── Role (SelectInput)
├── Status (SelectInput)
├── Active Only (Switch)
└── Search Button
Table:
├── Selection Column (40px)
├── Actions Menu Column (40px)
│ ├── Edit Profile
│ ├── Reset Password
│ ├── Manage Permissions
│ ├── Inactivate
│ └── Delete
├── Full Name (primary, linkTarget)
├── Email
├── Department
├── Role
├── Last Login
├── Status
└── Audit Columns
Key Points:
- Security-sensitive actions (password reset, permissions)
- Status column shows active/inactive
- Last login for activity tracking
- Inactivate instead of delete (preserve data)
Pattern 5: Equipment/Asset Management
Use Case: Tracking physical assets, equipment, tools
Structure:
Filter Panel:
├── Location (SelectInput)
├── Type (SelectInput)
├── Status (SelectInput)
├── Calibration Due (DateRange)
└── Search Button
Table:
├── Master-Detail Enabled (for maintenance history)
├── Selection Column (40px)
├── Actions Menu Column (40px)
│ ├── Edit
│ ├── View History
│ ├── Schedule Maintenance
│ └── Retire
├── Asset Number (primary, linkTarget)
├── Description
├── Type
├── Location
├── Status
├── Last Maintenance
├── Next Calibration
└── Audit Columns
Master-Detail View:
└── Maintenance History (inline table)
Key Points:
- Date-based filters (calibration, maintenance)
- Master-detail for maintenance history
- Status tracking (active, maintenance, retired)
- Action for scheduling
Troubleshooting
Common Layout Issues
Issue: Filter panel doesn't collapse fully
Symptoms:
- Panel stays at minimum width instead of 0px
- Cannot maximize table viewing area
Causes:
- ResizableFilterPanel `minSize` not set to 0
- Child containers have fixed minimum width
Solutions:
1. Set ResizableFilterPanel `minSize: 0`
2. Ensure ContainerFilterOuter has no minimum width constraints
3. Set filter inputs `width: 100%` not fixed pixels
Issue: Table doesn't fill vertical space
Symptoms:
- White space below table
- Table doesn't scroll
- Vertical space wasted
Causes:
- Table `height` not set to "0px" with `flexGrow: true`
- ContainerOuter not set to fill viewport
- ContainerBody missing flexGrow
Solutions:
1. Set Table `height: "0px"`, `flexGrow: true`
2. Set ContainerOuter `height: "0px"`, `flexGrow: true`
3. Set ContainerBody `width: "0px"`, `flexGrow: true`
4. Verify ROOT screen has `height: "100%"`
Issue: Horizontal scrolling in table when not needed
Symptoms:
- Table shows horizontal scrollbar with plenty of space
- Columns too wide for viewport
Cause*:
- Too many fixed-width columns
- Column widths exceed available space
- Browser window too narrow
Solutions:
1. Reduce number of visible columns (use tool panel for others)
2. Use flexible widths instead of fixed pixels
3. Review column width totals vs viewport
4. Let non-critical columns auto-size
Common Functional Issues
Issue: Search doesn't work
Symptoms:
- Clicking Search shows no data
- Filters not applied to query
Causes:
- Search action not calling `$components.Table1.fn.search()` (Or replace Table1 with actual table name)
- Filter form not bound to table query
- Wrong form element name referenced
Solutions:
1. Verify Search button action: `$components.Table1.fn.search()`
2. Check table element name matches (Table1)
3. Verify filter dataPath matches query parameter structure
4. Test with browser console for errors
Issue: Clear button doesn't reset filters
Symptoms:
- Filters remain populated after clicking Clear
- Must manually delete each filter value
Causes:
- Clear action not calling `$components.Form1.fn.reset()`
- Wrong form element name referenced
- Custom default values not configured
Solutions:
1. Verify Clear button action: `$components.Form1.fn.reset()`
2. Check form element name matches (Form1)
3. Set filter default values if needed
4. Use transformation to set custom reset state
Issue: Selection checkboxes don't appear
Symptoms:
- No checkboxes in first column
- Cannot select rows
Causes:
- Table `selectable` not set to "single" or "multiple"
- Selection column missing
- Selection column has dataPath set (should be empty/na)
Solutions:
1. Set Table `selectable: "single"` or `"multiple"`
2. Add selection column as first column
3. Set selection column `dataPath: "na"` or leave empty
4. Set selection column `width: "40px"`
Issue: Master-detail rows won't expand
Symptoms:
- Expand icon missing from rows
- Clicking row does nothing
Causes:
- Table `masterDetail: false`
- Missing `detailCellRendererParams` configuration
- Wrong data path for child records
Solutions:
1. Set Table `masterDetail: true`
2. Configure `detailCellRendererParams` with child grid options
3. Verify `getDetailRowData` function returns child array
4. Test data structure includes child records
Issue: Date columns show UTC time instead of local
Symptoms:
- Dates appear in wrong timezone
- Hours off by timezone offset
Causes:
- Column `timezone` not set to "tenant"
- Date stored incorrectly in database
- Format doesn't include timezone
Solutions:
1. Set date column `timezone: "tenant"`
2. Verify date format includes time if needed
3. Check database date storage (should be UTC)
4. Use proper date types in GraphQL schema
Issue: Created/Updated By shows email instead of name
Symptoms:
- Audit columns show "user@example.com" instead of "John Smith"
Causes:
- Column `dataPath` is "createdBy.email" instead of "createdBy.fullName"
- Query doesn't include fullName field
Solutions:
1. Change column `dataPath: "createdBy.fullName"`
2. Add to query.fields: `["createdBy.fullName"]`
3. Verify User model has fullName field
4. Same for updatedBy
Issue: Table loads slowly with many records
Symptoms:
- Long wait after clicking Search
- Browser becomes unresponsive
- Timeout errors
Causes:
- Too many records returned (no pagination)
- Complex query with many joins
- Too many columns loaded
Solutions:
1. Implement server-side pagination
2. Require initial filter (don't allow unfiltered search)
3. Reduce number of columns in initial query
4. Add indexes to filtered fields in database
5. Use dataPath to limit returned fields
Issue: Filter panel is slow to open/close
Symptoms:
- Lag when dragging resize handle
- Panel stutters when collapsing
Causes:
- Too many filter inputs (>10)
- Complex SelectInputs with large option sets
- Heavy rendering in filter body
Solutions:
1. Reduce number of filters (use GraphQL advanced instead)
2. Limit SelectInput options (use optionLimit)
3. Remove unnecessary filter complexity
4. Consider tabs for filter organization