feat: add ChatPanel and FiltersPanel components to MapsPage for enhanced user interaction and filtering options

This commit is contained in:
THIS ONE IS A LITTLE BIT TRICKY KRUB 2025-04-11 17:56:42 +07:00
parent e37364e3dc
commit 01abdb2c5d
3 changed files with 287 additions and 253 deletions

View File

@ -1,13 +1,12 @@
"use client";
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 { TopNavigation } from "@/components/navigation/top-navigation";
import { Badge } from "@/components/ui/badge";
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 {
BarChart2,
Bath,
@ -18,7 +17,6 @@ import {
Home,
MapPin,
MessageCircle,
Send,
Star,
Sun,
Wind,
@ -33,32 +31,9 @@ export default function MapsPage() {
const [showAnalytics, setShowAnalytics] = useState(false);
const [showChat, setShowChat] = useState(false);
const [showPropertyInfo, setShowPropertyInfo] = useState(false);
const [activeTab, setActiveTab] = useState("basic");
const [priceRange, setPriceRange] = useState([5000000, 20000000]);
const [radius, setRadius] = useState(30);
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 analyticsRef = useRef<HTMLDivElement>(null);
const filtersRef = useRef<HTMLDivElement>(null);
const chatRef = useRef<HTMLDivElement>(null);
const handlePropertyClick = () => {
setShowPropertyInfo(true);
@ -286,241 +261,28 @@ export default function MapsPage() {
)}
{/* Analytics Panel */}
{showAnalytics && (
<Draggable nodeRef={nodeRef as React.RefObject<HTMLElement>}>
<div ref={nodeRef}>
<Draggable nodeRef={analyticsRef as React.RefObject<HTMLElement>}>
<div ref={analyticsRef}>
<AnalyticsPanel setShowAnalytics={setShowAnalytics} />
</div>
</Draggable>
)}
{/* Filters Panel */}
{showFilters && (
<div className="absolute top-20 right-4 w-96 map-overlay z-20">
<div className="map-overlay-header">
<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>
<Draggable nodeRef={filtersRef as React.RefObject<HTMLElement>}>
<div ref={filtersRef}>
<FiltersPanel setShowFilters={setShowFilters} />
</div>
<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>
</Draggable>
)}
{/* Chat Panel */}
{showChat && (
<div className="absolute top-20 right-4 w-96 h-[500px] map-overlay z-20 flex flex-col">
<div className="map-overlay-header">
<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>
<Draggable nodeRef={chatRef as React.RefObject<HTMLElement>}>
<div ref={chatRef}>
<ChatPanel setShowChat={setShowChat} />
</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>
</Draggable>
)}
{/* Map Legend */}

View 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>
);
}

View 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>
);
}