313 lines
8.5 KiB
TypeScript
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 [];
|
|
}
|
|
}
|