From f8d5c0ae2d72166353fc982bbeac57a94cad0732 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Mon, 15 Dec 2025 09:23:09 +0100 Subject: [PATCH 1/3] fix:geolinien outline --- src/hooks/usePek.ts | 170 +++++++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 65 deletions(-) diff --git a/src/hooks/usePek.ts b/src/hooks/usePek.ts index 6dc4297..8067e46 100644 --- a/src/hooks/usePek.ts +++ b/src/hooks/usePek.ts @@ -54,6 +54,27 @@ export interface ParcelSearchResponse { id: string; egrid?: string; number?: string; + perimeter?: { + closed: boolean; + punkte: Array<{ + koordinatensystem: string; + x: number; + y: number; + z: number | null; + }>; + }; + geometry_geojson?: { + type: string; + geometry: { + type: string; + coordinates: number[][][]; + }; + properties: { + id: string; + egrid?: string; + number?: string; + }; + }; }>; } @@ -285,92 +306,85 @@ export function usePek() { } // Adjacent parcels (if available) - // Fetch geometries for adjacent parcels + // Use geometries from the response (no need to fetch separately) if (data.adjacent_parcels && includeAdjacent && data.adjacent_parcels.length > 0) { - // Fetch geometries for each adjacent parcel - const adjacentPromises = data.adjacent_parcels.map(async (adjacent) => { - try { - // Search for the adjacent parcel by its ID or EGRID - const searchLocation = adjacent.egrid || adjacent.id || adjacent.number; - if (!searchLocation) { - if (import.meta.env.DEV) { - console.warn(`⚠️ Adjacent parcel ${adjacent.id} has no search location`); - } - return null; - } + const adjacentGeometries: ParcelGeometry[] = []; - if (import.meta.env.DEV) { - console.log(`🔍 Fetching geometry for adjacent parcel: ${searchLocation}`); - } - - const adjResponse = await api.get('/api/realestate/parcel/search', { - params: { - location: searchLocation, - include_adjacent: false // Don't fetch adjacent of adjacent - } + data.adjacent_parcels.forEach((adjacent) => { + if (import.meta.env.DEV) { + console.log(`🔍 Processing adjacent parcel ${adjacent.id}:`, { + hasGeometryGeoJson: !!adjacent.geometry_geojson, + hasPerimeter: !!adjacent.perimeter, + geometryGeoJson: adjacent.geometry_geojson, + perimeter: adjacent.perimeter }); + } - const adjData: ParcelSearchResponse = adjResponse.data; - let adjCoordinates: MapPoint[] = []; + let adjCoordinates: MapPoint[] = []; - // Extract coordinates from adjacent parcel - if (adjData.map_view?.geometry_geojson?.geometry?.coordinates) { - const coords = adjData.map_view.geometry_geojson.geometry.coordinates[0]; - if (Array.isArray(coords)) { - adjCoordinates = coords.map((coord: number[]) => ({ - x: coord[0], - y: coord[1] - })); - } - } else if (adjData.parcel.perimeter?.punkte) { - adjCoordinates = adjData.parcel.perimeter.punkte.map((p) => ({ - x: p.x, - y: p.y + // Extract coordinates from geometry_geojson if available + if (adjacent.geometry_geojson?.geometry?.coordinates) { + const coords = adjacent.geometry_geojson.geometry.coordinates[0]; + if (Array.isArray(coords) && coords.length > 0) { + adjCoordinates = coords.map((coord: number[]) => ({ + x: coord[0], + y: coord[1] })); + if (import.meta.env.DEV) { + console.log(`✅ Extracted ${adjCoordinates.length} coordinates from geometry_geojson for ${adjacent.id}`); + } } - + } + // Fallback to perimeter.punkte if available + else if (adjacent.perimeter?.punkte) { + adjCoordinates = adjacent.perimeter.punkte.map((p) => ({ + x: p.x, + y: p.y + })); if (import.meta.env.DEV) { - console.log(`✅ Fetched ${adjCoordinates.length} coordinates for adjacent parcel ${adjacent.id}`); + console.log(`✅ Extracted ${adjCoordinates.length} coordinates from perimeter for ${adjacent.id}`); } + } - return { + // Only add if we have valid coordinates + if (adjCoordinates.length >= 3) { + adjacentGeometries.push({ id: adjacent.id, egrid: adjacent.egrid, number: adjacent.number, coordinates: adjCoordinates, isSelected: false, isAdjacent: true - }; - } catch (err) { - // If fetching fails, log error but don't add parcel - if (import.meta.env.DEV) { - console.error(`❌ Failed to fetch geometry for adjacent parcel ${adjacent.id}:`, err); - } - return null; + }); + } else if (import.meta.env.DEV) { + console.warn(`⚠️ Adjacent parcel ${adjacent.id} has insufficient geometry data:`, { + coordCount: adjCoordinates.length, + hasGeometryGeoJson: !!adjacent.geometry_geojson, + hasPerimeter: !!adjacent.perimeter, + geometryGeoJsonStructure: adjacent.geometry_geojson ? { + hasGeometry: !!adjacent.geometry_geojson.geometry, + hasCoordinates: !!adjacent.geometry_geojson.geometry?.coordinates, + coordinatesLength: adjacent.geometry_geojson.geometry?.coordinates?.length, + firstCoordLength: adjacent.geometry_geojson.geometry?.coordinates?.[0]?.length + } : null + }); } }); - // Wait for all adjacent parcel geometries - const adjacentGeometries = await Promise.all(adjacentPromises); - const validAdjacentGeometries = adjacentGeometries.filter( - (g): g is ParcelGeometry => g !== null && g.coordinates.length >= 3 - ); - if (import.meta.env.DEV) { console.log(`📦 Adjacent parcels summary:`, { requested: data.adjacent_parcels.length, - fetched: adjacentGeometries.filter(g => g !== null).length, - valid: validAdjacentGeometries.length, - geometries: validAdjacentGeometries.map(g => ({ + valid: adjacentGeometries.length, + geometries: adjacentGeometries.map(g => ({ id: g.id, number: g.number, coordCount: g.coordinates.length })) }); } - + // Add adjacent parcels to geometries array - geometries.push(...validAdjacentGeometries); + geometries.push(...adjacentGeometries); } // Update parcel geometries with all parcels (main + adjacent) @@ -430,17 +444,43 @@ export function usePek() { ); /** - * Handle parcel click on map + * Handle parcel click on map - select the clicked parcel */ const handleParcelClick = useCallback(async (parcelId: string) => { - // Re-search for this specific parcel with adjacent parcels - if (selectedParcel) { - const locationString = selectedParcel.parcel.centroid - ? `${selectedParcel.parcel.centroid.x},${selectedParcel.parcel.centroid.y}` - : locationInput; - await searchParcel(locationString, true); + // Find the clicked parcel in the geometries + const clickedParcel = parcelGeometries.find(p => p.id === parcelId); + + if (clickedParcel && clickedParcel.coordinates.length > 0) { + // Use a point inside the parcel (first coordinate is always on the boundary, which is inside) + // For better accuracy, use a point slightly inside the boundary + const firstCoord = clickedParcel.coordinates[0]; + + // Calculate centroid as fallback, but prefer a point we know is inside + const sumX = clickedParcel.coordinates.reduce((sum, coord) => sum + coord.x, 0); + const sumY = clickedParcel.coordinates.reduce((sum, coord) => sum + coord.y, 0); + const centroidX = sumX / clickedParcel.coordinates.length; + const centroidY = sumY / clickedParcel.coordinates.length; + + // Use first coordinate (guaranteed to be on/in the parcel) for search + const locationString = `${firstCoord.x},${firstCoord.y}`; + await searchParcel(locationString, true); // Always include adjacent parcels + } else { + // Fallback: try to search by parcel ID/EGRID if available + if (selectedParcel?.adjacent_parcels) { + const adjacentParcel = selectedParcel.adjacent_parcels.find(p => p.id === parcelId); + if (adjacentParcel?.egrid) { + // Search by EGRID + await searchParcel(adjacentParcel.egrid, true); + } else if (adjacentParcel?.number) { + // Try searching by number (might need address context) + await searchParcel(adjacentParcel.number, true); + } else if (adjacentParcel?.id) { + // Last resort: try searching by ID + await searchParcel(adjacentParcel.id, true); + } + } } - }, [selectedParcel, locationInput, searchParcel]); + }, [parcelGeometries, selectedParcel, searchParcel]); /** * Process natural language command From bfbe3f88693d92a4419e0d8fcbdb532fceb944e3 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Mon, 22 Dec 2025 07:31:56 +0100 Subject: [PATCH 2/3] PEK updates --- src/api/attributesApi.ts | 132 ++++++++++++ .../pages/pek/PekLocationInput.module.css | 8 +- .../data/pages/pek/PekLocationInput.tsx | 39 ---- src/hooks/usePek.ts | 198 +++++++++++++++++- src/utils/attributeTypeMapper.ts | 181 ++++++++++++++++ 5 files changed, 510 insertions(+), 48 deletions(-) create mode 100644 src/api/attributesApi.ts create mode 100644 src/utils/attributeTypeMapper.ts diff --git a/src/api/attributesApi.ts b/src/api/attributesApi.ts new file mode 100644 index 0000000..28f9600 --- /dev/null +++ b/src/api/attributesApi.ts @@ -0,0 +1,132 @@ +import { ApiRequestOptions } from '../hooks/useApi'; + +// ============================================================================ +// TYPES & INTERFACES +// ============================================================================ + +export interface AttributeDefinition { + name: string; + label: string; + type: 'string' | 'number' | 'date' | 'boolean' | 'enum' | 'text' | 'email' | 'checkbox' | 'select' | 'multiselect' | 'textarea'; + sortable?: boolean; + filterable?: boolean; + searchable?: boolean; + width?: number; + minWidth?: number; + maxWidth?: number; + filterOptions?: string[]; + description?: string; + required?: boolean; + default?: any; + options?: Array<{ value: string | number; label: string | { [key: string]: string } }> | string; + validation?: any; + ui?: any; + readonly?: boolean; + editable?: boolean; + visible?: boolean; + order?: number; + placeholder?: string; +} + +// Type for the request function passed to API functions +export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; + +// ============================================================================ +// API REQUEST FUNCTIONS +// ============================================================================ + +/** + * Generic function to fetch attributes for any entity type + * Endpoint: GET /api/attributes/{entityType} + */ +export async function fetchAttributes( + request: ApiRequestFunction, + entityType: string +): Promise { + const data = await request({ + url: `/api/attributes/${entityType}`, + method: 'get' + }); + + // Extract attributes from response - check if response.data.attributes exists, otherwise check if response.data is an array + let attrs: AttributeDefinition[] = []; + if (data?.attributes && Array.isArray(data.attributes)) { + attrs = data.attributes; + } else if (Array.isArray(data)) { + attrs = data; + } else if (data && typeof data === 'object') { + // Try to find any array property in the response + const keys = Object.keys(data); + for (const key of keys) { + if (Array.isArray(data[key])) { + attrs = data[key]; + break; + } + } + } + + return attrs; +} + +/** + * Fetch connection attributes from backend + * Endpoint: GET /api/attributes/UserConnection + */ +export async function fetchConnectionAttributes(request: ApiRequestFunction): Promise { + return fetchAttributes(request, 'UserConnection'); +} + +/** + * Fetch file attributes from backend + * Endpoint: GET /api/attributes/FileItem + */ +export async function fetchFileAttributes(request: ApiRequestFunction): Promise { + const data = await request({ + url: '/api/attributes/FileItem', + method: 'get' + }); + + // Handle different response formats + if (Array.isArray(data)) { + return data; + } + if (data && typeof data === 'object' && 'attributes' in data && Array.isArray(data.attributes)) { + return data.attributes; + } + + // Try to find any array property in the response + if (data && typeof data === 'object') { + const keys = Object.keys(data); + for (const key of keys) { + if (Array.isArray((data as any)[key])) { + return (data as any)[key]; + } + } + } + + return []; +} + +/** + * Fetch prompt attributes from backend + * Endpoint: GET /api/attributes/Prompt + */ +export async function fetchPromptAttributes(request: ApiRequestFunction): Promise { + return fetchAttributes(request, 'Prompt'); +} + +/** + * Fetch user attributes from backend + * Endpoint: GET /api/attributes/User + */ +export async function fetchUserAttributes(request: ApiRequestFunction): Promise { + return fetchAttributes(request, 'User'); +} + +/** + * Fetch workflow attributes from backend + * Endpoint: GET /api/attributes/ChatWorkflow + */ +export async function fetchWorkflowAttributes(request: ApiRequestFunction): Promise { + return fetchAttributes(request, 'ChatWorkflow'); +} diff --git a/src/core/PageManager/data/pages/pek/PekLocationInput.module.css b/src/core/PageManager/data/pages/pek/PekLocationInput.module.css index afed4e3..463346b 100644 --- a/src/core/PageManager/data/pages/pek/PekLocationInput.module.css +++ b/src/core/PageManager/data/pages/pek/PekLocationInput.module.css @@ -6,7 +6,7 @@ .fieldsRow { display: flex; gap: 1rem; - align-items: flex-start; + align-items: flex-end; } .fieldWrapper { @@ -15,9 +15,8 @@ .buttonsWrapper { display: flex; - flex-direction: column; + flex-direction: row; gap: 0.5rem; - margin-top: 1.5rem; min-width: 150px; } @@ -35,9 +34,7 @@ } .buttonsWrapper { - flex-direction: row; width: 100%; - margin-top: 0.5rem; } .fieldWrapper { @@ -57,7 +54,6 @@ .buttonsWrapper { width: 100%; - margin-top: 0.5rem; } .searchButton, diff --git a/src/core/PageManager/data/pages/pek/PekLocationInput.tsx b/src/core/PageManager/data/pages/pek/PekLocationInput.tsx index 71b8480..6f73875 100644 --- a/src/core/PageManager/data/pages/pek/PekLocationInput.tsx +++ b/src/core/PageManager/data/pages/pek/PekLocationInput.tsx @@ -36,45 +36,6 @@ const PekLocationInput: React.FC = () => { return (
-
- { - if (e.key === 'Enter') { - e.preventDefault(); - const gemeindeInput = document.querySelector('input[name="gemeinde"]') as HTMLInputElement; - if (gemeindeInput) gemeindeInput.focus(); - } - }} - /> -
-
- { - if (e.key === 'Enter') { - e.preventDefault(); - const adresseInput = document.querySelector('input[name="adresse"]') as HTMLInputElement; - if (adresseInput) adresseInput.focus(); - } - }} - /> -
{ if (!userInput.trim()) { @@ -504,9 +505,34 @@ export function usePek() { setCommandResults((prev) => [...prev, userMessage]); try { - const response = await api.post('/api/realestate/command', { + // Build request body with user input and selected parcel + const requestBody: any = { userInput: userInput.trim() - }); + }; + + // Always include the currently selected parcel if available + if (selectedParcel) { + requestBody.selectedParcel = { + id: selectedParcel.parcel.id, + egrid: selectedParcel.parcel.egrid, + number: selectedParcel.parcel.number, + name: selectedParcel.parcel.name, + identnd: selectedParcel.parcel.identnd, + canton: selectedParcel.parcel.canton, + municipality_code: selectedParcel.parcel.municipality_code, + municipality_name: selectedParcel.parcel.municipality_name, + address: selectedParcel.parcel.address, + area_m2: selectedParcel.parcel.area_m2, + centroid: selectedParcel.parcel.centroid, + geoportal_url: selectedParcel.parcel.geoportal_url, + realestate_type: selectedParcel.parcel.realestate_type, + // Include geometry data if available + geometry_geojson: selectedParcel.map_view?.geometry_geojson, + perimeter: selectedParcel.parcel.perimeter + }; + } + + const response = await api.post('/api/realestate/command', requestBody); const data: CommandResponse = response.data; @@ -534,6 +560,172 @@ export function usePek() { }; setCommandResults((prev) => [...prev, assistantMessage]); + // If a project was created and there's a selected parcel, automatically add it + if (data.success && data.intent === 'CREATE' && data.entity === 'Projekt' && selectedParcel) { + try { + // Extract projekt from result + const projektResult = data.result?.result || data.result; + if (projektResult?.id) { + // Set as current projekt + setCurrentProjekt(projektResult); + + // Add the selected parcel to the newly created project via direct API call + const addParcelRequestBody: any = { + parcelId: selectedParcel.parcel.id, + parcelData: { + id: selectedParcel.parcel.id, + egrid: selectedParcel.parcel.egrid, + number: selectedParcel.parcel.number, + name: selectedParcel.parcel.name, + identnd: selectedParcel.parcel.identnd, + canton: selectedParcel.parcel.canton, + municipality_code: selectedParcel.parcel.municipality_code, + municipality_name: selectedParcel.parcel.municipality_name, + address: selectedParcel.parcel.address, + area_m2: selectedParcel.parcel.area_m2, + centroid: selectedParcel.parcel.centroid, + geoportal_url: selectedParcel.parcel.geoportal_url, + realestate_type: selectedParcel.parcel.realestate_type, + geometry_geojson: selectedParcel.map_view?.geometry_geojson, + perimeter: selectedParcel.parcel.perimeter + } + }; + + const addResponse = await api.post( + `/api/realestate/projekt/${projektResult.id}/add-parcel`, + addParcelRequestBody + ); + const addResult: AddParcelResponse = addResponse.data; + + // Update current projekt with the updated version that includes the parcel + setCurrentProjekt(addResult.projekt); + + // Update the assistant message to indicate parcel was added + const updateMessage = { + ...assistantMessage, + id: `assistant-update-${Date.now()}`, + message: `${responseMessage}\n\n✅ Parzelle wurde automatisch zum Projekt hinzugefügt.` + }; + setCommandResults((prev) => { + const updated = [...prev]; + const lastIndex = updated.length - 1; + if (updated[lastIndex]?.id === assistantMessage.id) { + updated[lastIndex] = updateMessage; + } + return updated; + }); + } + } catch (addError: any) { + // Log error but don't fail the command + console.error('Failed to automatically add parcel to project:', addError); + const errorMessage = addError.response?.data?.detail || addError.message || 'Unbekannter Fehler'; + const errorUpdate = { + id: `assistant-error-${Date.now()}`, + role: 'assistant', + message: `⚠️ Projekt wurde erstellt, aber Parzelle konnte nicht automatisch hinzugefügt werden: ${errorMessage}`, + timestamp: Date.now() + }; + setCommandResults((prev) => [...prev, errorUpdate]); + } + } + + // If a parcel was created and there's a selected parcel, automatically populate it with the selected parcel data + if (data.success && data.intent === 'CREATE' && data.entity === 'Parzelle' && selectedParcel) { + try { + // Extract parzelle from result + const parzelleResult = data.result?.result || data.result; + if (parzelleResult?.id) { + // Update the newly created parcel with data from the selected parcel + const updateParcelRequestBody: any = { + // Map selected parcel data to parzelle fields + egrid: selectedParcel.parcel.egrid, + number: selectedParcel.parcel.number, + name: selectedParcel.parcel.name, + identnd: selectedParcel.parcel.identnd, + canton: selectedParcel.parcel.canton, + municipality_code: selectedParcel.parcel.municipality_code, + municipality_name: selectedParcel.parcel.municipality_name, + address: selectedParcel.parcel.address, + strasseNr: selectedParcel.parcel.address, + area_m2: selectedParcel.parcel.area_m2, + centroid: selectedParcel.parcel.centroid, + geoportal_url: selectedParcel.parcel.geoportal_url, + realestate_type: selectedParcel.parcel.realestate_type, + // Include geometry data + geometry_geojson: selectedParcel.map_view?.geometry_geojson, + perimeter: selectedParcel.parcel.perimeter + }; + + // Try to update the parcel via PUT request + try { + const updateResponse = await api.put( + `/api/realestate/parzelle/${parzelleResult.id}`, + updateParcelRequestBody + ); + + // Update the assistant message to indicate parcel was populated + const updateMessage = { + ...assistantMessage, + id: `assistant-update-${Date.now()}`, + message: `${responseMessage}\n\n✅ Parzelle wurde automatisch mit Daten der Kartenauswahl befüllt.` + }; + setCommandResults((prev) => { + const updated = [...prev]; + const lastIndex = updated.length - 1; + if (updated[lastIndex]?.id === assistantMessage.id) { + updated[lastIndex] = updateMessage; + } + return updated; + }); + } catch (putError: any) { + // If PUT doesn't work, try PATCH + try { + await api.patch( + `/api/realestate/parzelle/${parzelleResult.id}`, + updateParcelRequestBody + ); + + const updateMessage = { + ...assistantMessage, + id: `assistant-update-${Date.now()}`, + message: `${responseMessage}\n\n✅ Parzelle wurde automatisch mit Daten der Kartenauswahl befüllt.` + }; + setCommandResults((prev) => { + const updated = [...prev]; + const lastIndex = updated.length - 1; + if (updated[lastIndex]?.id === assistantMessage.id) { + updated[lastIndex] = updateMessage; + } + return updated; + }); + } catch (patchError: any) { + // If both PUT and PATCH fail, log but don't fail the command + console.error('Failed to update parcel with selected parcel data:', patchError); + const errorMessage = patchError.response?.data?.detail || patchError.message || 'Unbekannter Fehler'; + const errorUpdate = { + id: `assistant-error-${Date.now()}`, + role: 'assistant', + message: `⚠️ Parzelle wurde erstellt, aber konnte nicht automatisch mit Kartenauswahl-Daten befüllt werden: ${errorMessage}`, + timestamp: Date.now() + }; + setCommandResults((prev) => [...prev, errorUpdate]); + } + } + } + } catch (updateError: any) { + // Log error but don't fail the command + console.error('Failed to automatically populate parcel with selected parcel data:', updateError); + const errorMessage = updateError.response?.data?.detail || updateError.message || 'Unbekannter Fehler'; + const errorUpdate = { + id: `assistant-error-${Date.now()}`, + role: 'assistant', + message: `⚠️ Parzelle wurde erstellt, aber konnte nicht automatisch mit Kartenauswahl-Daten befüllt werden: ${errorMessage}`, + timestamp: Date.now() + }; + setCommandResults((prev) => [...prev, errorUpdate]); + } + } + // Clear input on success setCommandInput(''); @@ -555,7 +747,7 @@ export function usePek() { } finally { setIsProcessingCommand(false); } - }, []); + }, [selectedParcel]); /** * Create a new project diff --git a/src/utils/attributeTypeMapper.ts b/src/utils/attributeTypeMapper.ts new file mode 100644 index 0000000..b0c02b7 --- /dev/null +++ b/src/utils/attributeTypeMapper.ts @@ -0,0 +1,181 @@ +/** + * Utility functions for mapping attribute types to HTML input types and component types + */ + +export type AttributeType = + | 'text' + | 'textarea' + | 'select' + | 'multiselect' + | 'integer' + | 'float' + | 'number' + | 'timestamp' + | 'date' + | 'time' + | 'checkbox' + | 'boolean' + | 'email' + | 'url' + | 'password' + | 'file' + | 'string' + | 'enum' + | 'readonly'; + +export type InputComponentType = + | 'text' + | 'textarea' + | 'select' + | 'multiselect' + | 'checkbox' + | 'file' + | 'email' + | 'url' + | 'password' + | 'date' + | 'time' + | 'datetime-local' + | 'number'; + +/** + * Maps attribute type to HTML input type + * + * @param attributeType - The attribute type from the backend + * @returns The corresponding HTML input type + * + * Mapping rules: + * - text → text (single line) + * - textarea → textarea (multi-line) + * - select → select (dropdown with options) + * - multiselect → multiselect (multiple selection) + * - integer → number (integer only) + * - float or number → number (decimal allowed) + * - timestamp → datetime-local (date/time picker) + * - date → date (date picker, date only) + * - time → time (time picker, time only) + * - checkbox or boolean → checkbox (boolean) + * - email → email (with email validation) + * - url → url (with URL validation) + * - password → password (masked) + * - file → file (file upload) + */ +export function attributeTypeToInputType(attributeType: AttributeType): InputComponentType { + switch (attributeType) { + case 'text': + case 'string': + return 'text'; + + case 'textarea': + return 'textarea'; + + case 'select': + case 'enum': + return 'select'; + + case 'multiselect': + return 'multiselect'; + + case 'integer': + case 'number': + case 'float': + return 'number'; + + case 'timestamp': + return 'datetime-local'; + + case 'date': + return 'date'; + + case 'time': + return 'time'; + + case 'checkbox': + case 'boolean': + return 'checkbox'; + + case 'email': + return 'email'; + + case 'url': + return 'url'; + + case 'password': + return 'password'; + + case 'file': + return 'file'; + + case 'readonly': + return 'text'; // Default to text for readonly, but should be rendered as readonly + + default: + // Default fallback to text input + return 'text'; + } +} + +/** + * Determines if an attribute type should render as a textarea + */ +export function isTextareaType(attributeType: AttributeType): boolean { + return attributeType === 'textarea'; +} + +/** + * Determines if an attribute type should render as a select dropdown + */ +export function isSelectType(attributeType: AttributeType): boolean { + return attributeType === 'select' || attributeType === 'enum'; +} + +/** + * Determines if an attribute type should render as a multiselect + */ +export function isMultiselectType(attributeType: AttributeType): boolean { + return attributeType === 'multiselect'; +} + +/** + * Determines if an attribute type should render as a checkbox + */ +export function isCheckboxType(attributeType: AttributeType): boolean { + return attributeType === 'checkbox' || attributeType === 'boolean'; +} + +/** + * Determines if an attribute type should render as a file input + */ +export function isFileType(attributeType: AttributeType): boolean { + return attributeType === 'file'; +} + +/** + * Determines if an attribute type should render as a number input + */ +export function isNumberType(attributeType: AttributeType): boolean { + return attributeType === 'integer' || attributeType === 'number' || attributeType === 'float'; +} + +/** + * Determines if an attribute type should render as a date/time input + */ +export function isDateTimeType(attributeType: AttributeType): boolean { + return attributeType === 'timestamp' || attributeType === 'date' || attributeType === 'time'; +} + +/** + * Gets the default value for an attribute type + */ +export function getDefaultValueForType(attributeType: AttributeType): any { + if (isCheckboxType(attributeType)) { + return false; + } + if (isMultiselectType(attributeType)) { + return []; + } + if (isNumberType(attributeType)) { + return 0; + } + return ''; +} From cf76e89d407e9b71a93ef723da17617f661fd052 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Tue, 30 Dec 2025 09:10:08 +0100 Subject: [PATCH 3/3] pek update --- src/components/FormGenerator/index.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/components/FormGenerator/index.ts b/src/components/FormGenerator/index.ts index 48e90f6..b9ceecb 100644 --- a/src/components/FormGenerator/index.ts +++ b/src/components/FormGenerator/index.ts @@ -1,18 +1,5 @@ -// Legacy export - FormGenerator is now FormGeneratorTable (for backward compatibility) -export { FormGeneratorTable as FormGenerator } from './FormGeneratorTable'; -export type { ColumnConfig, FormGeneratorTableProps as FormGeneratorProps } from './FormGeneratorTable'; - -export { FormGeneratorTable } from './FormGeneratorTable'; -export type { ColumnConfig, FormGeneratorTableProps } from './FormGeneratorTable'; - -export { FormGeneratorList } from './FormGeneratorList'; -export type { FieldConfig, FormGeneratorListProps } from './FormGeneratorList'; - -export { FormGeneratorControls } from './FormGeneratorControls'; -export type { FilterableField, FormGeneratorControlsProps } from './FormGeneratorControls'; - -export { FormGeneratorForm } from './FormGeneratorForm'; -export type { FormGeneratorFormProps, AttributeDefinition, AttributeOption } from './FormGeneratorForm'; +export { default as FormGenerator } from './FormGenerator'; +export type { ColumnConfig, FormGeneratorProps } from './FormGenerator'; // Re-export action button components and types export * from './ActionButtons'; \ No newline at end of file