Run alembic migrations on startup, fix User model, add POI travel sorting and streaming options
This commit is contained in:
parent
743e018668
commit
54bdcac14a
5 changed files with 34 additions and 10 deletions
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"root":["./src/app.tsx","./src/appsidebar.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/auth/authservice.ts","./src/auth/config.ts","./src/auth/errors.ts","./src/auth/passkeyservice.ts","./src/auth/types.ts","./src/components/activequery.tsx","./src/components/alerterror.tsx","./src/components/authcallback.tsx","./src/components/filterpanel.tsx","./src/components/header.tsx","./src/components/healthindicator.tsx","./src/components/listview.tsx","./src/components/loginmodal.tsx","./src/components/map.tsx","./src/components/parameters.tsx","./src/components/propertycard.tsx","./src/components/spinner.tsx","./src/components/statsbar.tsx","./src/components/streamingprogressbar.tsx","./src/components/taskindicator.tsx","./src/components/taskprogressdrawer.tsx","./src/components/ui/datepicker.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dialog.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/constants/colorschemes.ts","./src/constants/index.ts","./src/hooks/use-mobile.ts","./src/lib/utils.ts","./src/services/apiclient.ts","./src/services/healthservice.ts","./src/services/index.ts","./src/services/listingservice.ts","./src/services/streamingservice.ts","./src/services/taskservice.ts","./src/types/index.ts","./src/utils/maputils.ts"],"version":"5.8.3"}
|
||||
{"root":["./src/app.tsx","./src/appsidebar.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/auth/authservice.ts","./src/auth/config.ts","./src/auth/errors.ts","./src/auth/passkeyservice.ts","./src/auth/types.ts","./src/components/activequery.tsx","./src/components/alerterror.tsx","./src/components/authcallback.tsx","./src/components/filterpanel.tsx","./src/components/header.tsx","./src/components/healthindicator.tsx","./src/components/listview.tsx","./src/components/loginmodal.tsx","./src/components/map.tsx","./src/components/poimanager.tsx","./src/components/propertycard.tsx","./src/components/spinner.tsx","./src/components/statsbar.tsx","./src/components/streamingprogressbar.tsx","./src/components/taskindicator.tsx","./src/components/taskprogressdrawer.tsx","./src/components/visualizationcard.tsx","./src/components/ui/datepicker.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dialog.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/range-slider-field.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/constants/colorschemes.ts","./src/constants/index.ts","./src/hooks/use-mobile.ts","./src/lib/utils.ts","./src/services/apiclient.ts","./src/services/healthservice.ts","./src/services/index.ts","./src/services/listingservice.ts","./src/services/poiservice.ts","./src/services/streamingservice.ts","./src/services/taskservice.ts","./src/types/index.ts","./src/utils/maputils.ts","./src/utils/poiutils.ts"],"version":"5.8.3"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue