frontend_nyla/src/api/realEstateApi.ts

313 lines
8.5 KiB
TypeScript

import api from '../api';
import type { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================
// TYPES & INTERFACES
// ============================================================================
export interface AddressSuggestion {
label: string;
value: string;
coordinates?: {
x: number;
y: number;
};
}
/** Real Estate Project (Projekt). Backend-driven CRUD uses instanceId. */
export interface RealEstateProject {
id: string;
label: string;
statusProzess?: string;
mandateId?: string;
featureInstanceId?: string;
perimeter?: any;
parzellen?: RealEstateParcel[];
_createdAt?: number;
_modifiedAt?: number;
[key: string]: any;
}
/** Real Estate Parcel (Parzelle). */
export interface RealEstateParcel {
id: string;
label?: string;
mandateId?: string;
featureInstanceId?: string;
strasseNr?: string;
plz?: string;
perimeter?: any;
bauzone?: string;
_createdAt?: number;
_modifiedAt?: number;
[key: string]: any;
}
export interface PaginationParams {
page?: number;
pageSize?: number;
sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
filters?: Record<string, any>;
search?: string;
}
export interface PaginatedResponse<T> {
items: T[];
pagination?: {
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
sort?: Array<{ field: string; direction: string }>;
filters?: Record<string, any>;
};
}
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
// ============================================================================
// HELPER FUNCTIONS (instanceId-based CRUD)
// ============================================================================
function _getRealEstateBaseUrl(instanceId: string): string {
return `/api/realestate/${instanceId}`;
}
function _buildPaginationParams(params?: PaginationParams): Record<string, string | number | boolean> {
if (!params) return {};
const paginationObj: Record<string, unknown> = {};
if (params.page !== undefined) paginationObj.page = params.page;
if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize;
if (params.sort) paginationObj.sort = params.sort;
if (params.filters) paginationObj.filters = params.filters;
if (params.search) paginationObj.search = params.search;
if (Object.keys(paginationObj).length === 0) return {};
return { pagination: JSON.stringify(paginationObj) } as Record<string, string | number | boolean>;
}
// ============================================================================
// PROJECTS CRUD (instanceId-based)
// ============================================================================
export async function fetchProjects(
request: ApiRequestFunction,
instanceId: string,
params?: PaginationParams
): Promise<PaginatedResponse<RealEstateProject>> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/projects`,
method: 'get',
params: _buildPaginationParams(params)
});
}
export async function fetchProjectById(
request: ApiRequestFunction,
instanceId: string,
id: string
): Promise<RealEstateProject | null> {
try {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/projects/${id}`,
method: 'get'
});
} catch {
return null;
}
}
export async function createProject(
request: ApiRequestFunction,
instanceId: string,
data: Partial<RealEstateProject>
): Promise<RealEstateProject> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/projects`,
method: 'post',
data
});
}
export async function updateProject(
request: ApiRequestFunction,
instanceId: string,
id: string,
data: Partial<RealEstateProject>
): Promise<RealEstateProject> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/projects/${id}`,
method: 'put',
data
});
}
export async function deleteProject(
request: ApiRequestFunction,
instanceId: string,
id: string
): Promise<void> {
await request({
url: `${_getRealEstateBaseUrl(instanceId)}/projects/${id}`,
method: 'delete'
});
}
// ============================================================================
// PARCELS CRUD (instanceId-based)
// ============================================================================
export async function fetchParcels(
request: ApiRequestFunction,
instanceId: string,
params?: PaginationParams
): Promise<PaginatedResponse<RealEstateParcel>> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/parcels`,
method: 'get',
params: _buildPaginationParams(params)
});
}
export async function fetchParcelById(
request: ApiRequestFunction,
instanceId: string,
id: string
): Promise<RealEstateParcel | null> {
try {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/parcels/${id}`,
method: 'get'
});
} catch {
return null;
}
}
export async function createParcel(
request: ApiRequestFunction,
instanceId: string,
data: Partial<RealEstateParcel>
): Promise<RealEstateParcel> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/parcels`,
method: 'post',
data
});
}
export async function updateParcel(
request: ApiRequestFunction,
instanceId: string,
id: string,
data: Partial<RealEstateParcel>
): Promise<RealEstateParcel> {
return await request({
url: `${_getRealEstateBaseUrl(instanceId)}/parcels/${id}`,
method: 'put',
data
});
}
export async function deleteParcel(
request: ApiRequestFunction,
instanceId: string,
id: string
): Promise<void> {
await request({
url: `${_getRealEstateBaseUrl(instanceId)}/parcels/${id}`,
method: 'delete'
});
}
// ============================================================================
// ADDRESS AUTOCOMPLETE (legacy, no instanceId)
// ============================================================================
/**
* Get address autocomplete suggestions for Swiss addresses
* Endpoint: GET /api/realestate/address/autocomplete
*
* @param query - Search text (minimum 2 characters)
* @param limit - Maximum number of results (default: 10, max: 20)
* @returns Array of address suggestions
*/
export async function autocompleteAddress(
query: string,
limit: number = 10
): Promise<AddressSuggestion[]> {
if (query.length < 2) {
return [];
}
try {
const trimmedQuery = query.trim();
const requestParams = {
query: trimmedQuery,
limit: Math.min(Math.max(limit, 1), 20) // Clamp between 1 and 20
};
if (import.meta.env.DEV) {
console.log('🔍 [AddressAutocomplete] Requesting suggestions:', {
query: trimmedQuery,
limit: requestParams.limit,
url: '/api/realestate/address/autocomplete'
});
}
const response = await api.get<AddressSuggestion[]>('/api/realestate/address/autocomplete', {
params: requestParams
});
const results = response.data || [];
if (import.meta.env.DEV) {
console.log('✅ [AddressAutocomplete] Received suggestions:', {
count: results.length,
results: results.slice(0, 3) // Log first 3 for debugging
});
}
return results;
} catch (error: any) {
// Detailed error logging
const errorDetails: any = {
message: error?.message || 'Unknown error',
query: query.trim(),
limit: limit
};
if (error?.response) {
// HTTP error response
errorDetails.status = error.response.status;
errorDetails.statusText = error.response.statusText;
errorDetails.data = error.response.data;
errorDetails.headers = error.response.headers;
console.error('❌ [AddressAutocomplete] API Error Response:', {
status: errorDetails.status,
statusText: errorDetails.statusText,
detail: errorDetails.data?.detail || errorDetails.data,
url: error.config?.url,
method: error.config?.method
});
} else if (error?.request) {
// Request made but no response received
errorDetails.requestError = true;
console.error('❌ [AddressAutocomplete] Network Error - No response received:', {
message: error.message,
url: error.config?.url
});
} else {
// Error setting up request
console.error('❌ [AddressAutocomplete] Request Setup Error:', errorDetails);
}
// Log full error in dev mode
if (import.meta.env.DEV) {
console.error('❌ [AddressAutocomplete] Full error object:', error);
}
// Return empty array on error to allow graceful degradation
return [];
}
}