@specverse/engines 4.1.5 → 4.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/libs/instance-factories/applications/templates/generic/backend-env-generator.js +22 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +66 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +54 -0
- package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +290 -0
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +530 -0
- package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +437 -0
- package/dist/libs/instance-factories/applications/templates/react/api-types-generator.js +146 -0
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +73 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +18 -0
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +99 -0
- package/dist/libs/instance-factories/applications/templates/react/gitignore-generator.js +35 -0
- package/dist/libs/instance-factories/applications/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/applications/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/applications/templates/react/main-tsx-generator.js +29 -0
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +49 -0
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +156 -0
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +935 -0
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +101 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +50 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +646 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +65 -0
- package/dist/libs/instance-factories/applications/templates/react/tsconfig-generator.js +28 -0
- package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +132 -0
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +355 -0
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +91 -0
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +79 -0
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +42 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-bin-wrapper-generator.js +11 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +111 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +928 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +83 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js +91 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js +86 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.js +93 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +280 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +125 -0
- package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js +25 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +371 -0
- package/dist/libs/instance-factories/orms/templates/prisma/services-generator.js +266 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-example-generator.js +51 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-generator.js +61 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.js +59 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +126 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/readme-generator.js +159 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +56 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.js +37 -0
- package/dist/libs/instance-factories/sdks/templates/python/sdk-generator.js +29 -0
- package/dist/libs/instance-factories/sdks/templates/typescript/sdk-generator.js +28 -0
- package/dist/libs/instance-factories/services/templates/memory/generate-interpreter.js +14 -0
- package/dist/libs/instance-factories/services/templates/memory/step-conventions-memory.js +415 -0
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +177 -0
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +413 -0
- package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +243 -0
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +264 -0
- package/dist/libs/instance-factories/services/templates/shared-patterns.js +24 -0
- package/dist/libs/instance-factories/shared/path-resolver.js +59 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/config-generator.js +13 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/docker-generator.js +16 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/config-generator.js +45 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/docker-generator.js +46 -0
- package/dist/libs/instance-factories/storage/templates/redis/config-generator.js +14 -0
- package/dist/libs/instance-factories/storage/templates/redis/docker-generator.js +16 -0
- package/dist/libs/instance-factories/test-generation.js +145 -0
- package/dist/libs/instance-factories/testing/templates/vitest/tests-generator.js +30 -0
- package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +149 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +232 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +49 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +18 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +97 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +64 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +182 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +1210 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +172 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +240 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +147 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +281 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +409 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +414 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +467 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +135 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
- package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +965 -0
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +238 -0
- package/dist/libs/instance-factories/validation/templates/zod/validation-generator.js +25 -0
- package/dist/libs/instance-factories/views/index.js +48 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +742 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +824 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +719 -0
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +45 -0
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +779 -0
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +285 -0
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +46 -0
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +111 -0
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +21 -0
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +299 -0
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +136 -0
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +107 -0
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +179 -0
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +7 -0
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +56 -0
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +27 -0
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +29 -0
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +261 -0
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +34 -0
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +800 -0
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +305 -0
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +517 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +445 -0
- package/dist/libs/instance-factories/views/templates/shared/index.js +80 -0
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +210 -0
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +492 -0
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +321 -0
- package/package.json +3 -2
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
async function generate(context) {
|
|
2
|
+
const files = {};
|
|
3
|
+
files["FormView.tsx"] = generateFormView();
|
|
4
|
+
files["ListView.tsx"] = generateListView();
|
|
5
|
+
files["DetailView.tsx"] = generateDetailView();
|
|
6
|
+
files["DashboardView.tsx"] = generateDashboardView();
|
|
7
|
+
return files;
|
|
8
|
+
}
|
|
9
|
+
function generateFormView() {
|
|
10
|
+
return `/**
|
|
11
|
+
* StandardFormView - Controller-Based Form View Component
|
|
12
|
+
*
|
|
13
|
+
* LOCAL VERSION - Uses local hooks with instance-factory-specific API client
|
|
14
|
+
*
|
|
15
|
+
* Supports both Create and Edit modes with:
|
|
16
|
+
* - Controller-driven form generation
|
|
17
|
+
* - Smart input types (text, number, boolean, relationship selectors)
|
|
18
|
+
* - Auto-generated field detection
|
|
19
|
+
* - Validation and error handling
|
|
20
|
+
* - Lifecycle management (Edit/Evolve tabs)
|
|
21
|
+
* - Foreign key relationship fields
|
|
22
|
+
* - Two-panel layout (form + entity list)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
26
|
+
import { useAppStore } from '../../stores/appStore';
|
|
27
|
+
import { useEntitiesQuery, useModelSchemaQuery, useExecuteOperationMutation, useAvailableTransitionsQuery, useTransitionStateMutation } from '../../hooks/useApi';
|
|
28
|
+
import { isAutoGeneratedField, isFieldRequired, initializeFormData } from '../../utils/field-helpers';
|
|
29
|
+
import { RelationshipField } from '../../components/RelationshipField';
|
|
30
|
+
import type { View, Entity } from '../../types/api';
|
|
31
|
+
|
|
32
|
+
type FormMode = 'create' | 'update';
|
|
33
|
+
type ActiveTab = 'edit' | 'evolve';
|
|
34
|
+
|
|
35
|
+
interface StandardFormViewProps {
|
|
36
|
+
view: View;
|
|
37
|
+
spec?: any; // Full spec for controller access
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function FormView({ view, spec }: StandardFormViewProps) {
|
|
41
|
+
// Determine controller and model
|
|
42
|
+
let controllerName: string;
|
|
43
|
+
let modelName: string;
|
|
44
|
+
|
|
45
|
+
if (view.controller && spec) {
|
|
46
|
+
// New: Controller-based
|
|
47
|
+
controllerName = view.controller;
|
|
48
|
+
const controller = spec.controllers[view.controller];
|
|
49
|
+
modelName = controller.model;
|
|
50
|
+
} else {
|
|
51
|
+
// Legacy: Model-based
|
|
52
|
+
modelName = view.model as string;
|
|
53
|
+
controllerName = \`\${modelName}Controller\`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const [selectedEntity, setSelectedEntity] = useState<Entity | null>(null);
|
|
57
|
+
const [formMode, setFormMode] = useState<FormMode>('create');
|
|
58
|
+
const [activeTab, setActiveTab] = useState<ActiveTab>('edit');
|
|
59
|
+
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
60
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
61
|
+
const [selectedLifecycleStates, setSelectedLifecycleStates] = useState<Record<string, string>>({});
|
|
62
|
+
|
|
63
|
+
// Fetch entities and schema - USES LOCAL HOOKS
|
|
64
|
+
const { data: entities = [], isLoading: entitiesLoading } = useEntitiesQuery(controllerName, modelName);
|
|
65
|
+
const { data: schema } = useModelSchemaQuery(modelName);
|
|
66
|
+
const { mutate: executeOperation, isPending } = useExecuteOperationMutation();
|
|
67
|
+
const { mutate: transitionState, isPending: isTransitioning } = useTransitionStateMutation();
|
|
68
|
+
|
|
69
|
+
const attributes = schema?.attributes || {};
|
|
70
|
+
const relationships = schema?.relationships || {};
|
|
71
|
+
const lifecycles = schema?.lifecycles || {};
|
|
72
|
+
|
|
73
|
+
// Get entities store for relationship resolution
|
|
74
|
+
const entitiesStore = useAppStore((state) => state.entities);
|
|
75
|
+
|
|
76
|
+
// Sync selected entity with updated entities list
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (selectedEntity && entities.length > 0) {
|
|
79
|
+
const updatedEntity = entities.find((e) => e.id === selectedEntity.id);
|
|
80
|
+
if (updatedEntity) {
|
|
81
|
+
const oldStates = JSON.stringify(selectedEntity.metadata?.lifecycleStates || {});
|
|
82
|
+
const newStates = JSON.stringify(updatedEntity.metadata?.lifecycleStates || {});
|
|
83
|
+
|
|
84
|
+
if (oldStates !== newStates) {
|
|
85
|
+
setSelectedEntity(updatedEntity);
|
|
86
|
+
setSelectedLifecycleStates({ ...(updatedEntity.metadata?.lifecycleStates || {}) });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, [entities, selectedEntity?.id]);
|
|
91
|
+
|
|
92
|
+
// Initialize form data when schema changes or when switching modes
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (selectedEntity && formMode === 'update') {
|
|
95
|
+
setFormData({ ...selectedEntity.data });
|
|
96
|
+
const currentStates = selectedEntity.metadata?.lifecycleStates || {};
|
|
97
|
+
setSelectedLifecycleStates({ ...currentStates });
|
|
98
|
+
} else if (schema?.attributes) {
|
|
99
|
+
setFormData(initializeFormData(schema.attributes, { includeAutoGenerated: false }));
|
|
100
|
+
setSelectedLifecycleStates({});
|
|
101
|
+
}
|
|
102
|
+
}, [selectedEntity, formMode, schema]);
|
|
103
|
+
|
|
104
|
+
// Check if field should be shown
|
|
105
|
+
const shouldShowField = (attrName: string, attrDef: any): boolean => {
|
|
106
|
+
const isAuto = isAutoGeneratedField(attrName, attrDef);
|
|
107
|
+
// In create mode, hide auto-generated fields
|
|
108
|
+
if (formMode === 'create' && isAuto) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// In update mode, show all fields
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Handle form submission
|
|
116
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
setErrors({});
|
|
119
|
+
|
|
120
|
+
const operation = formMode === 'create' ? 'create' : 'update';
|
|
121
|
+
const params = formMode === 'update' && selectedEntity
|
|
122
|
+
? { ...formData, id: selectedEntity.id }
|
|
123
|
+
: formData;
|
|
124
|
+
|
|
125
|
+
executeOperation(
|
|
126
|
+
{ controllerName, operationName: operation, params },
|
|
127
|
+
{
|
|
128
|
+
onSuccess: () => {
|
|
129
|
+
if (formMode === 'create') {
|
|
130
|
+
setFormData(initializeFormData(schema.attributes, { includeAutoGenerated: false }));
|
|
131
|
+
}
|
|
132
|
+
setFormMode('create');
|
|
133
|
+
setSelectedEntity(null);
|
|
134
|
+
},
|
|
135
|
+
onError: (error: any) => {
|
|
136
|
+
setErrors({ _form: error.message || 'Operation failed' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Handle entity selection for editing
|
|
143
|
+
const handleEntitySelect = (entity: Entity) => {
|
|
144
|
+
setSelectedEntity(entity);
|
|
145
|
+
setFormMode('update');
|
|
146
|
+
setActiveTab('edit');
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Handle lifecycle state transition
|
|
150
|
+
const handleLifecycleTransition = (lifecycleName: string, toState: string) => {
|
|
151
|
+
if (!selectedEntity) return;
|
|
152
|
+
|
|
153
|
+
transitionState(
|
|
154
|
+
{ modelName, entityId: selectedEntity.id, toState, lifecycleName },
|
|
155
|
+
{
|
|
156
|
+
onSuccess: () => {
|
|
157
|
+
setSelectedLifecycleStates((prev) => ({ ...prev, [lifecycleName]: toState }));
|
|
158
|
+
},
|
|
159
|
+
onError: (error: any) => {
|
|
160
|
+
setErrors({ _lifecycle: error.message || 'Transition failed' });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Render field input
|
|
167
|
+
const renderFieldInput = (attrName: string, attrDef: any) => {
|
|
168
|
+
const value = formData[attrName] ?? '';
|
|
169
|
+
const isRequired = isFieldRequired(attrDef);
|
|
170
|
+
|
|
171
|
+
const handleChange = (newValue: any) => {
|
|
172
|
+
setFormData((prev) => ({ ...prev, [attrName]: newValue }));
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Boolean field
|
|
176
|
+
if (attrDef.type === 'Boolean') {
|
|
177
|
+
return (
|
|
178
|
+
<input
|
|
179
|
+
type="checkbox"
|
|
180
|
+
checked={!!value}
|
|
181
|
+
onChange={(e) => handleChange(e.target.checked)}
|
|
182
|
+
className="h-4 w-4 rounded border-gray-300"
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Number field
|
|
188
|
+
if (attrDef.type === 'Integer' || attrDef.type === 'Float' || attrDef.type === 'Decimal') {
|
|
189
|
+
return (
|
|
190
|
+
<input
|
|
191
|
+
type="number"
|
|
192
|
+
value={value}
|
|
193
|
+
onChange={(e) => handleChange(e.target.value ? Number(e.target.value) : '')}
|
|
194
|
+
required={isRequired}
|
|
195
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Text field (default)
|
|
201
|
+
return (
|
|
202
|
+
<input
|
|
203
|
+
type="text"
|
|
204
|
+
value={value}
|
|
205
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
206
|
+
required={isRequired}
|
|
207
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="flex gap-4 h-full">
|
|
214
|
+
{/* Form Panel */}
|
|
215
|
+
<div className="flex-1 bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
216
|
+
<h2 className="text-2xl font-bold mb-4 text-gray-800 dark:text-white">
|
|
217
|
+
{formMode === 'create' ? \`Create \${modelName}\` : \`Edit \${modelName}\`}
|
|
218
|
+
</h2>
|
|
219
|
+
|
|
220
|
+
{/* Tabs for Edit/Evolve modes */}
|
|
221
|
+
{formMode === 'update' && Object.keys(lifecycles).length > 0 && (
|
|
222
|
+
<div className="flex gap-2 mb-4 border-b border-gray-200">
|
|
223
|
+
<button
|
|
224
|
+
onClick={() => setActiveTab('edit')}
|
|
225
|
+
className={\`px-4 py-2 font-medium \${activeTab === 'edit' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500'}\`}
|
|
226
|
+
>
|
|
227
|
+
Edit Data
|
|
228
|
+
</button>
|
|
229
|
+
<button
|
|
230
|
+
onClick={() => setActiveTab('evolve')}
|
|
231
|
+
className={\`px-4 py-2 font-medium \${activeTab === 'evolve' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500'}\`}
|
|
232
|
+
>
|
|
233
|
+
Lifecycle
|
|
234
|
+
</button>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{activeTab === 'edit' ? (
|
|
239
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
240
|
+
{errors._form && (
|
|
241
|
+
<div className="p-3 bg-red-100 text-red-700 rounded-md">{errors._form}</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
{/* Attributes */}
|
|
245
|
+
{Object.entries(attributes).map(([attrName, attrDef]: [string, any]) => {
|
|
246
|
+
if (!shouldShowField(attrName, attrDef)) return null;
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div key={attrName}>
|
|
250
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
251
|
+
{attrName}
|
|
252
|
+
{isFieldRequired(attrDef) && <span className="text-red-500">*</span>}
|
|
253
|
+
</label>
|
|
254
|
+
{renderFieldInput(attrName, attrDef)}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
|
|
259
|
+
{/* Relationships */}
|
|
260
|
+
{Object.entries(relationships).map(([relName, relDef]: [string, any]) => (
|
|
261
|
+
<RelationshipField
|
|
262
|
+
key={relName}
|
|
263
|
+
name={relName}
|
|
264
|
+
definition={relDef}
|
|
265
|
+
value={formData[relName]}
|
|
266
|
+
onChange={(value) => setFormData((prev) => ({ ...prev, [relName]: value }))}
|
|
267
|
+
entities={entitiesStore}
|
|
268
|
+
/>
|
|
269
|
+
))}
|
|
270
|
+
|
|
271
|
+
<div className="flex gap-2 pt-4">
|
|
272
|
+
<button
|
|
273
|
+
type="submit"
|
|
274
|
+
disabled={isPending}
|
|
275
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
|
|
276
|
+
>
|
|
277
|
+
{isPending ? 'Saving...' : formMode === 'create' ? 'Create' : 'Update'}
|
|
278
|
+
</button>
|
|
279
|
+
{formMode === 'update' && (
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
onClick={() => {
|
|
283
|
+
setFormMode('create');
|
|
284
|
+
setSelectedEntity(null);
|
|
285
|
+
setFormData(initializeFormData(schema.attributes, { includeAutoGenerated: false }));
|
|
286
|
+
}}
|
|
287
|
+
className="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600"
|
|
288
|
+
>
|
|
289
|
+
Cancel
|
|
290
|
+
</button>
|
|
291
|
+
)}
|
|
292
|
+
</div>
|
|
293
|
+
</form>
|
|
294
|
+
) : (
|
|
295
|
+
/* Lifecycle Tab */
|
|
296
|
+
<div className="space-y-4">
|
|
297
|
+
{errors._lifecycle && (
|
|
298
|
+
<div className="p-3 bg-red-100 text-red-700 rounded-md">{errors._lifecycle}</div>
|
|
299
|
+
)}
|
|
300
|
+
|
|
301
|
+
{Object.entries(lifecycles).map(([lifecycleName, lifecycle]: [string, any]) => {
|
|
302
|
+
const currentState = selectedLifecycleStates[lifecycleName] || lifecycle.initial;
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<div key={lifecycleName} className="border border-gray-200 rounded-lg p-4">
|
|
306
|
+
<h3 className="font-semibold text-lg mb-2">{lifecycleName}</h3>
|
|
307
|
+
<p className="text-sm text-gray-600 mb-3">Current: {currentState}</p>
|
|
308
|
+
|
|
309
|
+
<div className="flex flex-wrap gap-2">
|
|
310
|
+
{Object.keys(lifecycle.states || {}).map((state) => (
|
|
311
|
+
<button
|
|
312
|
+
key={state}
|
|
313
|
+
onClick={() => handleLifecycleTransition(lifecycleName, state)}
|
|
314
|
+
disabled={isTransitioning || state === currentState}
|
|
315
|
+
className={\`px-3 py-1 rounded-md \${state === currentState ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'} disabled:opacity-50\`}
|
|
316
|
+
>
|
|
317
|
+
{state}
|
|
318
|
+
</button>
|
|
319
|
+
))}
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
})}
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
{/* Entity List Panel */}
|
|
329
|
+
<div className="w-80 bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
330
|
+
<h3 className="text-lg font-semibold mb-3 text-gray-800 dark:text-white">
|
|
331
|
+
{modelName} List
|
|
332
|
+
</h3>
|
|
333
|
+
|
|
334
|
+
{entitiesLoading ? (
|
|
335
|
+
<p className="text-gray-500">Loading...</p>
|
|
336
|
+
) : entities.length === 0 ? (
|
|
337
|
+
<p className="text-gray-500 italic">No entities yet</p>
|
|
338
|
+
) : (
|
|
339
|
+
<div className="space-y-2">
|
|
340
|
+
{entities.map((entity) => (
|
|
341
|
+
<div
|
|
342
|
+
key={entity.id}
|
|
343
|
+
onClick={() => handleEntitySelect(entity)}
|
|
344
|
+
className={\`p-3 rounded-md cursor-pointer \${selectedEntity?.id === entity.id ? 'bg-blue-100 border-blue-500' : 'bg-gray-50 hover:bg-gray-100'} border\`}
|
|
345
|
+
>
|
|
346
|
+
<p className="font-medium text-sm">{entity.id}</p>
|
|
347
|
+
{Object.entries(entity.data).slice(0, 2).map(([key, value]) => (
|
|
348
|
+
<p key={key} className="text-xs text-gray-600">
|
|
349
|
+
{key}: {String(value)}
|
|
350
|
+
</p>
|
|
351
|
+
))}
|
|
352
|
+
</div>
|
|
353
|
+
))}
|
|
354
|
+
</div>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export default FormView;
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
function generateListView() {
|
|
365
|
+
return `/**
|
|
366
|
+
* StandardListView - Controller-Based List View Component
|
|
367
|
+
*
|
|
368
|
+
* LOCAL VERSION - Uses local hooks with instance-factory-specific API client
|
|
369
|
+
*/
|
|
370
|
+
|
|
371
|
+
import { useEntitiesQuery, useModelSchemaQuery } from '../../hooks/useApi';
|
|
372
|
+
import type { View } from '../../types/api';
|
|
373
|
+
|
|
374
|
+
interface StandardListViewProps {
|
|
375
|
+
view: View;
|
|
376
|
+
spec?: any;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function ListView({ view, spec }: StandardListViewProps) {
|
|
380
|
+
let controllerName: string;
|
|
381
|
+
let modelName: string;
|
|
382
|
+
|
|
383
|
+
if (view.controller && spec) {
|
|
384
|
+
controllerName = view.controller;
|
|
385
|
+
const controller = spec.controllers[view.controller];
|
|
386
|
+
modelName = controller.model;
|
|
387
|
+
} else {
|
|
388
|
+
modelName = view.model as string;
|
|
389
|
+
controllerName = \`\${modelName}Controller\`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const { data: entities = [], isLoading } = useEntitiesQuery(controllerName, modelName);
|
|
393
|
+
const { data: schema } = useModelSchemaQuery(modelName);
|
|
394
|
+
|
|
395
|
+
if (isLoading) {
|
|
396
|
+
return <div className="p-4">Loading...</div>;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div className="p-6">
|
|
401
|
+
<h2 className="text-2xl font-bold mb-4">{modelName} List</h2>
|
|
402
|
+
<div className="overflow-x-auto">
|
|
403
|
+
<table className="min-w-full bg-white border border-gray-200">
|
|
404
|
+
<thead className="bg-gray-50">
|
|
405
|
+
<tr>
|
|
406
|
+
<th className="px-4 py-2 border-b">ID</th>
|
|
407
|
+
{schema && Object.keys(schema.attributes || {}).map((attr) => (
|
|
408
|
+
<th key={attr} className="px-4 py-2 border-b">{attr}</th>
|
|
409
|
+
))}
|
|
410
|
+
</tr>
|
|
411
|
+
</thead>
|
|
412
|
+
<tbody>
|
|
413
|
+
{entities.map((entity) => (
|
|
414
|
+
<tr key={entity.id} className="hover:bg-gray-50">
|
|
415
|
+
<td className="px-4 py-2 border-b">{entity.id}</td>
|
|
416
|
+
{schema && Object.keys(schema.attributes || {}).map((attr) => (
|
|
417
|
+
<td key={attr} className="px-4 py-2 border-b">
|
|
418
|
+
{String(entity.data[attr] ?? '')}
|
|
419
|
+
</td>
|
|
420
|
+
))}
|
|
421
|
+
</tr>
|
|
422
|
+
))}
|
|
423
|
+
</tbody>
|
|
424
|
+
</table>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export default ListView;
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
function generateDetailView() {
|
|
434
|
+
return `/**
|
|
435
|
+
* StandardDetailView - Controller-Based Detail View Component
|
|
436
|
+
*
|
|
437
|
+
* LOCAL VERSION - Uses local hooks with instance-factory-specific API client
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
import { useEntitiesQuery, useModelSchemaQuery } from '../../hooks/useApi';
|
|
441
|
+
import type { View } from '../../types/api';
|
|
442
|
+
|
|
443
|
+
interface StandardDetailViewProps {
|
|
444
|
+
view: View;
|
|
445
|
+
spec?: any;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function DetailView({ view, spec }: StandardDetailViewProps) {
|
|
449
|
+
let controllerName: string;
|
|
450
|
+
let modelName: string;
|
|
451
|
+
|
|
452
|
+
if (view.controller && spec) {
|
|
453
|
+
controllerName = view.controller;
|
|
454
|
+
const controller = spec.controllers[view.controller];
|
|
455
|
+
modelName = controller.model;
|
|
456
|
+
} else {
|
|
457
|
+
modelName = view.model as string;
|
|
458
|
+
controllerName = \`\${modelName}Controller\`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const { data: entities = [] } = useEntitiesQuery(controllerName, modelName);
|
|
462
|
+
const { data: schema } = useModelSchemaQuery(modelName);
|
|
463
|
+
const entity = entities[0];
|
|
464
|
+
|
|
465
|
+
if (!entity) {
|
|
466
|
+
return <div className="p-4">No entity selected</div>;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return (
|
|
470
|
+
<div className="p-6">
|
|
471
|
+
<h2 className="text-2xl font-bold mb-4">{modelName} Detail</h2>
|
|
472
|
+
<div className="space-y-2">
|
|
473
|
+
<div>
|
|
474
|
+
<span className="font-semibold">ID:</span> {entity.id}
|
|
475
|
+
</div>
|
|
476
|
+
{schema && Object.entries(schema.attributes || {}).map(([attr, def]: [string, any]) => (
|
|
477
|
+
<div key={attr}>
|
|
478
|
+
<span className="font-semibold">{attr}:</span> {String(entity.data[attr] ?? '')}
|
|
479
|
+
</div>
|
|
480
|
+
))}
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export default DetailView;
|
|
487
|
+
`;
|
|
488
|
+
}
|
|
489
|
+
function generateDashboardView() {
|
|
490
|
+
return `/**
|
|
491
|
+
* StandardDashboardView - Controller-Based Dashboard View Component
|
|
492
|
+
*
|
|
493
|
+
* LOCAL VERSION - Uses local hooks with instance-factory-specific API client
|
|
494
|
+
*/
|
|
495
|
+
|
|
496
|
+
import type { View } from '../../types/api';
|
|
497
|
+
|
|
498
|
+
interface StandardDashboardViewProps {
|
|
499
|
+
view: View;
|
|
500
|
+
spec?: any;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function DashboardView({ view, spec }: StandardDashboardViewProps) {
|
|
504
|
+
return (
|
|
505
|
+
<div className="p-6">
|
|
506
|
+
<h2 className="text-2xl font-bold mb-4">Dashboard</h2>
|
|
507
|
+
<div className="grid grid-cols-3 gap-4">
|
|
508
|
+
<div className="bg-white p-4 rounded-lg shadow">
|
|
509
|
+
<h3 className="text-lg font-semibold">Card 1</h3>
|
|
510
|
+
<p>Content here</p>
|
|
511
|
+
</div>
|
|
512
|
+
<div className="bg-white p-4 rounded-lg shadow">
|
|
513
|
+
<h3 className="text-lg font-semibold">Card 2</h3>
|
|
514
|
+
<p>Content here</p>
|
|
515
|
+
</div>
|
|
516
|
+
<div className="bg-white p-4 rounded-lg shadow">
|
|
517
|
+
<h3 className="text-lg font-semibold">Card 3</h3>
|
|
518
|
+
<p>Content here</p>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export default DashboardView;
|
|
526
|
+
`;
|
|
527
|
+
}
|
|
528
|
+
export {
|
|
529
|
+
generate
|
|
530
|
+
};
|