Run alembic migrations on startup, fix User model, add POI travel sorting and streaming options

This commit is contained in:
Viktor Barzin 2026-02-08 18:50:13 +00:00
parent 743e018668
commit 54bdcac14a
No known key found for this signature in database
GPG key ID: 0EB088298288D958
5 changed files with 34 additions and 10 deletions

View file

@ -3,15 +3,16 @@ import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import { Virtuoso } from 'react-virtuoso';
import { Button } from './ui/button';
import { PropertyCard } from './PropertyCard';
import type { GeoJSONFeatureCollection, PropertyFeature, PropertyProperties } from '@/types';
import type { GeoJSONFeatureCollection, PropertyFeature, PropertyProperties, POIDistanceInfo } from '@/types';
type SortField = 'total_price' | 'qmprice' | 'qm' | 'rooms' | 'last_seen';
type SortField = 'total_price' | 'qmprice' | 'qm' | 'rooms' | 'last_seen' | 'poi_travel';
type SortOrder = 'asc' | 'desc';
interface ListViewProps {
listingData: GeoJSONFeatureCollection;
onPropertyClick?: (property: PropertyProperties, coordinates: [number, number]) => void;
highlightedPropertyUrl?: string | null;
poiMetricSelection?: { poiId: number; travelMode: string } | null;
}
interface SortConfig {
@ -19,7 +20,7 @@ interface SortConfig {
order: SortOrder;
}
const SORT_OPTIONS: { field: SortField; label: string }[] = [
const BASE_SORT_OPTIONS: { field: SortField; label: string }[] = [
{ field: 'total_price', label: 'Price' },
{ field: 'qmprice', label: '£/m²' },
{ field: 'qm', label: 'Size' },
@ -27,7 +28,7 @@ const SORT_OPTIONS: { field: SortField; label: string }[] = [
{ field: 'last_seen', label: 'Last Seen' },
];
export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl }: ListViewProps) {
export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl, poiMetricSelection }: ListViewProps) {
const [sortConfig, setSortConfig] = useState<SortConfig>({ field: 'qmprice', order: 'asc' });
// Calculate average price per sqm for "good deal" indicator
@ -40,6 +41,13 @@ export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl
: 0;
}, [listingData]);
const sortOptions = useMemo(() => {
if (poiMetricSelection) {
return [...BASE_SORT_OPTIONS, { field: 'poi_travel' as SortField, label: 'Travel' }];
}
return BASE_SORT_OPTIONS;
}, [poiMetricSelection]);
// Sort features
const sortedFeatures = useMemo(() => {
const features = [...listingData.features];
@ -69,6 +77,18 @@ export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl
aValue = new Date(a.properties.last_seen).getTime();
bValue = new Date(b.properties.last_seen).getTime();
break;
case 'poi_travel': {
const getTravel = (f: PropertyFeature): number => {
if (!poiMetricSelection) return Infinity;
const match = f.properties.poi_distances?.find(
(d: POIDistanceInfo) => d.poi_id === poiMetricSelection.poiId && d.travel_mode === poiMetricSelection.travelMode,
);
return match?.duration_seconds ?? Infinity;
};
aValue = getTravel(a);
bValue = getTravel(b);
break;
}
default:
return 0;
}
@ -80,7 +100,7 @@ export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl
});
return features;
}, [listingData.features, sortConfig]);
}, [listingData.features, sortConfig, poiMetricSelection]);
const handleSort = (field: SortField) => {
setSortConfig((prev) => ({
@ -109,7 +129,7 @@ export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl
{/* Sort controls */}
<div className="flex items-center gap-1 p-2 border-b overflow-x-auto">
<span className="text-xs text-muted-foreground mr-1 shrink-0">Sort:</span>
{SORT_OPTIONS.map((option) => (
{sortOptions.map((option) => (
<Button
key={option.field}
variant={sortConfig.field === option.field ? 'secondary' : 'ghost'}

View file

@ -67,9 +67,13 @@ export interface StreamingProgress {
export async function* streamListingGeoJSON(
user: AuthUser,
parameters: ParameterValues,
onProgress?: (progress: StreamingProgress) => void
onProgress?: (progress: StreamingProgress) => void,
options?: { includePoiDistances?: boolean },
): AsyncGenerator<PropertyFeature[], void, unknown> {
const params = buildListingParams(parameters);
if (options?.includePoiDistances) {
params.include_poi_distances = 'true';
}
const queryString = buildQueryString(params);
const url = queryString
? `${API_ENDPOINTS.LISTING_GEOJSON_STREAM}?${queryString}`