ui-nyla/src/hooks/usePek.ts
2025-12-22 07:31:56 +01:00

907 lines
31 KiB
TypeScript

import { useState, useCallback } 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;
};
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;
};
};
}>;
}
// 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<string>('');
const [gemeinde, setGemeinde] = useState<string>('');
const [adresse, setAdresse] = useState<string>('');
const [isGettingLocation, setIsGettingLocation] = useState(false);
const [locationError, setLocationError] = useState<string | null>(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 [selectedParcel, setSelectedParcel] = useState<ParcelSearchResponse | null>(null);
const [isSearchingParcel, setIsSearchingParcel] = useState(false);
const [parcelSearchError, setParcelSearchError] = useState<string | null>(null);
// Map view state
const [mapCenter, setMapCenter] = useState<MapPoint | null>(null);
const [mapZoomBounds, setMapZoomBounds] = useState<{
min_x: number;
min_y: number;
max_x: number;
max_y: number;
} | null>(null);
const [parcelGeometries, setParcelGeometries] = useState<ParcelGeometry[]>([]);
// Command processing state
const [commandInput, setCommandInput] = useState<string>('');
const [isProcessingCommand, setIsProcessingCommand] = useState(false);
const [commandResults, setCommandResults] = useState<any[]>([]);
const [commandError, setCommandError] = useState<string | null>(null);
// Project state
const [currentProjekt, setCurrentProjekt] = useState<Projekt | null>(null);
const [isCreatingProjekt, setIsCreatingProjekt] = useState(false);
const [isAddingParcel, setIsAddingParcel] = useState(false);
const [projektError, setProjektError] = useState<string | null>(null);
// Panel state
const [isPanelOpen, setIsPanelOpen] = useState(false);
/**
* 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<void>((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
});
}
// Update selected parcel
setSelectedParcel(data);
// Open panel when parcel is found
setIsPanelOpen(true);
// Update map center and zoom bounds
if (data.map_view) {
setMapCenter(data.map_view.center);
setMapZoomBounds(data.map_view.zoom_bounds);
// Convert parcel data to geometries
const geometries: ParcelGeometry[] = [];
// Main parcel - use geometry_geojson if available, otherwise use perimeter.punkte
let mainParcelCoordinates: MapPoint[] = [];
if (data.map_view.geometry_geojson?.geometry?.coordinates) {
// GeoJSON format: coordinates is an array of coordinate arrays
// For Polygon: coordinates[0] is the outer ring
const coords = data.map_view.geometry_geojson.geometry.coordinates[0];
if (Array.isArray(coords)) {
mainParcelCoordinates = coords.map((coord: number[]) => ({
x: coord[0], // Longitude/X in LV95
y: coord[1] // Latitude/Y in LV95
}));
}
} else if (data.parcel.perimeter?.punkte) {
// Fallback to perimeter.punkte
mainParcelCoordinates = data.parcel.perimeter.punkte.map((p) => ({
x: p.x,
y: p.y
}));
}
if (mainParcelCoordinates.length > 0) {
geometries.push({
id: data.parcel.id,
egrid: data.parcel.egrid,
number: data.parcel.number,
coordinates: mainParcelCoordinates,
isSelected: true,
isAdjacent: false
});
}
// Adjacent parcels (if available)
// Use geometries from the response (no need to fetch separately)
if (data.adjacent_parcels && includeAdjacent && data.adjacent_parcels.length > 0) {
const adjacentGeometries: ParcelGeometry[] = [];
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
});
}
let adjCoordinates: MapPoint[] = [];
// 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(`✅ Extracted ${adjCoordinates.length} coordinates from perimeter for ${adjacent.id}`);
}
}
// 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
});
} 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
});
}
});
if (import.meta.env.DEV) {
console.log(`📦 Adjacent parcels summary:`, {
requested: data.adjacent_parcels.length,
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(...adjacentGeometries);
}
// Update parcel geometries with all parcels (main + adjacent)
setParcelGeometries(geometries);
if (import.meta.env.DEV) {
console.log(`🗺️ Total geometries to display: ${geometries.length}`, {
main: geometries.filter(g => g.isSelected).length,
adjacent: geometries.filter(g => g.isAdjacent).length
});
}
} 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
}));
setParcelGeometries([{
id: data.parcel.id,
egrid: data.parcel.egrid,
number: data.parcel.number,
coordinates,
isSelected: true,
isAdjacent: false
}]);
// Set center from centroid if available
if (data.parcel.centroid) {
setMapCenter(data.parcel.centroid);
}
}
}
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]
);
/**
* Handle parcel click on map - select the clicked parcel
*/
const handleParcelClick = useCallback(async (parcelId: string) => {
// 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);
}
}
}
}, [parcelGeometries, selectedParcel, 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 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;
// 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'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('');
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);
}
}, [selectedParcel]);
/**
* 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<string, any>;
}
) => {
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
selectedParcel,
searchParcel,
isSearchingParcel,
parcelSearchError,
// 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
};
}