import { useState, useCallback, useEffect } from 'react'; import api from '../api'; import type { MapPoint, ParcelGeometry } from '../components/UiComponents/MapView'; import { wgs84ToLV95 } from '../components/UiComponents/MapView/LV95Converter'; // Parcel search response interfaces export interface ParcelSearchResponse { parcel: { id: string; egrid?: string; number?: string; name?: string; identnd?: string; canton?: string; municipality_code?: number; municipality_name?: string; address?: string; perimeter?: { closed: boolean; punkte: Array<{ koordinatensystem: string; x: number; y: number; z: number | null; }>; }; area_m2?: number; centroid?: { x: number; y: number }; geoportal_url?: string; realestate_type?: string | null; bauzone?: string | null; zone?: Array | null; }; map_view: { center: { x: number; y: number }; zoom_bounds: { min_x: number; min_y: number; max_x: number; max_y: number; }; geometry_geojson: { type: string; geometry: { type: string; coordinates: number[][][]; }; properties: { id: string; egrid?: string; number?: string; }; }; }; adjacent_parcels?: Array<{ 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; }; }; }>; gemeinde?: { id: string; label: string; plz: string; }; documents?: Array<{ id: string; label: string; dokumentTyp: string; dokumentReferenz: string; quelle: string; mimeType: string; }>; } // Command response interface export interface CommandResponse { success: boolean; intent?: string; entity?: string; result?: any; error?: string; } // Project interfaces export interface Projekt { id: string; mandateId?: string; label: string; statusProzess?: string; perimeter?: any; baulinie?: any; parzellen?: any[]; dokumente?: any[]; kontextInformationen?: any[]; } export interface CreateProjektResponse { projekt: Projekt; parzellen?: any[]; } export interface AddParcelResponse { projekt: Projekt; parzelle: any; } // Main PEK hook export function usePek() { // Location input state - separate fields const [kanton, setKanton] = useState(''); const [gemeinde, setGemeinde] = useState(''); const [adresse, setAdresse] = useState(''); const [isGettingLocation, setIsGettingLocation] = useState(false); const [locationError, setLocationError] = useState(null); // Legacy locationInput for backward compatibility (combines fields) const locationInput = [kanton, gemeinde, adresse].filter(Boolean).join(', '); const setLocationInput = (value: string) => { // Parse combined input if needed (for map clicks, etc.) const parts = value.split(',').map(p => p.trim()); if (parts.length >= 3) { setKanton(parts[0]); setGemeinde(parts[1]); setAdresse(parts.slice(2).join(', ')); } else { setAdresse(value); } }; // Parcel search state const [selectedParcels, setSelectedParcels] = useState([]); const [isSearchingParcel, setIsSearchingParcel] = useState(false); const [parcelSearchError, setParcelSearchError] = useState(null); // Map view state const [mapCenter, setMapCenter] = useState(null); const [mapZoomBounds, setMapZoomBounds] = useState<{ min_x: number; min_y: number; max_x: number; max_y: number; } | null>(null); const [parcelGeometries, setParcelGeometries] = useState([]); // Command processing state const [commandInput, setCommandInput] = useState(''); const [isProcessingCommand, setIsProcessingCommand] = useState(false); const [commandResults, setCommandResults] = useState([]); const [commandError, setCommandError] = useState(null); // Project state const [currentProjekt, setCurrentProjekt] = useState(null); const [isCreatingProjekt, setIsCreatingProjekt] = useState(false); const [isAddingParcel, setIsAddingParcel] = useState(false); const [projektError, setProjektError] = useState(null); // Panel state const [isPanelOpen, setIsPanelOpen] = useState(false); // Update parcel geometries when selected parcels change // Ensure all selected parcels are marked as selected and not as adjacent useEffect(() => { const selectedParcelIds = new Set(selectedParcels.map(p => p.parcel.id)); setParcelGeometries(prev => prev.map(geo => { const isSelected = selectedParcelIds.has(geo.id); // If parcel is selected, it should not be marked as adjacent const isAdjacent = isSelected ? false : geo.isAdjacent; return { ...geo, isSelected, isAdjacent }; })); }, [selectedParcels]); /** * Get current geolocation and directly search for parcel * Does not fill input fields, directly makes the request */ const useCurrentLocation = useCallback(async () => { setIsGettingLocation(true); setLocationError(null); try { if (!navigator.geolocation) { throw new Error('Geolocation wird von Ihrem Browser nicht unterstützt'); } return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( async (position) => { try { // Convert WGS84 to LV95 using the converter function const lat = position.coords.latitude; const lon = position.coords.longitude; const lv95 = wgs84ToLV95(lat, lon); const locationString = `${Math.round(lv95.x)},${Math.round(lv95.y)}`; // Directly search for parcel without updating input fields await searchParcel(locationString, true); resolve(); } catch (err: any) { setLocationError(err.message || 'Fehler beim Konvertieren der Koordinaten'); reject(err); } }, (error) => { let errorMessage = 'Fehler beim Abrufen der Position'; switch (error.code) { case error.PERMISSION_DENIED: errorMessage = 'Zugriff auf Standort wurde verweigert'; break; case error.POSITION_UNAVAILABLE: errorMessage = 'Standortinformationen nicht verfügbar'; break; case error.TIMEOUT: errorMessage = 'Zeitüberschreitung beim Abrufen der Position'; break; } setLocationError(errorMessage); reject(new Error(errorMessage)); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); }); } catch (err: any) { setLocationError(err.message || 'Fehler beim Abrufen der aktuellen Position'); throw err; } finally { setIsGettingLocation(false); } }, []); /** * Search for parcel by location (address or coordinates) * Always includes adjacent parcels by default */ const searchParcel = useCallback(async (location: string, includeAdjacent: boolean = true) => { if (!location.trim()) { setParcelSearchError('Bitte geben Sie einen Standort ein'); return; } setIsSearchingParcel(true); setParcelSearchError(null); try { const response = await api.get('/api/realestate/parcel/search', { params: { location: location.trim(), include_adjacent: includeAdjacent } }); const data: ParcelSearchResponse = response.data; // Debug logging if (import.meta.env.DEV) { console.log('📦 Parcel search response:', { hasMapView: !!data.map_view, hasGeometry: !!data.map_view?.geometry_geojson, hasPerimeter: !!data.parcel.perimeter, adjacentCount: data.adjacent_parcels?.length || 0 }); } // Add parcel to selected parcels array if not already selected // Update geometries within the callback to have access to updated selectedParcels setSelectedParcels(prev => { const exists = prev.some(p => p.parcel.id === data.parcel.id); if (exists) { return prev; // Already selected, don't add again } const updatedSelectedParcels = [...prev, data]; const selectedParcelIds = new Set(updatedSelectedParcels.map(p => p.parcel.id)); // Update geometries setParcelGeometries(currentGeometries => { const geometryMap = new Map(); // Keep existing geometries currentGeometries.forEach(geo => { geometryMap.set(geo.id, geo); }); // Update map center and zoom bounds if (data.map_view) { setMapCenter(data.map_view.center); setMapZoomBounds(data.map_view.zoom_bounds); // Main parcel - use geometry_geojson if available, otherwise use perimeter.punkte let mainParcelCoordinates: MapPoint[] = []; if (data.map_view.geometry_geojson?.geometry?.coordinates) { const coords = data.map_view.geometry_geojson.geometry.coordinates[0]; if (Array.isArray(coords)) { mainParcelCoordinates = coords.map((coord: number[]) => ({ x: coord[0], y: coord[1] })); } } else if (data.parcel.perimeter?.punkte) { mainParcelCoordinates = data.parcel.perimeter.punkte.map((p) => ({ x: p.x, y: p.y })); } if (mainParcelCoordinates.length > 0) { geometryMap.set(data.parcel.id, { id: data.parcel.id, egrid: data.parcel.egrid, number: data.parcel.number, coordinates: mainParcelCoordinates, isSelected: true, isAdjacent: false }); } // Add adjacent parcels, but skip if already selected if (data.adjacent_parcels && includeAdjacent && data.adjacent_parcels.length > 0) { data.adjacent_parcels.forEach((adjacent) => { // Skip if this adjacent parcel is already selected if (selectedParcelIds.has(adjacent.id)) { // If it exists, mark as selected, not adjacent const existingGeo = geometryMap.get(adjacent.id); if (existingGeo) { geometryMap.set(adjacent.id, { ...existingGeo, isSelected: true, isAdjacent: false }); } if (import.meta.env.DEV) { console.log(`⏭️ Skipping adjacent parcel ${adjacent.id} - already selected`); } return; } // Only add if not already in map if (!geometryMap.has(adjacent.id)) { let adjCoordinates: MapPoint[] = []; 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] })); } } else if (adjacent.perimeter?.punkte) { adjCoordinates = adjacent.perimeter.punkte.map((p) => ({ x: p.x, y: p.y })); } if (adjCoordinates.length >= 3) { geometryMap.set(adjacent.id, { id: adjacent.id, egrid: adjacent.egrid, number: adjacent.number, coordinates: adjCoordinates, isSelected: false, isAdjacent: true }); } } }); } } else { // If no map_view, still try to use parcel data if (data.parcel.perimeter?.punkte) { const coordinates = data.parcel.perimeter.punkte.map((p) => ({ x: p.x, y: p.y })); geometryMap.set(data.parcel.id, { id: data.parcel.id, egrid: data.parcel.egrid, number: data.parcel.number, coordinates, isSelected: true, isAdjacent: false }); if (data.parcel.centroid) { setMapCenter(data.parcel.centroid); } } } // Update all geometries: mark selected ones and unmark adjacent for selected ones const updatedGeometries = Array.from(geometryMap.values()).map(geo => { const isSelected = selectedParcelIds.has(geo.id); return { ...geo, isSelected, isAdjacent: isSelected ? false : geo.isAdjacent }; }); if (import.meta.env.DEV) { console.log(`🗺️ Total geometries to display: ${updatedGeometries.length}`, { selected: updatedGeometries.filter(g => g.isSelected).length, adjacent: updatedGeometries.filter(g => g.isAdjacent).length }); } return updatedGeometries; }); return updatedSelectedParcels; }); // Open panel when parcel is found setIsPanelOpen(true); return { success: true, data }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Fehler beim Suchen der Parzelle'; setParcelSearchError(errorMessage); return { success: false, error: errorMessage }; } finally { setIsSearchingParcel(false); } }, []); /** * Handle map click - search for parcel at clicked coordinates */ const handleMapClick = useCallback( async (point: MapPoint) => { const locationString = `${point.x},${point.y}`; setLocationInput(locationString); await searchParcel(locationString, true); // Always include adjacent parcels }, [searchParcel] ); /** * Check if a parcel is selected */ const isParcelSelected = useCallback((parcelId: string): boolean => { return selectedParcels.some(p => p.parcel.id === parcelId); }, [selectedParcels]); /** * Remove a parcel from selection */ const removeParcel = useCallback((parcelId: string) => { setSelectedParcels(prev => prev.filter(p => p.parcel.id !== parcelId)); // Update geometries to reflect deselection setParcelGeometries(prev => prev.map(geo => geo.id === parcelId ? { ...geo, isSelected: false } : geo )); }, []); /** * Clear all selected parcels */ const clearSelectedParcels = useCallback(() => { setSelectedParcels([]); // Update geometries to reflect deselection setParcelGeometries(prev => prev.map(geo => ({ ...geo, isSelected: false }))); }, []); /** * Handle parcel click on map - toggle parcel selection */ const handleParcelClick = useCallback(async (parcelId: string) => { // Check if parcel is already selected const isSelected = isParcelSelected(parcelId); if (isSelected) { // Remove from selection removeParcel(parcelId); } else { // 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) const firstCoord = clickedParcel.coordinates[0]; // 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 // Check all selected parcels for adjacent parcels for (const selectedParcel of selectedParcels) { 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); break; } else if (adjacentParcel?.number) { // Try searching by number (might need address context) await searchParcel(adjacentParcel.number, true); break; } else if (adjacentParcel?.id) { // Last resort: try searching by ID await searchParcel(adjacentParcel.id, true); break; } } } } } }, [parcelGeometries, selectedParcels, isParcelSelected, removeParcel, searchParcel]); /** * Process natural language command * Always includes the currently selected parcel if available */ const processCommand = useCallback(async (userInput: string) => { if (!userInput.trim()) { setCommandError('Bitte geben Sie einen Befehl ein'); return; } setIsProcessingCommand(true); setCommandError(null); // Add user message const userMessage = { id: `user-${Date.now()}`, role: 'user', message: userInput.trim(), timestamp: Date.now() }; setCommandResults((prev) => [...prev, userMessage]); try { // Build request body with user input and selected parcel const requestBody: any = { userInput: userInput.trim() }; // Always include the currently selected parcels if available if (selectedParcels.length > 0) { // Use first selected parcel for backward compatibility const firstParcel = selectedParcels[0]; requestBody.selectedParcel = { id: firstParcel.parcel.id, egrid: firstParcel.parcel.egrid, number: firstParcel.parcel.number, name: firstParcel.parcel.name, identnd: firstParcel.parcel.identnd, canton: firstParcel.parcel.canton, municipality_code: firstParcel.parcel.municipality_code, municipality_name: firstParcel.parcel.municipality_name, address: firstParcel.parcel.address, area_m2: firstParcel.parcel.area_m2, centroid: firstParcel.parcel.centroid, geoportal_url: firstParcel.parcel.geoportal_url, realestate_type: firstParcel.parcel.realestate_type, bauzone: firstParcel.parcel.bauzone, zone: firstParcel.parcel.zone, // Include geometry data if available geometry_geojson: firstParcel.map_view?.geometry_geojson, perimeter: firstParcel.parcel.perimeter }; // Also include all selected parcels as array requestBody.selectedParcels = selectedParcels.map(p => ({ id: p.parcel.id, egrid: p.parcel.egrid, number: p.parcel.number, name: p.parcel.name, identnd: p.parcel.identnd, canton: p.parcel.canton, municipality_code: p.parcel.municipality_code, municipality_name: p.parcel.municipality_name, address: p.parcel.address, area_m2: p.parcel.area_m2, centroid: p.parcel.centroid, geoportal_url: p.parcel.geoportal_url, realestate_type: p.parcel.realestate_type, bauzone: p.parcel.bauzone, zone: p.parcel.zone, geometry_geojson: p.map_view?.geometry_geojson, perimeter: p.parcel.perimeter })); } const response = await api.post('/api/realestate/command', requestBody); const data: CommandResponse = response.data; // Format response as assistant message let responseMessage = ''; if (data.success) { if (data.result) { if (typeof data.result === 'object') { responseMessage = `**Intent:** ${data.intent || 'Unknown'}\n**Entity:** ${data.entity || 'N/A'}\n\n**Result:**\n\`\`\`json\n${JSON.stringify(data.result, null, 2)}\n\`\`\``; } else { responseMessage = `**Intent:** ${data.intent || 'Unknown'}\n**Entity:** ${data.entity || 'N/A'}\n\n**Result:** ${data.result}`; } } else { responseMessage = `Command executed successfully.\n**Intent:** ${data.intent || 'Unknown'}\n**Entity:** ${data.entity || 'N/A'}`; } } else { responseMessage = `Error: ${data.error || 'Unknown error'}`; } const assistantMessage = { id: `assistant-${Date.now()}`, role: 'assistant', message: responseMessage, timestamp: Date.now() }; setCommandResults((prev) => [...prev, assistantMessage]); // If a project was created and there are selected parcels, automatically add them if (data.success && data.intent === 'CREATE' && data.entity === 'Projekt' && selectedParcels.length > 0) { try { // Extract projekt from result const projektResult = data.result?.result || data.result; if (projektResult?.id) { // Set as current projekt setCurrentProjekt(projektResult); // Add all selected parcels to the newly created project via direct API call let addedCount = 0; for (const selectedParcel of selectedParcels) { try { 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, bauzone: selectedParcel.parcel.bauzone, zone: selectedParcel.parcel.zone, 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); addedCount++; } catch (addError: any) { console.error(`Failed to add parcel ${selectedParcel.parcel.id} to project:`, addError); } } // Update the assistant message to indicate parcels were added const parcelText = addedCount === 1 ? 'Parzelle' : 'Parzellen'; const updateMessage = { ...assistantMessage, id: `assistant-update-${Date.now()}`, message: `${responseMessage}\n\n✅ ${addedCount} ${parcelText} ${addedCount === 1 ? 'wurde' : 'wurden'} 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 are selected parcels, automatically populate it with the first selected parcel data if (data.success && data.intent === 'CREATE' && data.entity === 'Parzelle' && selectedParcels.length > 0) { const selectedParcel = selectedParcels[0]; // Use first selected parcel 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, bauzone: selectedParcel.parcel.bauzone, zone: selectedParcel.parcel.zone, // Include geometry data geometry_geojson: selectedParcel.map_view?.geometry_geojson, perimeter: selectedParcel.parcel.perimeter }; // Try to update the parcel via PUT request try { 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(''); return { success: true, data }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to send command'; setCommandError(errorMessage); // Add error message const errorMsg = { id: `error-${Date.now()}`, role: 'assistant', message: `**Error:** ${errorMessage}`, timestamp: Date.now() }; setCommandResults((prev) => [...prev, errorMsg]); return { success: false, error: errorMessage }; } finally { setIsProcessingCommand(false); } }, [selectedParcels]); /** * Create a new project */ const createProjekt = useCallback( async (data: { label: string; statusProzess?: string; location?: string; parcelIds?: string[]; }) => { setIsCreatingProjekt(true); setProjektError(null); try { const requestBody: any = { label: data.label }; if (data.statusProzess) { requestBody.statusProzess = data.statusProzess; } if (data.location) { requestBody.location = data.location; } if (data.parcelIds && data.parcelIds.length > 0) { requestBody.parcelIds = data.parcelIds; } const response = await api.post('/api/realestate/projekt/create', requestBody); const result: CreateProjektResponse = response.data; setCurrentProjekt(result.projekt); return { success: true, data: result }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Fehler beim Erstellen des Projekts'; setProjektError(errorMessage); return { success: false, error: errorMessage }; } finally { setIsCreatingProjekt(false); } }, [] ); /** * Add a parcel to an existing project */ const addParcelToProjekt = useCallback( async ( projektId: string, data: { parcelId?: string; location?: string; parcelData?: Record; } ) => { if (!currentProjekt || currentProjekt.id !== projektId) { setProjektError('Projekt nicht gefunden'); return { success: false, error: 'Projekt nicht gefunden' }; } setIsAddingParcel(true); setProjektError(null); try { const requestBody: any = {}; if (data.parcelId) { requestBody.parcelId = data.parcelId; } else if (data.location) { requestBody.location = data.location; } else if (data.parcelData) { requestBody.parcelData = data.parcelData; } else { throw new Error('Bitte geben Sie parcelId, location oder parcelData an'); } const response = await api.post( `/api/realestate/projekt/${projektId}/add-parcel`, requestBody ); const result: AddParcelResponse = response.data; setCurrentProjekt(result.projekt); return { success: true, data: result }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Fehler beim Hinzufügen der Parzelle'; setProjektError(errorMessage); return { success: false, error: errorMessage }; } finally { setIsAddingParcel(false); } }, [currentProjekt] ); // Build location string from separate fields const buildLocationString = useCallback(() => { const parts = [kanton, gemeinde, adresse].filter(Boolean); return parts.join(', '); }, [kanton, gemeinde, adresse]); return { // Location input - separate fields kanton, setKanton, gemeinde, setGemeinde, adresse, setAdresse, buildLocationString, // Legacy locationInput for backward compatibility locationInput, setLocationInput, useCurrentLocation, isGettingLocation, locationError, // Parcel search selectedParcels, searchParcel, isSearchingParcel, parcelSearchError, removeParcel, clearSelectedParcels, isParcelSelected, // Map view mapCenter, mapZoomBounds, parcelGeometries, handleMapClick, handleParcelClick, // Command processing commandInput, setCommandInput, processCommand, isProcessingCommand, commandResults, commandError, // Project management currentProjekt, createProjekt, isCreatingProjekt, addParcelToProjekt, isAddingParcel, projektError, // Panel state isPanelOpen, setIsPanelOpen }; }