mirror of
https://github.com/borbann-platform/backend-api.git
synced 2025-12-18 20:24:05 +01:00
feat: add ChatPanel and FiltersPanel components to MapsPage for enhanced user interaction and filtering options
This commit is contained in:
parent
e37364e3dc
commit
01abdb2c5d
@ -1,13 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AnalyticsPanel } from "@/components/map/analytics-panel";
|
import { AnalyticsPanel } from "@/components/map/analytics-panel";
|
||||||
|
import { ChatPanel } from "@/components/map/chat-panel";
|
||||||
|
import { FiltersPanel } from "@/components/map/filters-panel";
|
||||||
import MapWithSearch from "@/components/map/map-with-search";
|
import MapWithSearch from "@/components/map/map-with-search";
|
||||||
import { TopNavigation } from "@/components/navigation/top-navigation";
|
import { TopNavigation } from "@/components/navigation/top-navigation";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Slider } from "@/components/ui/slider";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import {
|
import {
|
||||||
BarChart2,
|
BarChart2,
|
||||||
Bath,
|
Bath,
|
||||||
@ -18,7 +17,6 @@ import {
|
|||||||
Home,
|
Home,
|
||||||
MapPin,
|
MapPin,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Send,
|
|
||||||
Star,
|
Star,
|
||||||
Sun,
|
Sun,
|
||||||
Wind,
|
Wind,
|
||||||
@ -33,32 +31,9 @@ export default function MapsPage() {
|
|||||||
const [showAnalytics, setShowAnalytics] = useState(false);
|
const [showAnalytics, setShowAnalytics] = useState(false);
|
||||||
const [showChat, setShowChat] = useState(false);
|
const [showChat, setShowChat] = useState(false);
|
||||||
const [showPropertyInfo, setShowPropertyInfo] = useState(false);
|
const [showPropertyInfo, setShowPropertyInfo] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState("basic");
|
const analyticsRef = useRef<HTMLDivElement>(null);
|
||||||
const [priceRange, setPriceRange] = useState([5000000, 20000000]);
|
const filtersRef = useRef<HTMLDivElement>(null);
|
||||||
const [radius, setRadius] = useState(30);
|
const chatRef = useRef<HTMLDivElement>(null);
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
const [messages, setMessages] = useState([
|
|
||||||
{ role: "assistant", content: "Hi! How can I help you today?" },
|
|
||||||
]);
|
|
||||||
const nodeRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const handleSendMessage = () => {
|
|
||||||
if (message.trim()) {
|
|
||||||
setMessages([...messages, { role: "user", content: message }]);
|
|
||||||
// Simulate AI response
|
|
||||||
setTimeout(() => {
|
|
||||||
setMessages((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
content:
|
|
||||||
"I can provide information about properties in this area. Would you like to know about flood risks, air quality, or nearby amenities?",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}, 1000);
|
|
||||||
setMessage("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePropertyClick = () => {
|
const handlePropertyClick = () => {
|
||||||
setShowPropertyInfo(true);
|
setShowPropertyInfo(true);
|
||||||
@ -286,241 +261,28 @@ export default function MapsPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Analytics Panel */}
|
{/* Analytics Panel */}
|
||||||
|
|
||||||
{showAnalytics && (
|
{showAnalytics && (
|
||||||
<Draggable nodeRef={nodeRef as React.RefObject<HTMLElement>}>
|
<Draggable nodeRef={analyticsRef as React.RefObject<HTMLElement>}>
|
||||||
<div ref={nodeRef}>
|
<div ref={analyticsRef}>
|
||||||
<AnalyticsPanel setShowAnalytics={setShowAnalytics} />
|
<AnalyticsPanel setShowAnalytics={setShowAnalytics} />
|
||||||
</div>
|
</div>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Filters Panel */}
|
|
||||||
{showFilters && (
|
{showFilters && (
|
||||||
<div className="absolute top-20 right-4 w-96 map-overlay z-20">
|
<Draggable nodeRef={filtersRef as React.RefObject<HTMLElement>}>
|
||||||
<div className="map-overlay-header">
|
<div ref={filtersRef}>
|
||||||
<div className="flex items-center gap-2">
|
<FiltersPanel setShowFilters={setShowFilters} />
|
||||||
<Filter className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Property Filters</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-8 w-8"
|
|
||||||
onClick={() => setShowFilters(false)}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Draggable>
|
||||||
<Tabs
|
|
||||||
defaultValue="basic"
|
|
||||||
value={activeTab}
|
|
||||||
onValueChange={setActiveTab}
|
|
||||||
>
|
|
||||||
<TabsList className="w-full grid grid-cols-2">
|
|
||||||
<TabsTrigger value="basic">Basic</TabsTrigger>
|
|
||||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="basic" className="p-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Area Radius
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Slider
|
|
||||||
defaultValue={[30]}
|
|
||||||
max={50}
|
|
||||||
min={1}
|
|
||||||
step={1}
|
|
||||||
onValueChange={(value) => setRadius(value[0])}
|
|
||||||
className="flex-1"
|
|
||||||
/>
|
|
||||||
<span className="text-sm w-16 text-right">{radius} km</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Time Period
|
|
||||||
</label>
|
|
||||||
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
|
||||||
<option value="all">All Time</option>
|
|
||||||
<option value="1m">Last Month</option>
|
|
||||||
<option value="3m">Last 3 Months</option>
|
|
||||||
<option value="6m">Last 6 Months</option>
|
|
||||||
<option value="1y">Last Year</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Property Type
|
|
||||||
</label>
|
|
||||||
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
|
||||||
<option value="any">Any Type</option>
|
|
||||||
<option value="house">House</option>
|
|
||||||
<option value="condo">Condominium</option>
|
|
||||||
<option value="townhouse">Townhouse</option>
|
|
||||||
<option value="land">Land</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className="w-full">Apply Filters</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="advanced" className="p-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Price Range
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground mb-2">
|
|
||||||
<span>฿{priceRange[0].toLocaleString()}</span>
|
|
||||||
<span>฿{priceRange[1].toLocaleString()}</span>
|
|
||||||
</div>
|
|
||||||
<Slider
|
|
||||||
defaultValue={[5000000, 20000000]}
|
|
||||||
max={50000000}
|
|
||||||
min={1000000}
|
|
||||||
step={1000000}
|
|
||||||
onValueChange={(value) => setPriceRange(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Environmental Factors
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Low Flood Risk</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Good Air Quality</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Low Noise Pollution</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">
|
|
||||||
Facilities Nearby
|
|
||||||
</label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="bts" className="h-4 w-4" />
|
|
||||||
<label htmlFor="bts" className="text-sm">
|
|
||||||
BTS/MRT Station
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="school" className="h-4 w-4" />
|
|
||||||
<label htmlFor="school" className="text-sm">
|
|
||||||
Schools
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="hospital"
|
|
||||||
className="h-4 w-4"
|
|
||||||
/>
|
|
||||||
<label htmlFor="hospital" className="text-sm">
|
|
||||||
Hospitals
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="mall" className="h-4 w-4" />
|
|
||||||
<label htmlFor="mall" className="text-sm">
|
|
||||||
Shopping Malls
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className="w-full">Apply Filters</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Chat Panel */}
|
|
||||||
{showChat && (
|
{showChat && (
|
||||||
<div className="absolute top-20 right-4 w-96 h-[500px] map-overlay z-20 flex flex-col">
|
<Draggable nodeRef={chatRef as React.RefObject<HTMLElement>}>
|
||||||
<div className="map-overlay-header">
|
<div ref={chatRef}>
|
||||||
<div className="flex items-center gap-2">
|
<ChatPanel setShowChat={setShowChat} />
|
||||||
<MessageCircle className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Chat Assistant</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-8 w-8"
|
|
||||||
onClick={() => setShowChat(false)}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Draggable>
|
||||||
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
|
||||||
{messages.map((msg, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`flex ${
|
|
||||||
msg.role === "user" ? "justify-end" : "justify-start"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`max-w-[80%] rounded-lg px-3 py-2 ${
|
|
||||||
msg.role === "user"
|
|
||||||
? "bg-primary text-primary-foreground"
|
|
||||||
: "bg-muted"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{msg.content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-3 border-t">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Type your message..."
|
|
||||||
className="flex-1 h-10 px-3 rounded-md border border-input bg-background"
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") handleSendMessage();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="icon"
|
|
||||||
className="h-10 w-10"
|
|
||||||
onClick={handleSendMessage}
|
|
||||||
>
|
|
||||||
<Send className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Map Legend */}
|
{/* Map Legend */}
|
||||||
|
|||||||
97
frontend/components/map/chat-panel.tsx
Normal file
97
frontend/components/map/chat-panel.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { MessageCircle, Send, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
export function ChatPanel({
|
||||||
|
setShowChat,
|
||||||
|
}: {
|
||||||
|
setShowChat: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [messages, setMessages] = useState([
|
||||||
|
{ role: "assistant", content: "Hi! How can I help you today?" },
|
||||||
|
]);
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
if (message.trim()) {
|
||||||
|
setMessages([...messages, { role: "user", content: message }]);
|
||||||
|
// Simulate AI response
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content:
|
||||||
|
"I can provide information about properties in this area. Would you like to know about flood risks, air quality, or nearby amenities?",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, 1000);
|
||||||
|
setMessage("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 h-[500px] map-overlay z-20 flex flex-col bg-background p-5 rounded-md scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MessageCircle className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Chat Assistant</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowChat(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
||||||
|
{messages.map((msg, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`flex ${
|
||||||
|
msg.role === "user" ? "justify-end" : "justify-start"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`max-w-[80%] rounded-lg px-3 py-2 ${
|
||||||
|
msg.role === "user"
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{msg.content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 border-t">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Type your message..."
|
||||||
|
className="flex-1 h-10 px-3 rounded-md border border-input bg-background"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") handleSendMessage();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="icon"
|
||||||
|
className="h-10 w-10"
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
175
frontend/components/map/filters-panel.tsx
Normal file
175
frontend/components/map/filters-panel.tsx
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { Filter, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Slider } from "../ui/slider";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
|
|
||||||
|
export function FiltersPanel({
|
||||||
|
setShowFilters,
|
||||||
|
}: {
|
||||||
|
setShowFilters: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [activeTab, setActiveTab] = useState("basic");
|
||||||
|
const [priceRange, setPriceRange] = useState([5000000, 20000000]);
|
||||||
|
const [radius, setRadius] = useState(30);
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 map-overlay z-20 bg-background p-5 rounded-md overflow-y-auto scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header flex w-full">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Filter className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Property Filters</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowFilters(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultValue="basic"
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={setActiveTab}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
<TabsList className="w-full grid grid-cols-2">
|
||||||
|
<TabsTrigger value="basic">Basic</TabsTrigger>
|
||||||
|
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="basic" className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Area Radius
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Slider
|
||||||
|
defaultValue={[30]}
|
||||||
|
max={50}
|
||||||
|
min={1}
|
||||||
|
step={1}
|
||||||
|
onValueChange={(value) => setRadius(value[0])}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-sm w-16 text-right">{radius} km</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Time Period
|
||||||
|
</label>
|
||||||
|
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
||||||
|
<option value="all">All Time</option>
|
||||||
|
<option value="1m">Last Month</option>
|
||||||
|
<option value="3m">Last 3 Months</option>
|
||||||
|
<option value="6m">Last 6 Months</option>
|
||||||
|
<option value="1y">Last Year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Property Type
|
||||||
|
</label>
|
||||||
|
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
||||||
|
<option value="any">Any Type</option>
|
||||||
|
<option value="house">House</option>
|
||||||
|
<option value="condo">Condominium</option>
|
||||||
|
<option value="townhouse">Townhouse</option>
|
||||||
|
<option value="land">Land</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full">Apply Filters</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="advanced" className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Price Range
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center justify-between text-xs text-muted-foreground mb-2">
|
||||||
|
<span>฿{priceRange[0].toLocaleString()}</span>
|
||||||
|
<span>฿{priceRange[1].toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
defaultValue={[5000000, 20000000]}
|
||||||
|
max={50000000}
|
||||||
|
min={1000000}
|
||||||
|
step={1000000}
|
||||||
|
onValueChange={(value) => setPriceRange(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Environmental Factors
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Low Flood Risk</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Good Air Quality</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Low Noise Pollution</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Facilities Nearby
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="bts" className="h-4 w-4" />
|
||||||
|
<label htmlFor="bts" className="text-sm">
|
||||||
|
BTS/MRT Station
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="school" className="h-4 w-4" />
|
||||||
|
<label htmlFor="school" className="text-sm">
|
||||||
|
Schools
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="hospital" className="h-4 w-4" />
|
||||||
|
<label htmlFor="hospital" className="text-sm">
|
||||||
|
Hospitals
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="mall" className="h-4 w-4" />
|
||||||
|
<label htmlFor="mall" className="text-sm">
|
||||||
|
Shopping Malls
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full">Apply Filters</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user