diff --git a/frontend/app/(sidebar)/maps/page.tsx b/frontend/app/(sidebar)/maps/page.tsx
index 65630b2..197a70b 100644
--- a/frontend/app/(sidebar)/maps/page.tsx
+++ b/frontend/app/(sidebar)/maps/page.tsx
@@ -1,12 +1,12 @@
-"use client"
+"use client";
-import { useState, useRef } from "react"
-import { Button } from "@/components/ui/button"
-import { Card, CardContent } from "@/components/ui/card"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { Badge } from "@/components/ui/badge"
-import { Slider } from "@/components/ui/slider"
-import { Switch } from "@/components/ui/switch"
+import { useState, useRef } from "react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent } from "@/components/ui/card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Badge } from "@/components/ui/badge";
+import { Slider } from "@/components/ui/slider";
+import { Switch } from "@/components/ui/switch";
import {
MapPin,
Home,
@@ -28,29 +28,30 @@ import {
Star,
Clock,
RefreshCw,
-} from "lucide-react"
-import Link from "next/link"
-import { TopNavigation } from "@/components/navigation/top-navigation"
+} from "lucide-react";
+import Link from "next/link";
+import { TopNavigation } from "@/components/navigation/top-navigation";
+import MapWithSearch from "@/components/map/map-with-search";
export default function MapsPage() {
- const [showFilters, setShowFilters] = useState(false)
- 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 [mapZoom, setMapZoom] = useState(14)
- const [selectedModel, setSelectedModel] = useState("Standard ML Model v2.4")
- const mapRef = useRef(null)
-
-
+ const [showFilters, setShowFilters] = useState(false);
+ 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 [mapZoom, setMapZoom] = useState(14);
+ const [selectedModel, setSelectedModel] = useState("Standard ML Model v2.4");
+ const mapRef = useRef(null);
const handleSendMessage = () => {
if (message.trim()) {
- setMessages([...messages, { role: "user", content: message }])
+ setMessages([...messages, { role: "user", content: message }]);
// Simulate AI response
setTimeout(() => {
setMessages((prev) => [
@@ -60,37 +61,41 @@ export default function MapsPage() {
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("")
+ ]);
+ }, 1000);
+ setMessage("");
}
- }
+ };
const handleZoomIn = () => {
- setMapZoom((prev) => Math.min(prev + 1, 20))
- }
+ setMapZoom((prev) => Math.min(prev + 1, 20));
+ };
const handleZoomOut = () => {
- setMapZoom((prev) => Math.max(prev - 1, 10))
- }
+ setMapZoom((prev) => Math.max(prev - 1, 10));
+ };
const handlePropertyClick = () => {
- setShowPropertyInfo(true)
- setShowFilters(false)
- setShowChat(false)
- }
+ setShowPropertyInfo(true);
+ setShowFilters(false);
+ setShowChat(false);
+ };
return (
- {/* Map Container */}
-
- {/* Map Placeholder - In a real implementation, this would be a map component */}
+
-
Map View
+
+
+ Map View
+
{/* Sample Property Markers */}
-
+
@@ -100,7 +105,10 @@ export default function MapsPage() {
-
+
@@ -110,7 +118,10 @@ export default function MapsPage() {
-
+
@@ -125,37 +136,20 @@ export default function MapsPage() {
{/* Top Navigation Bar */}
- {/* Map Controls */}
-
{/* Map Overlay Controls */}
{
- setShowAnalytics(!showAnalytics)
+ setShowAnalytics(!showAnalytics);
if (showAnalytics) {
- setShowFilters(false)
- setShowPropertyInfo(false)
+ setShowFilters(false);
+ setShowPropertyInfo(false);
}
}}
>
@@ -164,12 +158,14 @@ export default function MapsPage() {
{
- setShowFilters(!showFilters)
+ setShowFilters(!showFilters);
if (showFilters) {
- setShowAnalytics(false)
- setShowPropertyInfo(false)
+ setShowAnalytics(false);
+ setShowPropertyInfo(false);
}
}}
>
@@ -178,11 +174,13 @@ export default function MapsPage() {
{
- setShowChat(!showChat)
+ setShowChat(!showChat);
if (showChat) {
- setShowPropertyInfo(false)
+ setShowPropertyInfo(false);
}
}}
>
@@ -199,7 +197,12 @@ export default function MapsPage() {
Property Details
- setShowPropertyInfo(false)}>
+ setShowPropertyInfo(false)}
+ >
@@ -244,12 +247,16 @@ export default function MapsPage() {
-
Environmental Factors
+
+ Environmental Factors
+
Flood Risk
- Moderate
+
+ Moderate
+
@@ -314,14 +321,21 @@ export default function MapsPage() {
>
- setShowAnalytics(false)}>
+ setShowAnalytics(false)}
+ >
-
Information in radius will be analyzed
+
+ Information in radius will be analyzed
+
Using: {selectedModel}
@@ -334,7 +348,9 @@ export default function MapsPage() {
Area Price History
10,000,000 Baht
-
Overall Price History of this area
+
+ Overall Price History of this area
+
{/* Simple line chart simulation */}
@@ -358,7 +374,9 @@ export default function MapsPage() {
Price Prediction
15,000,000 Baht
-
The estimated price based on various factors.
+
+ The estimated price based on various factors.
+
{/* Simple line chart simulation */}
@@ -406,9 +424,12 @@ export default function MapsPage() {
- New BTS Extension Planned
+
+ New BTS Extension Planned
+
- The BTS Skytrain will be extended to cover more areas in Sukhumvit by 2025.
+ The BTS Skytrain will be extended to cover more areas in
+ Sukhumvit by 2025.
@@ -418,9 +439,12 @@ export default function MapsPage() {
- Property Tax Changes
+
+ Property Tax Changes
+
- New property tax regulations will take effect next month affecting luxury condominiums.
+ New property tax regulations will take effect next month
+ affecting luxury condominiums.
@@ -453,13 +477,22 @@ export default function MapsPage() {
Property Filters
- setShowFilters(false)}>
+ setShowFilters(false)}
+ >
-
+
Basic
Advanced
@@ -468,7 +501,9 @@ export default function MapsPage() {
-
Area Radius
+
+ Area Radius
+
- Time Period
+
+ Time Period
+
All Time
Last Month
@@ -494,7 +531,9 @@ export default function MapsPage() {
-
Property Type
+
+ Property Type
+
Any Type
House
@@ -511,7 +550,9 @@ export default function MapsPage() {
-
Price Range
+
+ Price Range
+
฿{priceRange[0].toLocaleString()}
฿{priceRange[1].toLocaleString()}
@@ -526,7 +567,9 @@ export default function MapsPage() {
-
Environmental Factors
+
+ Environmental Factors
+
Low Flood Risk
@@ -545,7 +588,9 @@ export default function MapsPage() {
-
Facilities Nearby
+
+ Facilities Nearby
+
@@ -560,7 +605,11 @@ export default function MapsPage() {
-
+
Hospitals
@@ -590,7 +639,12 @@ export default function MapsPage() {
Chat Assistant
- setShowChat(false)}>
+ setShowChat(false)}
+ >
@@ -598,10 +652,17 @@ export default function MapsPage() {
{messages.map((msg, index) => (
-
+
{msg.content}
@@ -619,10 +680,15 @@ export default function MapsPage() {
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
- if (e.key === "Enter") handleSendMessage()
+ if (e.key === "Enter") handleSendMessage();
}}
/>
-
+
@@ -631,7 +697,7 @@ export default function MapsPage() {
)}
{/* Map Legend */}
-
+ {/*
Property Status
@@ -647,8 +713,7 @@ export default function MapsPage() {
Sold
-
+
*/}
- )
+ );
}
-
diff --git a/frontend/components/map/map-with-search.tsx b/frontend/components/map/map-with-search.tsx
new file mode 100644
index 0000000..4318580
--- /dev/null
+++ b/frontend/components/map/map-with-search.tsx
@@ -0,0 +1,111 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import { Loader } from "@googlemaps/js-api-loader";
+import { Input } from "../ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../ui/select";
+
+declare global {
+ interface Window {
+ google: typeof google;
+ }
+}
+
+const GOOGLE_MAPS_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!;
+
+export default function MapWithSearch() {
+ const mapRef = useRef
(null);
+ const inputRef = useRef(null);
+ const mapInstanceRef = useRef(null);
+ const [mapType, setMapType] = useState("roadmap");
+
+ useEffect(() => {
+ const loader = new Loader({
+ apiKey: GOOGLE_MAPS_API_KEY,
+ version: "weekly",
+ libraries: ["places"],
+ });
+
+ loader.load().then(() => {
+ if (!mapRef.current || !inputRef.current) return;
+
+ const map = new google.maps.Map(mapRef.current, {
+ center: { lat: 13.7563, lng: 100.5018 },
+ zoom: 13,
+ gestureHandling: "greedy",
+ mapTypeControl: false,
+ mapTypeId: mapType,
+ });
+
+ mapInstanceRef.current = map;
+
+ const input = inputRef.current;
+
+ const searchBox = new google.maps.places.SearchBox(input);
+
+ map.addListener("bounds_changed", () => {
+ searchBox.setBounds(map.getBounds()!);
+ });
+
+ let marker: google.maps.Marker;
+
+ searchBox.addListener("places_changed", () => {
+ const places = searchBox.getPlaces();
+ if (!places || places.length === 0) return;
+
+ const place = places[0];
+ if (!place.geometry || !place.geometry.location) return;
+
+ map.panTo(place.geometry.location);
+ map.setZoom(15);
+
+ if (marker) marker.setMap(null);
+ marker = new google.maps.Marker({
+ map,
+ position: place.geometry.location,
+ });
+ });
+ });
+ }, []);
+
+ useEffect(() => {
+ if (mapInstanceRef.current) {
+ mapInstanceRef.current.setMapTypeId(mapType as google.maps.MapTypeId);
+ }
+ }, [mapType]);
+
+return (
+
+
+
+ setMapType(value as google.maps.MapTypeId)}
+ defaultValue="roadmap"
+ >
+
+
+
+
+ Roadmap
+ Satellite
+ Hybrid
+ Terrain
+
+
+
+
+
+);
+
+}
diff --git a/frontend/components/navigation/top-navigation.tsx b/frontend/components/navigation/top-navigation.tsx
index 553d21c..40a5847 100644
--- a/frontend/components/navigation/top-navigation.tsx
+++ b/frontend/components/navigation/top-navigation.tsx
@@ -34,18 +34,6 @@ export function TopNavigation() {
BorBann
-
diff --git a/frontend/package.json b/frontend/package.json
index c3602eb..aa5d25f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
+ "@googlemaps/js-api-loader": "^1.16.8",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
@@ -94,6 +95,7 @@
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "latest",
"@tailwindcss/postcss": "^4",
+ "@types/google.maps": "^3.58.1",
"@types/node": "^20",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index ecd41ca..de9f337 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -5,6 +5,9 @@ overrides:
'@types/react-dom': 19.0.4
dependencies:
+ '@googlemaps/js-api-loader':
+ specifier: ^1.16.8
+ version: 1.16.8
'@hookform/resolvers':
specifier: ^3.9.1
version: 3.9.1(react-hook-form@7.54.1)
@@ -172,6 +175,9 @@ devDependencies:
'@eslint/eslintrc':
specifier: ^3
version: 3.0.0
+ '@types/google.maps':
+ specifier: ^3.58.1
+ version: 3.58.1
'@types/node':
specifier: ^20
version: 20.0.0
@@ -305,6 +311,10 @@ packages:
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
dev: false
+ /@googlemaps/js-api-loader@1.16.8:
+ resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==}
+ dev: false
+
/@hookform/resolvers@3.9.1(react-hook-form@7.54.1):
resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==}
peerDependencies:
@@ -1997,6 +2007,10 @@ packages:
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
dev: false
+ /@types/google.maps@3.58.1:
+ resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==}
+ dev: true
+
/@types/json5@0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true