From cf4e0fa08ba69a1f6de6248d493cdb9a9a1154fb Mon Sep 17 00:00:00 2001 From: Babayaga Date: Sat, 4 Apr 2026 18:49:33 +0200 Subject: [PATCH] types:vfs/cats/groups/posts/pages - fixes --- packages/ui/src/modules/types/TypesEditor.tsx | 14 +- .../ui/src/modules/types/TypesPlayground.tsx | 280 +++++++++--------- 2 files changed, 152 insertions(+), 142 deletions(-) diff --git a/packages/ui/src/modules/types/TypesEditor.tsx b/packages/ui/src/modules/types/TypesEditor.tsx index 98cfd0f8..d9763034 100644 --- a/packages/ui/src/modules/types/TypesEditor.tsx +++ b/packages/ui/src/modules/types/TypesEditor.tsx @@ -264,8 +264,16 @@ export const TypesEditor: React.FC = ({ }); } else if (output.mode === 'structure') { const nestedFields = output.elements.map((el, idx) => { - let fieldType = types.find(t => t.name === `${selectedType.name}.${el.name}` && t.kind === 'field'); + // Prefer the authoritative fieldTypeId set during hydration (structureFieldToBuilderElement). + // Fall back to name-based lookup only for safety (e.g. manually-constructed elements). + // Use selectedType.name (DB name) for the lookup — NOT output.name, which may differ if + // the user renamed the type in the builder. + const explicitId = (el as any).fieldTypeId as string | undefined; + const fieldType = explicitId + ? types.find(t => t.id === explicitId && t.kind === 'field') + : types.find(t => t.name === `${selectedType.name}.${el.name}` && t.kind === 'field'); + // parentType: prefer refId (set when dragged from palette or loaded from DB via structureFieldToBuilderElement) const parentType = (el as any).refId ? types.find(t => t.id === (el as any).refId) : fieldType?.parent_type_id @@ -273,7 +281,7 @@ export const TypesEditor: React.FC = ({ : types.find(t => t.name === el.type); if (!parentType) { - console.error(`Parent type not found: ${el.type} (refId: ${(el as any).refId})`); + console.error(`Parent type not found: ${el.type} (refId: ${(el as any).refId}, fieldTypeId: ${explicitId})`); return null; } @@ -283,6 +291,8 @@ export const TypesEditor: React.FC = ({ } return { + // Send the explicit fieldTypeId so the server can UPDATE-in-place rather than INSERT. + // If fieldType is undefined (new field or stale ID gone from cache), id is undefined → INSERT. id: fieldType?.id, name: `${output.name}.${el.name}`, field_name: el.name, diff --git a/packages/ui/src/modules/types/TypesPlayground.tsx b/packages/ui/src/modules/types/TypesPlayground.tsx index d955b79e..5f7a4d03 100644 --- a/packages/ui/src/modules/types/TypesPlayground.tsx +++ b/packages/ui/src/modules/types/TypesPlayground.tsx @@ -1,140 +1,140 @@ -import React, { useEffect, useState } from 'react'; -import { fetchTypes, deleteType, TypeDefinition } from './client-types'; -import { Loader2, Plus } from "lucide-react"; -import { toast } from "sonner"; -import { T, translate } from '@/i18n'; -import { TypesList } from './TypesList'; -import { TypesEditor } from './TypesEditor'; -import { useActions } from '@/actions/useActions'; -import { Action } from '@/actions/types'; -import { TypeEditorActions } from './TypeEditorActions'; - -const TypesPlayground: React.FC = () => { - const [types, setTypes] = useState([]); - const [loading, setLoading] = useState(true); - const [selectedTypeId, setSelectedTypeId] = useState(null); - const [isBuilding, setIsBuilding] = useState(false); - - const { registerAction, updateAction, unregisterAction } = useActions(); - - const loadTypes = React.useCallback(async () => { - setLoading(true); - try { - const data = await fetchTypes(); - // console.log('types', data); - setTypes(data); - if (selectedTypeId) { - // Ensure selection is valid - const exists = data.find(t => t.id === selectedTypeId); - if (!exists) setSelectedTypeId(null); - } - } catch (error) { - console.error("Failed to fetch types", error); - toast.error(translate("Failed to load types")); - } finally { - setLoading(false); - } - }, [selectedTypeId]); - - useEffect(() => { - loadTypes(); - }, [loadTypes]); - - const handleCreateNew = React.useCallback(() => { - setSelectedTypeId(null); - setIsBuilding(true); - }, []); - - const handleSelectType = React.useCallback((t: TypeDefinition) => { - setIsBuilding(false); - setSelectedTypeId(t.id); - }, []); - - // Register "New Type" action - useEffect(() => { - const action: Action = { - id: 'types.new', - label: 'New Type', - icon: Plus, - group: 'types', - handler: handleCreateNew, - shortcut: 'mod+n' // Or something else - }; - - registerAction(action); - - return () => unregisterAction('types.new'); - }, [registerAction, unregisterAction]); - - // Update "New Type" state - useEffect(() => { - updateAction('types.new', { - disabled: isBuilding || loading, - visible: !isBuilding // Hide when building to keep toolbar clean? Or just disable? - // User requested "Gray out buttons", so maybe just disabled. - // But if we hide it, we make space for other actions. - // Let's decide to keep it visible but disabled if building, or hide it if we want to focus on editor actions. - // Logic in TypesEditor hides some actions. - // Let's hide 'types.new' when building to match 'cancel' appearing. - }); - }, [isBuilding, loading, updateAction]); - - - return ( -
- {/* Header */} -
-
-

Types Editor

-

- Manage and preview your type definitions -

-
- -
- - {/* Main Content */} -
- {loading ? ( -
- -
- ) : ( -
- {/* List Sidebar */} - - - {/* Main Content Area */} -
- t.id === selectedTypeId) || null} - isBuilding={isBuilding} - onIsBuildingChange={setIsBuilding} - onSave={loadTypes} - onDeleteRaw={deleteType} - /> -
-
- )} -
-
- ); -}; - -export default TypesPlayground; +import React, { useEffect, useState } from 'react'; +import { fetchTypes, deleteType, TypeDefinition } from './client-types'; +import { Loader2, Plus } from "lucide-react"; +import { toast } from "sonner"; +import { T, translate } from '@/i18n'; +import { TypesList } from './TypesList'; +import { TypesEditor } from './TypesEditor'; +import { useActions } from '@/actions/useActions'; +import { Action } from '@/actions/types'; +import { TypeEditorActions } from './TypeEditorActions'; + +const TypesPlayground: React.FC = () => { + const [types, setTypes] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedTypeId, setSelectedTypeId] = useState(null); + const [isBuilding, setIsBuilding] = useState(false); + + const { registerAction, updateAction, unregisterAction } = useActions(); + + const loadTypes = React.useCallback(async () => { + setLoading(true); + try { + const data = await fetchTypes(); + // console.log('types', data); + setTypes(data); + if (selectedTypeId) { + // Ensure selection is valid + const exists = data.find(t => t.id === selectedTypeId); + if (!exists) setSelectedTypeId(null); + } + } catch (error) { + console.error("Failed to fetch types", error); + toast.error(translate("Failed to load types")); + } finally { + setLoading(false); + } + }, [selectedTypeId]); + + useEffect(() => { + loadTypes(); + }, [loadTypes]); + + const handleCreateNew = React.useCallback(() => { + setSelectedTypeId(null); + setIsBuilding(true); + }, []); + + const handleSelectType = React.useCallback((t: TypeDefinition) => { + setIsBuilding(false); + setSelectedTypeId(t.id); + }, []); + + // Register "New Type" action + useEffect(() => { + const action: Action = { + id: 'types.new', + label: 'New Type', + icon: Plus, + group: 'types', + handler: handleCreateNew, + shortcut: 'mod+n' // Or something else + }; + + registerAction(action); + + return () => unregisterAction('types.new'); + }, [registerAction, unregisterAction]); + + // Update "New Type" state + useEffect(() => { + updateAction('types.new', { + disabled: isBuilding || loading, + visible: !isBuilding // Hide when building to keep toolbar clean? Or just disable? + // User requested "Gray out buttons", so maybe just disabled. + // But if we hide it, we make space for other actions. + // Let's decide to keep it visible but disabled if building, or hide it if we want to focus on editor actions. + // Logic in TypesEditor hides some actions. + // Let's hide 'types.new' when building to match 'cancel' appearing. + }); + }, [isBuilding, loading, updateAction]); + + + return ( +
+ {/* Header */} +
+
+

Types Editor

+

+ Manage and preview your type definitions +

+
+ +
+ + {/* Main Content */} +
+ {loading ? ( +
+ +
+ ) : ( +
+ {/* List Sidebar */} + + + {/* Main Content Area */} +
+ t.id === selectedTypeId) || null} + isBuilding={isBuilding} + onIsBuildingChange={setIsBuilding} + onSave={loadTypes} + onDeleteRaw={deleteType} + /> +
+
+ )} +
+
+ ); +}; + +export default TypesPlayground;