Add component tree definitions - build complete UIs from JSON configuration

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-08 13:39:34 +00:00
parent be1b6f02d3
commit 7da561bd86
6 changed files with 2492 additions and 0 deletions

View File

@@ -325,6 +325,816 @@
"elevation": 2
}
},
"formSchemas": {
"users": {
"fields": [
{
"name": "name",
"type": "text",
"label": "Name",
"placeholder": "Enter full name",
"required": true,
"minLength": 2,
"maxLength": 100
},
{
"name": "email",
"type": "email",
"label": "Email",
"placeholder": "user@example.com",
"required": true,
"validation": "email"
},
{
"name": "role",
"type": "select",
"label": "Role",
"required": true,
"options": [
{ "value": "admin", "label": "Administrator" },
{ "value": "user", "label": "User" },
{ "value": "guest", "label": "Guest" }
]
},
{
"name": "active",
"type": "checkbox",
"label": "Active",
"defaultValue": true
}
],
"submitLabel": "Save User",
"cancelLabel": "Cancel"
},
"products": {
"fields": [
{
"name": "name",
"type": "text",
"label": "Product Name",
"placeholder": "Enter product name",
"required": true,
"minLength": 3,
"maxLength": 200
},
{
"name": "description",
"type": "textarea",
"label": "Description",
"placeholder": "Product description",
"rows": 4,
"maxLength": 1000
},
{
"name": "price",
"type": "number",
"label": "Price",
"placeholder": "0.00",
"required": true,
"min": 0,
"step": 0.01,
"prefix": "$"
},
{
"name": "stock",
"type": "number",
"label": "Stock Quantity",
"placeholder": "0",
"required": true,
"min": 0,
"step": 1
},
{
"name": "category",
"type": "select",
"label": "Category",
"required": true,
"options": [
{ "value": "electronics", "label": "Electronics" },
{ "value": "clothing", "label": "Clothing" },
{ "value": "food", "label": "Food" },
{ "value": "books", "label": "Books" }
]
},
{
"name": "available",
"type": "checkbox",
"label": "Available for Purchase",
"defaultValue": true
}
],
"submitLabel": "Save Product",
"cancelLabel": "Cancel"
}
},
"validationRules": {
"email": {
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"message": "Please enter a valid email address"
},
"phone": {
"pattern": "^[+]?[(]?[0-9]{1,4}[)]?[-\\s\\.]?[(]?[0-9]{1,4}[)]?[-\\s\\.]?[0-9]{1,9}$",
"message": "Please enter a valid phone number"
},
"url": {
"pattern": "^(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$",
"message": "Please enter a valid URL"
},
"number": {
"pattern": "^-?\\d*\\.?\\d+$",
"message": "Please enter a valid number"
},
"integer": {
"pattern": "^-?\\d+$",
"message": "Please enter a valid integer"
},
"alphanumeric": {
"pattern": "^[a-zA-Z0-9]+$",
"message": "Only letters and numbers are allowed"
},
"username": {
"pattern": "^[a-zA-Z0-9_-]{3,20}$",
"message": "Username must be 3-20 characters and contain only letters, numbers, underscores, or hyphens"
}
},
"apiEndpoints": {
"users": {
"list": {
"method": "GET",
"path": "/api/admin/users",
"description": "List all users with pagination"
},
"get": {
"method": "GET",
"path": "/api/admin/users/:id",
"description": "Get a single user by ID"
},
"create": {
"method": "POST",
"path": "/api/admin/users",
"description": "Create a new user"
},
"update": {
"method": "PUT",
"path": "/api/admin/users/:id",
"description": "Update an existing user"
},
"delete": {
"method": "DELETE",
"path": "/api/admin/users/:id",
"description": "Delete a user"
}
},
"products": {
"list": {
"method": "GET",
"path": "/api/admin/products",
"description": "List all products with pagination"
},
"get": {
"method": "GET",
"path": "/api/admin/products/:id",
"description": "Get a single product by ID"
},
"create": {
"method": "POST",
"path": "/api/admin/products",
"description": "Create a new product"
},
"update": {
"method": "PUT",
"path": "/api/admin/products/:id",
"description": "Update an existing product"
},
"delete": {
"method": "DELETE",
"path": "/api/admin/products/:id",
"description": "Delete a product"
}
}
},
"permissions": {
"users": {
"create": ["admin"],
"read": ["admin", "user"],
"update": ["admin"],
"delete": ["admin"]
},
"products": {
"create": ["admin", "editor"],
"read": ["admin", "editor", "user"],
"update": ["admin", "editor"],
"delete": ["admin"]
}
},
"relationships": {
"users": {
"hasMany": ["orders", "reviews"],
"belongsTo": []
},
"products": {
"hasMany": ["reviews", "orderItems"],
"belongsTo": ["category"]
},
"orders": {
"belongsTo": ["users"],
"hasMany": ["orderItems"]
}
},
"uiViews": {
"users": {
"list": {
"component": "DataGrid",
"showActions": true,
"showSearch": true,
"showFilters": true,
"showExport": true
},
"detail": {
"component": "DetailView",
"showRelated": true,
"tabs": ["info", "orders", "activity"]
},
"create": {
"component": "FormDialog",
"redirect": "list"
},
"edit": {
"component": "FormDialog",
"redirect": "detail"
}
},
"products": {
"list": {
"component": "DataGrid",
"showActions": true,
"showSearch": true,
"showFilters": true,
"showExport": true
},
"detail": {
"component": "DetailView",
"showRelated": true,
"tabs": ["info", "reviews", "inventory"]
},
"create": {
"component": "FormDialog",
"redirect": "list"
},
"edit": {
"component": "FormDialog",
"redirect": "detail"
}
}
},
"componentTrees": {
"AdminDashboard": {
"component": "Box",
"props": {
"sx": { "display": "flex", "minHeight": "100vh" }
},
"children": [
{
"component": "Sidebar",
"props": {
"width": 240,
"variant": "permanent"
},
"children": [
{
"component": "Box",
"props": { "sx": { "p": 2 } },
"children": [
{
"component": "Typography",
"props": {
"variant": "h6",
"text": "Admin Panel"
}
}
]
},
{
"component": "NavList",
"dataSource": "navItems",
"children": [
{
"component": "NavItem",
"props": {
"icon": "{{item.icon}}",
"label": "{{item.label}}",
"href": "/admin/{{item.id}}"
}
}
]
}
]
},
{
"component": "Box",
"props": {
"sx": { "flexGrow": 1, "display": "flex", "flexDirection": "column" }
},
"children": [
{
"component": "AppBar",
"props": {
"position": "sticky",
"elevation": 1
},
"children": [
{
"component": "Toolbar",
"children": [
{
"component": "Typography",
"props": {
"variant": "h6",
"text": "{{pageTitle}}"
}
},
{
"component": "Box",
"props": { "sx": { "flexGrow": 1 } }
},
{
"component": "IconButton",
"props": {
"icon": "AccountCircle",
"onClick": "openUserMenu"
}
}
]
}
]
},
{
"component": "Box",
"props": {
"sx": { "p": 3, "flexGrow": 1 }
},
"children": [
{
"component": "Outlet",
"comment": "Child routes render here"
}
]
}
]
}
]
},
"ResourceListPage": {
"component": "Box",
"children": [
{
"component": "Box",
"props": {
"sx": { "display": "flex", "justifyContent": "space-between", "mb": 3 }
},
"children": [
{
"component": "Typography",
"props": {
"variant": "h4",
"text": "{{resourceName}}"
}
},
{
"component": "Box",
"props": { "sx": { "display": "flex", "gap": 2 } },
"children": [
{
"component": "Button",
"condition": "hasPermission('create')",
"props": {
"variant": "contained",
"startIcon": "Add",
"text": "Create New",
"onClick": "openCreateDialog"
}
},
{
"component": "Button",
"condition": "features.enableExport",
"props": {
"variant": "outlined",
"startIcon": "Download",
"text": "Export",
"onClick": "handleExport"
}
}
]
}
]
},
{
"component": "Paper",
"props": { "sx": { "mb": 2 } },
"condition": "features.enableSearch || features.enableFilters",
"children": [
{
"component": "Box",
"props": { "sx": { "p": 2, "display": "flex", "gap": 2 } },
"children": [
{
"component": "TextField",
"condition": "features.enableSearch",
"props": {
"placeholder": "Search...",
"variant": "outlined",
"size": "small",
"fullWidth": true,
"onChange": "handleSearch"
}
},
{
"component": "Button",
"condition": "features.enableFilters",
"props": {
"variant": "outlined",
"startIcon": "FilterList",
"text": "Filters",
"onClick": "toggleFilters"
}
}
]
}
]
},
{
"component": "DataGrid",
"dataSource": "tableData",
"props": {
"columns": "{{columns}}",
"rows": "{{rows}}",
"loading": "{{loading}}",
"onEdit": "handleEdit",
"onDelete": "handleDelete",
"primaryKey": "id"
}
},
{
"component": "Box",
"props": { "sx": { "mt": 2, "display": "flex", "justifyContent": "center" } },
"condition": "features.enablePagination",
"children": [
{
"component": "Pagination",
"props": {
"count": "{{totalPages}}",
"page": "{{currentPage}}",
"onChange": "handlePageChange"
}
}
]
}
]
},
"ResourceDetailPage": {
"component": "Box",
"children": [
{
"component": "Box",
"props": { "sx": { "display": "flex", "justifyContent": "space-between", "mb": 3 } },
"children": [
{
"component": "Box",
"children": [
{
"component": "Button",
"props": {
"startIcon": "ArrowBack",
"text": "Back to List",
"onClick": "goBack"
}
},
{
"component": "Typography",
"props": {
"variant": "h4",
"text": "{{resourceName}} #{{id}}",
"sx": { "mt": 2 }
}
}
]
},
{
"component": "Box",
"props": { "sx": { "display": "flex", "gap": 2 } },
"children": [
{
"component": "Button",
"condition": "hasPermission('update')",
"props": {
"variant": "contained",
"startIcon": "Edit",
"text": "Edit",
"onClick": "openEditDialog"
}
},
{
"component": "Button",
"condition": "hasPermission('delete')",
"props": {
"variant": "outlined",
"color": "error",
"startIcon": "Delete",
"text": "Delete",
"onClick": "openDeleteDialog"
}
}
]
}
]
},
{
"component": "Tabs",
"props": {
"value": "{{activeTab}}",
"onChange": "handleTabChange"
},
"children": [
{
"component": "Tab",
"forEach": "tabs",
"props": {
"label": "{{tab.label}}",
"value": "{{tab.value}}"
}
}
]
},
{
"component": "TabPanel",
"forEach": "tabs",
"props": {
"value": "{{tab.value}}",
"activeTab": "{{activeTab}}"
},
"children": [
{
"component": "Paper",
"props": { "sx": { "p": 3 } },
"children": [
{
"component": "Grid",
"props": { "container": true, "spacing": 2 },
"children": [
{
"component": "Grid",
"forEach": "columns",
"props": {
"item": true,
"xs": 12,
"sm": 6,
"md": 4
},
"children": [
{
"component": "Typography",
"props": {
"variant": "caption",
"color": "text.secondary",
"text": "{{column.label}}"
}
},
{
"component": "Typography",
"props": {
"variant": "body1",
"text": "{{data[column.name]}}"
}
}
]
}
]
}
]
}
]
},
{
"component": "Box",
"condition": "relationships && relationships.hasMany.length > 0",
"props": { "sx": { "mt": 4 } },
"children": [
{
"component": "Typography",
"props": {
"variant": "h6",
"text": "Related Records",
"sx": { "mb": 2 }
}
},
{
"component": "Accordion",
"forEach": "relationships.hasMany",
"children": [
{
"component": "AccordionSummary",
"props": {
"expandIcon": "ExpandMore"
},
"children": [
{
"component": "Typography",
"props": {
"text": "{{relation.name}} ({{relation.count}})"
}
}
]
},
{
"component": "AccordionDetails",
"children": [
{
"component": "DataGrid",
"props": {
"columns": "{{relation.columns}}",
"rows": "{{relation.data}}",
"size": "small"
}
}
]
}
]
}
]
}
]
},
"FormDialogTree": {
"component": "Dialog",
"props": {
"open": "{{open}}",
"onClose": "handleClose",
"maxWidth": "md",
"fullWidth": true
},
"children": [
{
"component": "DialogTitle",
"children": [
{
"component": "Typography",
"props": {
"variant": "h6",
"text": "{{title}}"
}
}
]
},
{
"component": "DialogContent",
"children": [
{
"component": "Box",
"props": { "sx": { "pt": 2 } },
"children": [
{
"component": "Grid",
"props": { "container": true, "spacing": 2 },
"children": [
{
"component": "Grid",
"forEach": "formFields",
"props": {
"item": true,
"xs": 12,
"sm": "{{field.fullWidth ? 12 : 6}}"
},
"children": [
{
"component": "DynamicField",
"props": {
"field": "{{field}}",
"value": "{{values[field.name]}}",
"error": "{{errors[field.name]}}",
"onChange": "handleFieldChange"
}
}
]
}
]
}
]
}
]
},
{
"component": "DialogActions",
"children": [
{
"component": "Button",
"props": {
"text": "{{cancelLabel}}",
"onClick": "handleClose"
}
},
{
"component": "Button",
"props": {
"variant": "contained",
"text": "{{submitLabel}}",
"onClick": "handleSubmit",
"disabled": "{{loading || !isValid}}"
}
}
]
}
]
},
"DashboardStatsCards": {
"component": "Grid",
"props": { "container": true, "spacing": 3 },
"children": [
{
"component": "Grid",
"forEach": "statsCards",
"props": {
"item": true,
"xs": 12,
"sm": 6,
"md": 3
},
"children": [
{
"component": "Card",
"children": [
{
"component": "CardContent",
"children": [
{
"component": "Box",
"props": {
"sx": { "display": "flex", "alignItems": "center", "mb": 2 }
},
"children": [
{
"component": "Icon",
"props": {
"name": "{{card.icon}}",
"color": "{{card.color}}",
"sx": { "fontSize": 40, "mr": 2 }
}
},
{
"component": "Box",
"children": [
{
"component": "Typography",
"props": {
"variant": "h4",
"text": "{{card.value}}"
}
},
{
"component": "Typography",
"props": {
"variant": "body2",
"color": "text.secondary",
"text": "{{card.label}}"
}
}
]
}
]
},
{
"component": "Box",
"condition": "card.change",
"props": {
"sx": { "display": "flex", "alignItems": "center" }
},
"children": [
{
"component": "Icon",
"props": {
"name": "{{card.change > 0 ? 'TrendingUp' : 'TrendingDown'}}",
"color": "{{card.change > 0 ? 'success' : 'error'}}",
"fontSize": "small"
}
},
{
"component": "Typography",
"props": {
"variant": "caption",
"color": "{{card.change > 0 ? 'success.main' : 'error.main'}}",
"text": "{{Math.abs(card.change)}}%"
}
}
]
}
]
}
]
}
]
}
]
}
},
"features": [
{
"id": "database-crud",

View File

@@ -17,6 +17,17 @@ import {
getTableFeatures,
getColumnFeatures,
getComponentLayout,
getFormSchema,
getValidationRule,
getApiEndpoints,
getApiEndpoint,
getPermissions,
hasPermission,
getRelationships,
getUiViews,
getUiView,
getComponentTree,
getAllComponentTrees,
} from './featureConfig';
describe('FeatureConfig', () => {
@@ -678,4 +689,357 @@ describe('FeatureConfig', () => {
expect(layout).toBeUndefined();
});
});
describe('getFormSchema', () => {
it('should return form schema for users table', () => {
const schema = getFormSchema('users');
expect(schema).toBeDefined();
expect(schema?.fields).toBeDefined();
expect(Array.isArray(schema?.fields)).toBe(true);
expect(schema?.submitLabel).toBe('Save User');
expect(schema?.cancelLabel).toBe('Cancel');
});
it('should have name field in users schema', () => {
const schema = getFormSchema('users');
const nameField = schema?.fields.find(f => f.name === 'name');
expect(nameField).toBeDefined();
expect(nameField?.type).toBe('text');
expect(nameField?.required).toBe(true);
});
it('should return form schema for products table', () => {
const schema = getFormSchema('products');
expect(schema).toBeDefined();
expect(schema?.fields).toBeDefined();
expect(schema?.submitLabel).toBe('Save Product');
});
it('should have price field with number type in products schema', () => {
const schema = getFormSchema('products');
const priceField = schema?.fields.find(f => f.name === 'price');
expect(priceField).toBeDefined();
expect(priceField?.type).toBe('number');
expect(priceField?.required).toBe(true);
expect(priceField?.prefix).toBe('$');
});
});
describe('getValidationRule', () => {
it('should return validation rule for email', () => {
const rule = getValidationRule('email');
expect(rule).toBeDefined();
expect(rule?.pattern).toBeDefined();
expect(rule?.message).toContain('email');
});
it('should return validation rule for phone', () => {
const rule = getValidationRule('phone');
expect(rule).toBeDefined();
expect(rule?.pattern).toBeDefined();
expect(rule?.message).toContain('phone');
});
it('should return validation rule for number', () => {
const rule = getValidationRule('number');
expect(rule).toBeDefined();
expect(rule?.message).toContain('number');
});
});
describe('getApiEndpoints', () => {
it('should return all endpoints for users resource', () => {
const endpoints = getApiEndpoints('users');
expect(endpoints).toBeDefined();
expect(endpoints?.list).toBeDefined();
expect(endpoints?.get).toBeDefined();
expect(endpoints?.create).toBeDefined();
expect(endpoints?.update).toBeDefined();
expect(endpoints?.delete).toBeDefined();
});
it('should return all endpoints for products resource', () => {
const endpoints = getApiEndpoints('products');
expect(endpoints).toBeDefined();
expect(endpoints?.list).toBeDefined();
});
});
describe('getApiEndpoint', () => {
it('should return list endpoint for users', () => {
const endpoint = getApiEndpoint('users', 'list');
expect(endpoint).toBeDefined();
expect(endpoint?.method).toBe('GET');
expect(endpoint?.path).toBe('/api/admin/users');
});
it('should return create endpoint for users', () => {
const endpoint = getApiEndpoint('users', 'create');
expect(endpoint).toBeDefined();
expect(endpoint?.method).toBe('POST');
expect(endpoint?.path).toBe('/api/admin/users');
});
it('should return update endpoint for products', () => {
const endpoint = getApiEndpoint('products', 'update');
expect(endpoint).toBeDefined();
expect(endpoint?.method).toBe('PUT');
expect(endpoint?.path).toBe('/api/admin/products/:id');
});
});
describe('getPermissions', () => {
it('should return permissions for users resource', () => {
const permissions = getPermissions('users');
expect(permissions).toBeDefined();
expect(permissions?.create).toContain('admin');
expect(permissions?.read).toContain('admin');
expect(permissions?.read).toContain('user');
});
it('should return permissions for products resource', () => {
const permissions = getPermissions('products');
expect(permissions).toBeDefined();
expect(permissions?.create).toContain('admin');
expect(permissions?.create).toContain('editor');
});
});
describe('hasPermission', () => {
it('should return true when user has permission', () => {
expect(hasPermission('users', 'create', 'admin')).toBe(true);
expect(hasPermission('users', 'read', 'user')).toBe(true);
});
it('should return false when user does not have permission', () => {
expect(hasPermission('users', 'create', 'user')).toBe(false);
expect(hasPermission('users', 'delete', 'guest')).toBe(false);
});
it('should check product permissions correctly', () => {
expect(hasPermission('products', 'create', 'editor')).toBe(true);
expect(hasPermission('products', 'update', 'editor')).toBe(true);
expect(hasPermission('products', 'delete', 'editor')).toBe(false);
});
});
describe('getRelationships', () => {
it('should return relationships for users table', () => {
const relationships = getRelationships('users');
expect(relationships).toBeDefined();
expect(relationships?.hasMany).toContain('orders');
expect(relationships?.hasMany).toContain('reviews');
});
it('should return relationships for products table', () => {
const relationships = getRelationships('products');
expect(relationships).toBeDefined();
expect(relationships?.hasMany).toContain('reviews');
expect(relationships?.belongsTo).toContain('category');
});
it('should return relationships for orders table', () => {
const relationships = getRelationships('orders');
expect(relationships).toBeDefined();
expect(relationships?.belongsTo).toContain('users');
expect(relationships?.hasMany).toContain('orderItems');
});
});
describe('getUiViews', () => {
it('should return all views for users resource', () => {
const views = getUiViews('users');
expect(views).toBeDefined();
expect(views?.list).toBeDefined();
expect(views?.detail).toBeDefined();
expect(views?.create).toBeDefined();
expect(views?.edit).toBeDefined();
});
it('should return all views for products resource', () => {
const views = getUiViews('products');
expect(views).toBeDefined();
expect(views?.list).toBeDefined();
});
});
describe('getUiView', () => {
it('should return list view configuration for users', () => {
const view = getUiView('users', 'list');
expect(view).toBeDefined();
expect(view?.component).toBe('DataGrid');
expect(view?.showActions).toBe(true);
expect(view?.showSearch).toBe(true);
expect(view?.showFilters).toBe(true);
});
it('should return detail view configuration for users', () => {
const view = getUiView('users', 'detail');
expect(view).toBeDefined();
expect(view?.component).toBe('DetailView');
expect(view?.showRelated).toBe(true);
expect(view?.tabs).toContain('info');
expect(view?.tabs).toContain('orders');
});
it('should return create view configuration with redirect', () => {
const view = getUiView('users', 'create');
expect(view).toBeDefined();
expect(view?.component).toBe('FormDialog');
expect(view?.redirect).toBe('list');
});
it('should return edit view configuration for products', () => {
const view = getUiView('products', 'edit');
expect(view).toBeDefined();
expect(view?.redirect).toBe('detail');
});
});
describe('getComponentTree', () => {
it('should return component tree for AdminDashboard', () => {
const tree = getComponentTree('AdminDashboard');
expect(tree).toBeDefined();
expect(tree?.component).toBe('Box');
expect(tree?.children).toBeDefined();
expect(Array.isArray(tree?.children)).toBe(true);
});
it('should have Sidebar in AdminDashboard tree', () => {
const tree = getComponentTree('AdminDashboard');
const sidebar = tree?.children?.find(child => child.component === 'Sidebar');
expect(sidebar).toBeDefined();
expect(sidebar?.props?.width).toBe(240);
});
it('should return component tree for ResourceListPage', () => {
const tree = getComponentTree('ResourceListPage');
expect(tree).toBeDefined();
expect(tree?.component).toBe('Box');
expect(tree?.children).toBeDefined();
});
it('should have DataGrid in ResourceListPage tree', () => {
const tree = getComponentTree('ResourceListPage');
function findComponent(node: any, componentName: string): any {
if (node.component === componentName) return node;
if (node.children) {
for (const child of node.children) {
const found = findComponent(child, componentName);
if (found) return found;
}
}
return null;
}
const dataGrid = findComponent(tree, 'DataGrid');
expect(dataGrid).toBeDefined();
expect(dataGrid?.dataSource).toBe('tableData');
});
it('should return component tree for FormDialogTree', () => {
const tree = getComponentTree('FormDialogTree');
expect(tree).toBeDefined();
expect(tree?.component).toBe('Dialog');
});
it('should have conditional rendering in component tree', () => {
const tree = getComponentTree('ResourceListPage');
function findNodeWithCondition(node: any): any {
if (node.condition) return node;
if (node.children) {
for (const child of node.children) {
const found = findNodeWithCondition(child);
if (found) return found;
}
}
return null;
}
const conditionalNode = findNodeWithCondition(tree);
expect(conditionalNode).toBeDefined();
expect(conditionalNode?.condition).toBeDefined();
});
it('should have forEach loops in component tree', () => {
const tree = getComponentTree('ResourceDetailPage');
function findNodeWithForEach(node: any): any {
if (node.forEach) return node;
if (node.children) {
for (const child of node.children) {
const found = findNodeWithForEach(child);
if (found) return found;
}
}
return null;
}
const loopNode = findNodeWithForEach(tree);
expect(loopNode).toBeDefined();
expect(loopNode?.forEach).toBeDefined();
});
});
describe('getAllComponentTrees', () => {
it('should return all component trees', () => {
const trees = getAllComponentTrees();
expect(trees).toBeDefined();
expect(typeof trees).toBe('object');
});
it('should include AdminDashboard tree', () => {
const trees = getAllComponentTrees();
expect(trees.AdminDashboard).toBeDefined();
});
it('should include ResourceListPage tree', () => {
const trees = getAllComponentTrees();
expect(trees.ResourceListPage).toBeDefined();
});
it('should include FormDialogTree tree', () => {
const trees = getAllComponentTrees();
expect(trees.FormDialogTree).toBeDefined();
});
it('should include DashboardStatsCards tree', () => {
const trees = getAllComponentTrees();
expect(trees.DashboardStatsCards).toBeDefined();
});
});
});

View File

@@ -109,6 +109,79 @@ export type ComponentLayout = {
[key: string]: any;
};
export type FormField = {
name: string;
type: 'text' | 'email' | 'number' | 'textarea' | 'select' | 'checkbox' | 'date' | 'datetime';
label: string;
placeholder?: string;
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
step?: number;
rows?: number;
defaultValue?: any;
options?: Array<{ value: string; label: string }>;
validation?: string;
prefix?: string;
suffix?: string;
};
export type FormSchema = {
fields: FormField[];
submitLabel: string;
cancelLabel: string;
};
export type ValidationRule = {
pattern: string;
message: string;
};
export type ApiEndpoint = {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
path: string;
description: string;
};
export type Permissions = {
create?: string[];
read?: string[];
update?: string[];
delete?: string[];
};
export type Relationships = {
hasMany?: string[];
belongsTo?: string[];
hasOne?: string[];
belongsToMany?: string[];
};
export type UiView = {
component: string;
showActions?: boolean;
showSearch?: boolean;
showFilters?: boolean;
showExport?: boolean;
showRelated?: boolean;
tabs?: string[];
redirect?: string;
};
export type ComponentNode = {
component: string;
props?: Record<string, any>;
children?: ComponentNode[];
condition?: string;
forEach?: string;
dataSource?: string;
comment?: string;
};
export type ComponentTree = ComponentNode;
// Type definition for the features config structure
type FeaturesConfig = {
translations?: Translations;
@@ -118,6 +191,13 @@ type FeaturesConfig = {
tableFeatures?: Record<string, TableFeatures>;
columnFeatures?: Record<string, ColumnFeatures>;
componentLayouts?: Record<string, ComponentLayout>;
formSchemas?: Record<string, FormSchema>;
validationRules?: Record<string, ValidationRule>;
apiEndpoints?: Record<string, Record<string, ApiEndpoint>>;
permissions?: Record<string, Permissions>;
relationships?: Record<string, Relationships>;
uiViews?: Record<string, Record<string, UiView>>;
componentTrees?: Record<string, ComponentTree>;
features: Feature[];
dataTypes: DataType[];
constraintTypes?: ConstraintType[];
@@ -208,3 +288,49 @@ export function getColumnFeatures(columnName: string): ColumnFeatures | undefine
export function getComponentLayout(componentName: string): ComponentLayout | undefined {
return config.componentLayouts?.[componentName];
}
export function getFormSchema(tableName: string): FormSchema | undefined {
return config.formSchemas?.[tableName];
}
export function getValidationRule(ruleName: string): ValidationRule | undefined {
return config.validationRules?.[ruleName];
}
export function getApiEndpoints(resourceName: string): Record<string, ApiEndpoint> | undefined {
return config.apiEndpoints?.[resourceName];
}
export function getApiEndpoint(resourceName: string, action: string): ApiEndpoint | undefined {
return config.apiEndpoints?.[resourceName]?.[action];
}
export function getPermissions(resourceName: string): Permissions | undefined {
return config.permissions?.[resourceName];
}
export function hasPermission(resourceName: string, action: string, userRole: string): boolean {
const permissions = config.permissions?.[resourceName];
const allowedRoles = permissions?.[action as keyof Permissions];
return allowedRoles?.includes(userRole) ?? false;
}
export function getRelationships(tableName: string): Relationships | undefined {
return config.relationships?.[tableName];
}
export function getUiViews(resourceName: string): Record<string, UiView> | undefined {
return config.uiViews?.[resourceName];
}
export function getUiView(resourceName: string, viewName: string): UiView | undefined {
return config.uiViews?.[resourceName]?.[viewName];
}
export function getComponentTree(treeName: string): ComponentTree | undefined {
return config.componentTrees?.[treeName];
}
export function getAllComponentTrees(): Record<string, ComponentTree> {
return config.componentTrees || {};
}