Skip to main content

DataView Designer Documentation

Overview (Non-Technical)

The DataView Designer enables users to configure how data is displayed, filtered, sorted, and navigated without writing code. Users select an entity (like "Customers" or "Orders"), choose which fields to show, add filters and sorting, and define actions users can take (view details, edit, delete). The system generates a fully-functional data table or custom view.

Key Purpose: Enable users to create powerful data viewing and manipulation interfaces that can handle filtering, sorting, searching, pagination, and bulk actions without any coding.


Standards

DataView Architecture

DataView Components:

┌─────────────────────────────────────────────────┐
│ DATAVIEW (Data Display) │
├─────────────────────────────────────────────────┤
│ │
│ Search Filters Sort [Actions] │
│ ┌──────────────────────────────────────────┐ │
│ │ Field1 │ Field2 │ Field3 │ Actions │ │
│ ├──────────────────────────────────────────┤ │
│ │ Value1 │ Value2 │ Value3 │ [View] │ │
│ │ Value1 │ Value2 │ Value3 │ [Edit] │ │
│ │ Value1 │ Value2 │ Value3 │ [Delete] │ │
│ │ Value1 │ Value2 │ Value3 │ [More] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Pagination: 1 2 3 ... [10-20 of 100 records] │
│ │
└─────────────────────────────────────────────────┘

Configuration Schema

{
"dataviewId": "string",
"dataviewName": "string",
"dataviewDescription": "string",
"version": "1.0",
"entityId": "string",
"displaySettings": {
"viewType": "table|grid|card|timeline|list",
"pageSize": 20,
"showPagination": true,
"showSearch": true,
"showFilters": true,
"showColumnSelector": true,
"showBulkActions": true,
"density": "compact|comfortable|spacious",
"striped": true
},
"columns": [
{
"columnId": "col-1",
"fieldId": "customer_name",
"fieldName": "Customer Name",
"dataType": "string",
"width": 200,
"sortable": true,
"filterable": true,
"visible": true,
"alignment": "left|center|right",
"format": "plain|currency|date|percentage|custom",
"formatConfig": {
"currency": "USD",
"dateFormat": "YYYY-MM-DD",
"decimalPlaces": 2
},
"customRenderer": null,
"searchable": true
}
],
"filters": [
{
"filterId": "filter-1",
"fieldId": "status",
"fieldName": "Status",
"filterType": "equals|contains|range|date|multi-select",
"defaultValue": "active",
"options": ["active", "inactive", "archived"],
"label": "Filter by Status",
"required": false
}
],
"sorting": {
"defaultSortField": "created_date",
"defaultSortOrder": "desc",
"multiSortEnabled": true
},
"actions": [
{
"actionId": "view",
"actionName": "View Details",
"actionType": "view|edit|delete|custom|navigate",
"icon": "eye",
"cssClass": "primary",
"target": "modal|page|dialog",
"targetUrl": "/records/${id}",
"requiresSelection": false,
"bulkApplicable": false
},
{
"actionId": "edit",
"actionName": "Edit",
"actionType": "edit",
"icon": "pencil",
"requiresSelection": false,
"bulkApplicable": true
}
],
"rowActions": {
"expandable": true,
"clickableRow": false,
"parentChildRelationship": {
"enabled": false,
"parentId": "",
"childViews": []
}
},
"styling": {
"alternateRowColor": true,
"highlightOnHover": true,
"borderStyle": "bordered|borderless",
"cssClass": "custom-dataview"
},
"metadata": {
"createdBy": "string",
"createdDate": "ISO8601",
"modifiedBy": "string",
"modifiedDate": "ISO8601"
}
}

Do's and Don'ts

✅ DO

  • Show only necessary columns: Too many columns create clutter
  • Make frequently-filtered fields filterable: Enable quick data discovery
  • Set appropriate column widths: Based on content type and length
  • Use semantic colors: Green for success, red for error/delete
  • Implement search on key fields: Name, ID, email, etc.
  • Add row actions inline: View, Edit, Delete icons in each row
  • Use pagination for large datasets: Avoid loading thousands of rows
  • Enable sorting on numeric/date columns: Always sortable
  • Provide bulk actions carefully: Only for safe operations
  • Show loading state: During data fetch
  • Implement empty states: Show helpful message when no data
  • Test with actual data: Verify column widths, formats, performance

❌ DON'T

  • Display sensitive data in lists: Use view details modal instead
  • Create overly wide tables: Causes horizontal scrolling
  • Use cryptic column names: Be descriptive and user-friendly
  • Enable bulk delete without confirmation: Add safety confirmation
  • Overload with filters: Keep to 5-7 most important filters
  • Hardcode filter values: Use configurable defaults
  • Skip loading indicators: Users get confused with no feedback
  • Use real-time updates for datasets > 1000 rows: Too expensive
  • Mix action types inconsistently: View should always open details
  • Forget to handle errors: Network errors, empty results, etc.
  • Ignore mobile responsiveness: DataView must work on phones
  • Create circular parent-child relationships: Causes infinite loops

Technical Details

View Types

1. Table View (Default):

export function TableDataView({ dataviewConfig, entityId }) {
const [page, setPage] = useState(1);
const [sortBy, setSortBy] = useState(dataviewConfig.sorting.defaultSortField);
const [sortOrder, setSortOrder] = useState(dataviewConfig.sorting.defaultSortOrder);
const [filters, setFilters] = useState(getDefaultFilters(dataviewConfig));

const { data, isLoading } = useQuery({
queryKey: ['records', entityId, page, sortBy, sortOrder, filters],
queryFn: () => api.record.list({
entityId,
page,
pageSize: dataviewConfig.displaySettings.pageSize,
sortBy,
sortOrder,
filters
})
});

if (isLoading) return <Skeleton rows={dataviewConfig.displaySettings.pageSize} />;

return (
<div>
<FilterBar
filters={dataviewConfig.filters}
onChange={setFilters}
/>
<table>
<thead>
{dataviewConfig.columns.map(col => (
<th key={col.columnId} onClick={() => toggleSort(col.fieldId)}>
{col.fieldName}
{sortBy === col.fieldId && (
<span>{sortOrder === 'asc' ? '↑' : '↓'}</span>
)}
</th>
))}
<th>Actions</th>
</thead>
<tbody>
{data.records.map(record => (
<tr key={record.id}>
{dataviewConfig.columns.map(col => (
<td key={col.columnId}>
<FieldRenderer
value={record[col.fieldId]}
format={col.format}
formatConfig={col.formatConfig}
/>
</td>
))}
<td>
<RowActionsMenu
record={record}
actions={dataviewConfig.actions}
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
current={page}
total={data.total}
pageSize={dataviewConfig.displaySettings.pageSize}
onChange={setPage}
/>
</div>
);
}

2. Card View:

export function CardDataView({ dataviewConfig, entityId }) {
// Similar data fetching as Table

return (
<div className="card-grid">
{data.records.map(record => (
<Card key={record.id}>
{dataviewConfig.columns.map(col => (
<CardField
key={col.columnId}
label={col.fieldName}
value={record[col.fieldId]}
format={col.format}
/>
))}
<CardActions actions={dataviewConfig.actions} record={record} />
</Card>
))}
</div>
);
}

3. Timeline View:

export function TimelineDataView({ dataviewConfig, entityId }) {
// Group records by date field
const groupedByDate = groupBy(data.records, dateField);

return (
<div className="timeline">
{Object.entries(groupedByDate).map(([date, records]) => (
<TimelineGroup key={date} date={date}>
{records.map(record => (
<TimelineItem key={record.id} record={record} />
))}
</TimelineGroup>
))}
</div>
);
}

Filter System

Filter Types & Implementation:

const filterImplementations = {
'equals': (field, value) => `${field} = '${value}'`,
'contains': (field, value) => `${field} LIKE '%${value}%'`,
'startsWith': (field, value) => `${field} LIKE '${value}%'`,
'endsWith': (field, value) => `${field} LIKE '%${value}'`,
'range': (field, min, max) => `${field} BETWEEN ${min} AND ${max}`,
'date': (field, operator, value) => {
const ops = {
'equals': '=',
'before': '<',
'after': '>',
'between': 'BETWEEN'
};
return `${field} ${ops[operator]} '${value}'`;
},
'multiSelect': (field, values) =>
`${field} IN (${values.map(v => `'${v}'`).join(',')})`,
'custom': (field, expression) => expression
};

// Filter UI Component
export function FilterBar({ filters, onChange }) {
return (
<div className="filter-bar">
{filters.map(filter => (
<FilterControl
key={filter.filterId}
filter={filter}
onChange={value => onChange(filter.filterId, value)}
/>
))}
</div>
);
}

Field Formatting

Format Types:

const fieldFormatters = {
'plain': (value) => String(value),
'currency': (value, config) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: config.currency || 'USD'
}).format(value),
'date': (value, config) =>
new Date(value).toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}),
'percentage': (value, config) =>
`${(value * 100).toFixed(config.decimalPlaces || 2)}%`,
'boolean': (value) => value ? '✓' : '✗',
'json': (value) => JSON.stringify(value, null, 2),
'email': (value) => (
<a href={`mailto:${value}`}>{value}</a>
),
'phone': (value) => (
<a href={`tel:${value}`}>{formatPhoneNumber(value)}</a>
)
};

Searching

Search Implementation:

// Frontend: Simple search across searchable columns
const handleSearch = (query) => {
const searchFields = dataviewConfig.columns
.filter(col => col.searchable)
.map(col => col.fieldId);

setFilters(prev => ({
...prev,
searchQuery: query,
searchFields
}));
};

// Middleware: Full-text search or LIKE operation
const records = await db.query(
`SELECT * FROM ${entityTable}
WHERE ${searchFields.map(f => `${f} LIKE @query`).join(' OR ')}
ORDER BY relevance DESC`,
{ query: `%${searchQuery}%` }
);

Parent-Child DataViews

Configuration:

{
"dataviewId": "orders-dataview",
"entityId": "orders",
"rowActions": {
"expandable": true,
"parentChildRelationship": {
"enabled": true,
"parentId": "orders",
"childViews": [
{
"relationshipId": "order-items",
"childEntityId": "order_items",
"childDataviewId": "order-items-dataview",
"foreignKeyField": "order_id",
"parentKeyField": "id"
}
]
}
}
}

Rendering:

export function ExpandableRow({ record, childViews, dataviewConfig }) {
const [expanded, setExpanded] = useState(false);

return (
<>
<tr>
<td>
<button onClick={() => setExpanded(!expanded)}>
{expanded ? '▼' : '▶'}
</button>
</td>
{/* Regular row cells */}
</tr>
{expanded && childViews.map(child => (
<tr key={child.relationshipId} className="child-row">
<td colSpan="100%">
<ChildDataView
parentId={record.id}
childDataviewConfig={child}
/>
</td>
</tr>
))}
</>
);
}

Bulk Actions

Bulk Action Support:

export function DataViewWithBulkActions({ dataviewConfig, entityId }) {
const [selectedIds, setSelectedIds] = useState([]);

const bulkActions = dataviewConfig.actions.filter(a => a.bulkApplicable);

const handleBulkAction = async (action) => {
const result = await api.record.bulkAction({
entityId,
recordIds: selectedIds,
action: action.actionId,
payload: action.payload
});

setSelectedIds([]); // Clear selection
refetchData(); // Reload table
};

return (
<>
{selectedIds.length > 0 && (
<BulkActionBar
recordCount={selectedIds.length}
actions={bulkActions}
onAction={handleBulkAction}
/>
)}
{/* DataView table with checkboxes */}
</>
);
}

Export Functionality

Export Support:

const exportHandlers = {
'csv': (records, columns) => {
const csv = [
columns.map(c => c.fieldName).join(','),
...records.map(r =>
columns.map(c => r[c.fieldId]).join(',')
)
].join('\n');
downloadFile(csv, 'export.csv', 'text/csv');
},
'excel': (records, columns) => {
const worksheet = XLSX.utils.json_to_sheet(records);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet);
XLSX.writeFile(workbook, 'export.xlsx');
},
'json': (records, columns) => {
downloadFile(
JSON.stringify(records, null, 2),
'export.json',
'application/json'
);
}
};

Key Files & Imports

PurposeFileNotes
DataView renderercomponents/viewer/DataViewRenderer.tsxMain component
Filter systemcomponents/dataview/FilterBar.tsxFilter UI
Sortingcomponents/dataview/SortingHeader.tsxColumn sorting
Paginationcomponents/dataview/Pagination.tsxPage navigation
Actionscomponents/dataview/RowActions.tsxRow buttons
Service layerservices/api.ts → record.*API calls
Custom hookshooks/useDataView.tsData fetching, state

Common Issues & Solutions

IssueCauseSolution
Columns not showingvisible: false or not in configCheck column configuration, visible property
Filters not workingFilter condition incorrectVerify entity has field, test SQL
Sorting brokenNon-sortable fieldEnable sortable: true in column config
Search not finding resultsSearch fields not specifiedAdd searchable: true to columns
Pagination brokenPage size mismatchEnsure pageSize matches query results
Large table slowLoading too many rowsImplement pagination, add indexes
Parent-child infinite loopCircular relationshipVerify relationship direction
Bulk action failsNot marked bulkApplicableCheck action configuration
Export missing dataCustom renderer not exportingImplement export logic in renderer

DataView Creator Workflow

  1. Select Entity → Choose data source (Customers, Orders, etc.)
  2. Choose View Type → Table, Card, Timeline, Grid
  3. Select Columns → Choose fields to display, set order
  4. Configure Formatting → Number format, dates, currency
  5. Add Filters → Enable quick data filtering
  6. Set Sorting → Default sort order
  7. Define Actions → View, Edit, Delete, Custom
  8. Setup Search → Enable searching on key fields
  9. Test with Data → Verify display, filters, actions
  10. Configure Pagination → Set page size (default 20)
  11. Setup Exports → Enable CSV, Excel, JSON export
  12. Publish → Save version, make available