mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-25 14:25:06 +00:00
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:
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 || {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user