Add proper buy listing support with type-aware UI filters and display

This commit is contained in:
Viktor Barzin 2026-02-01 19:13:29 +00:00
parent c7ac448f15
commit 6d8f69610f
6 changed files with 416 additions and 87 deletions

View file

@ -101,6 +101,24 @@ export function FilterPanel({ onSubmit, isLoading, listingCount }: FilterPanelPr
},
});
// Watch listing_type to make filters type-aware
const watchedListingType = form.watch('listing_type');
// Update price defaults when listing type changes
useEffect(() => {
if (watchedListingType === ListingType.BUY) {
form.setValue('min_price', 300000);
form.setValue('max_price', 600000);
} else {
form.setValue('min_price', 2000);
form.setValue('max_price', 3000);
}
// Clear furnish types when switching to BUY
if (watchedListingType === ListingType.BUY) {
setSelectedFurnishTypes([]);
}
}, [watchedListingType, form]);
const handleFormSubmit = (action: 'fetch-data' | 'visualize') => {
return form.handleSubmit((values) => {
const params: ParameterValues = {
@ -400,29 +418,31 @@ export function FilterPanel({ onSubmit, isLoading, listingCount }: FilterPanelPr
)}
/>
</div>
<div>
<FormLabel className="text-xs">Furnishing</FormLabel>
<div className="flex flex-wrap gap-2 mt-2">
{[
{ value: FurnishType.FURNISHED, label: 'Furnished' },
{ value: FurnishType.PART_FURNISHED, label: 'Part' },
{ value: FurnishType.UNFURNISHED, label: 'Unfurn.' },
].map((option) => (
<button
key={option.value}
type="button"
onClick={() => toggleFurnishType(option.value)}
className={`px-2 py-1 text-xs rounded-md border transition-colors ${
selectedFurnishTypes.includes(option.value)
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-muted border-input'
}`}
>
{option.label}
</button>
))}
{watchedListingType === ListingType.RENT && (
<div>
<FormLabel className="text-xs">Furnishing</FormLabel>
<div className="flex flex-wrap gap-2 mt-2">
{[
{ value: FurnishType.FURNISHED, label: 'Furnished' },
{ value: FurnishType.PART_FURNISHED, label: 'Part' },
{ value: FurnishType.UNFURNISHED, label: 'Unfurn.' },
].map((option) => (
<button
key={option.value}
type="button"
onClick={() => toggleFurnishType(option.value)}
className={`px-2 py-1 text-xs rounded-md border transition-colors ${
selectedFurnishTypes.includes(option.value)
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-muted border-input'
}`}
>
{option.label}
</button>
))}
</div>
</div>
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
@ -456,33 +476,32 @@ export function FilterPanel({ onSubmit, isLoading, listingCount }: FilterPanelPr
</AccordionContent>
</AccordionItem>
{/* Availability */}
{/* Availability / Recency */}
<AccordionItem value="availability">
<AccordionTrigger className="py-2 text-sm font-medium">
Availability
{watchedListingType === ListingType.RENT ? 'Availability' : 'Recency'}
</AccordionTrigger>
<AccordionContent>
<div className="space-y-4">
<FormField
control={form.control}
name="available_from"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs">Available From</FormLabel>
<FormControl>
<Calendar29
onSelect={field.onChange}
selected={field.value}
rawInputValue={availableFromRawInput}
onChangeRawInputValue={setAvailableFromRawInput}
/>
</FormControl>
<FormDescription className="text-xs">
Rental listings only
</FormDescription>
</FormItem>
)}
/>
{watchedListingType === ListingType.RENT && (
<FormField
control={form.control}
name="available_from"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs">Available From</FormLabel>
<FormControl>
<Calendar29
onSelect={field.onChange}
selected={field.value}
rawInputValue={availableFromRawInput}
onChangeRawInputValue={setAvailableFromRawInput}
/>
</FormControl>
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name="last_seen_days"

View file

@ -59,7 +59,9 @@ export function PropertyCard({
<div className="flex items-start justify-between gap-2">
<div className="font-semibold text-base truncate">
£{property.total_price.toLocaleString()}
<span className="text-muted-foreground font-normal text-sm">/mo</span>
{property.listing_type !== 'BUY' && (
<span className="text-muted-foreground font-normal text-sm">/mo</span>
)}
</div>
{priceIndicator && (
<span className={`text-xs px-1.5 py-0.5 rounded ${priceIndicator.color}`}>
@ -119,7 +121,9 @@ export function PropertyCard({
<div>
<div className="font-semibold text-xl">
£{property.total_price.toLocaleString()}
<span className="text-muted-foreground font-normal text-sm">/mo</span>
{property.listing_type !== 'BUY' && (
<span className="text-muted-foreground font-normal text-sm">/mo</span>
)}
</div>
{priceIndicator && (
<span className={`inline-block mt-1 text-xs px-2 py-0.5 rounded ${priceIndicator.color}`}>
@ -145,10 +149,18 @@ export function PropertyCard({
<PoundSterling className="h-4 w-4 text-muted-foreground" />
<span><strong>£{property.qmprice}</strong>/m²</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span>Available <strong>{property.available_from}</strong></span>
</div>
{property.listing_type !== 'BUY' && property.available_from && (
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span>Available <strong>{property.available_from}</strong></span>
</div>
)}
{property.listing_type === 'BUY' && (
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span>Seen <strong>{lastSeenDays}d</strong> ago</span>
</div>
)}
</div>
{/* Agency and last seen */}