Add frontend POI management and travel time display

POIManager component in FilterPanel for creating/deleting POIs and
triggering distance calculations. PropertyCard shows travel time badges
(walk/cycle/transit) per POI. Map renders POI locations as red markers.
API client extended with POST body support for POI endpoints.
This commit is contained in:
Viktor Barzin 2026-02-08 13:16:32 +00:00
parent bb489c2032
commit 8509a0326f
No known key found for this signature in database
GPG key ID: 0EB088298288D958
9 changed files with 414 additions and 10 deletions

View file

@ -6,6 +6,7 @@ import { ApiError } from '@/types';
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
params?: Record<string, string | number | boolean | Date | undefined>;
body?: unknown;
}
/**
@ -35,7 +36,7 @@ export async function apiRequest<T>(
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const { method = 'GET', params } = options;
const { method = 'GET', params, body } = options;
let url = endpoint;
if (params) {
@ -45,13 +46,19 @@ export async function apiRequest<T>(
}
}
const response = await fetch(url, {
const fetchOptions: RequestInit = {
method,
headers: {
Authorization: `Bearer ${user.accessToken}`,
'Content-Type': 'application/json',
},
});
};
if (body !== undefined) {
fetchOptions.body = JSON.stringify(body);
}
const response = await fetch(url, fetchOptions);
if (!response.ok) {
throw new ApiError(`Error: ${response.status}`, response.status);

View file

@ -4,3 +4,4 @@ export { fetchListingGeoJSON, refreshListings } from './listingService';
export { streamListingGeoJSON, type StreamingProgress } from './streamingService';
export { fetchTasksForUser, fetchTaskStatus, cancelTask, clearAllTasks, type CancelTaskResponse, type ClearAllTasksResponse } from './taskService';
export { checkBackendHealth, type HealthStatus, type HealthCheckResult } from './healthService';
export { fetchUserPOIs, createPOI, updatePOI, deletePOI, triggerPOICalculation, fetchPOIDistances } from './poiService';

View file

@ -0,0 +1,64 @@
// POI API service for managing Points of Interest
import type { AuthUser } from '@/auth/types';
import type { POI, POIDistanceInfo } from '@/types';
import { apiRequest } from './apiClient';
export async function fetchUserPOIs(user: AuthUser): Promise<POI[]> {
return apiRequest<POI[]>(user, '/api/poi');
}
export async function createPOI(
user: AuthUser,
data: { name: string; address: string; latitude: number; longitude: number }
): Promise<POI> {
return apiRequest<POI>(user, '/api/poi', {
method: 'POST',
body: data,
});
}
export async function updatePOI(
user: AuthUser,
poiId: number,
data: { name?: string; address?: string; latitude?: number; longitude?: number }
): Promise<POI> {
return apiRequest<POI>(user, `/api/poi/${poiId}`, {
method: 'PUT',
body: data,
});
}
export async function deletePOI(user: AuthUser, poiId: number): Promise<void> {
await apiRequest(user, `/api/poi/${poiId}`, { method: 'DELETE' });
}
export async function triggerPOICalculation(
user: AuthUser,
poiId: number,
travelModes: string[],
listingType: 'RENT' | 'BUY',
listingIds?: number[]
): Promise<{ task_id: string; message: string }> {
return apiRequest(user, `/api/poi/${poiId}/calculate`, {
method: 'POST',
body: {
travel_modes: travelModes,
listing_type: listingType,
listing_ids: listingIds,
},
});
}
export async function fetchPOIDistances(
user: AuthUser,
listingId: number,
listingType: 'RENT' | 'BUY' = 'RENT'
): Promise<POIDistanceInfo[]> {
return apiRequest<POIDistanceInfo[]>(user, '/api/poi/distances', {
params: {
listing_id: listingId,
listing_type: listingType,
},
});
}