Skip to content

Commit 9d06323

Browse files
authored
Merge pull request #22 from AIoT-Lab-AI4LIFE/feat/reponsive-tropical-cyclone
feat/reponsive-tropical-cyclone
2 parents 9a40ed1 + c329a67 commit 9d06323

6 files changed

Lines changed: 220 additions & 166 deletions

File tree

src/features/weather-map/components/search-input.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@ export function SearchInput() {
1616
} = useWeatherMapStore();
1717

1818
const handleSearch = useCallback(async () => {
19-
if (!searchQuery.trim())
20-
return;
19+
if (!searchQuery.trim()) return;
2120

2221
setIsSearching(true);
2322
try {
24-
// TODO: Implement actual search API call
25-
// For now, this is a placeholder
26-
27-
// Mock search results - replace with actual API call
2823
const mockResults = [
2924
{
3025
id: 1,
@@ -37,29 +32,31 @@ export function SearchInput() {
3732

3833
setSearchResults(mockResults);
3934

40-
// Auto-select first result and update map
4135
if (mockResults.length > 0) {
4236
const firstResult = mockResults[0];
4337
setSelectedStation(firstResult);
4438
setMapCenter([firstResult.lat, firstResult.lng]);
4539
setMapZoom(10);
4640
}
47-
}
48-
catch (error) {
41+
} catch (error) {
4942
console.error("Search error:", error);
5043
setSearchResults([]);
51-
}
52-
finally {
44+
} finally {
5345
setIsSearching(false);
5446
}
55-
}, [searchQuery, setIsSearching, setSearchResults, setSelectedStation, setMapCenter, setMapZoom]);
47+
}, [
48+
searchQuery,
49+
setIsSearching,
50+
setSearchResults,
51+
setSelectedStation,
52+
setMapCenter,
53+
setMapZoom,
54+
]);
5655

5756
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
5857
const value = e.target.value;
5958
setSearchQuery(value);
6059

61-
// TODO: Implement debounced search logic here
62-
// For now, just clear results when query is empty
6360
if (!value.trim()) {
6461
setSearchResults([]);
6562
}
@@ -72,7 +69,7 @@ export function SearchInput() {
7269
};
7370

7471
return (
75-
<div className="flex items-center w-[448px] h-12 px-4 bg-white rounded-full shadow-md">
72+
<div className="flex items-center w-full md:w-[448px] h-12 px-4 bg-white rounded-full shadow-md">
7673
<Icon
7774
path={isSearching ? mdiLoading : mdiMagnify}
7875
size={1}
Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
import { stormsApi } from "@/services/apis/storms.api";
2-
import { mdiWeatherHurricaneOutline } from "@mdi/js";
2+
import { mdiChevronUp, mdiWeatherHurricaneOutline } from "@mdi/js";
33
import Icon from "@mdi/react";
44
import dayjs from "dayjs";
5-
import { useEffect } from "react";
5+
import { useEffect, useState } from "react";
66
import { useWeatherMapStore } from "../store";
77

88
export function StormSelector() {
9-
const { isStormSelectorOpen, setSelectedStormId, setStorms, storms, selectedStormId } = useWeatherMapStore();
9+
const {
10+
isStormSelectorOpen,
11+
setSelectedStormId,
12+
setStorms,
13+
storms,
14+
selectedStormId,
15+
} = useWeatherMapStore();
16+
17+
const [isCollapsed, setIsCollapsed] = useState(false);
18+
1019
const handleStormClick = (stormId: number | null) => {
1120
setSelectedStormId(stormId);
21+
setIsCollapsed(true);
1222
};
1323

1424
useEffect(() => {
@@ -17,93 +27,72 @@ export function StormSelector() {
1727
const response = await stormsApi.storms.list();
1828
const data = response.data.sort((a, b) => b.storm_id - a.storm_id);
1929
setStorms(data);
20-
}
21-
catch (error) {
30+
} catch (error) {
2231
console.error("Error fetching storms:", error);
2332
}
2433
};
2534

2635
fetchStorms();
2736
}, [setStorms]);
2837

38+
if (!isStormSelectorOpen) {
39+
return null;
40+
}
41+
2942
return (
30-
<>
31-
{/* Storm Selector Panel */}
32-
{isStormSelectorOpen && (
33-
<div className="absolute top-6 right-6 z-[1000] pointer-events-auto w-[400px]">
34-
<div className="p-4 flex flex-col gap-2 bg-white/70 backdrop-blur-sm shadow-2xl rounded-2xl">
35-
<div className="flex justify-between items-start">
36-
<div>
37-
<h2 className="text-2xl font-medium text-black">Select Storm</h2>
38-
<p className="text-base text-black/80 mt-2">
39-
Choose a storm to display its trajectory
40-
</p>
41-
</div>
42-
{/* <button
43-
onClick={onToggle}
44-
className="w-8 h-8 flex items-center justify-center rounded-full bg-white hover:bg-gray-200 transition-colors"
45-
>
46-
<Icon path={mdiClose} size={1} className="text-black" />
47-
</button> */}
48-
</div>
43+
<div className="absolute top-20 right-4 md:top-6 md:right-6 z-20 pointer-events-auto w-[280px] md:w-[240px]">
44+
{" "}
45+
<div className="p-3 flex flex-col gap-2 bg-white/70 backdrop-blur-sm shadow-lg rounded-2xl transition-all duration-300">
46+
<button
47+
type="button"
48+
onClick={() => setIsCollapsed(!isCollapsed)}
49+
className="flex justify-between items-center w-full"
50+
>
51+
<span className="font-medium text-base text-black">Select Storm</span>
52+
<Icon
53+
path={mdiChevronUp}
54+
size={1}
55+
className={`text-black transition-transform duration-300 ${
56+
isCollapsed ? "rotate-180" : ""
57+
}`}
58+
/>
59+
</button>
4960

50-
<div className="flex-1 flex flex-col gap-2 overflow-y-auto max-h-[500px] mt-4">
51-
{/* All Storms Option */}
52-
{/* <button
53-
onClick={() => handleStormClick(null)}
54-
className={`p-4 rounded-lg text-left transition-colors ${selectedStormId === null
55-
? "bg-orange-500 text-white"
56-
: "bg-white/50 hover:bg-white/80 text-black"
61+
{!isCollapsed && (
62+
<div className="flex-1 flex flex-col gap-2 overflow-y-auto max-h-[400px] md:max-h-[300px] mt-2">
63+
{storms.map((storm) => (
64+
<button
65+
key={storm.storm_id}
66+
onClick={() => handleStormClick(storm.storm_id)}
67+
className={`p-3 rounded-lg text-left transition-colors ${
68+
selectedStormId === storm.storm_id
69+
? "bg-orange-500 text-white"
70+
: "bg-white/50 hover:bg-white/80 text-black"
5771
}`}
5872
>
59-
<div className="flex items-center gap-2 mb-2">
60-
<Icon path={mdiWeatherHurricaneOutline} size={0.8} />
61-
<h3 className="text-lg font-semibold">All Storms</h3>
73+
<div className="flex items-center gap-2 mb-1">
74+
<Icon path={mdiWeatherHurricaneOutline} size={0.7} />
75+
<h3 className="text-sm font-semibold">{storm.storm_name}</h3>
6276
</div>
63-
<p className="text-sm opacity-80">Display all available storms</p>
64-
</button> */}
65-
66-
{/* Individual Storms */}
67-
{storms.map(storm => (
68-
<button
69-
key={storm.storm_id}
70-
onClick={() => handleStormClick(storm.storm_id)}
71-
className={`p-4 rounded-lg text-left transition-colors ${selectedStormId === storm.storm_id
72-
? "bg-orange-500 text-white"
73-
: "bg-white/50 hover:bg-white/80 text-black"
74-
}`}
75-
>
76-
<div className="flex items-center gap-2 mb-2">
77-
<Icon path={mdiWeatherHurricaneOutline} size={0.8} />
78-
<h3 className="text-lg font-semibold">{storm.storm_name}</h3>
79-
</div>
80-
<div className="text-sm space-y-1">
81-
<p>
82-
<span className="opacity-80">ID:</span>
83-
{" "}
84-
<span className="font-medium">{storm.storm_id}</span>
85-
</p>
86-
<p>
87-
<span className="opacity-80">Start:</span>
88-
{" "}
89-
<span className="font-medium">
90-
{dayjs(storm.start_date).format("DD/MM/YYYY")}
91-
</span>
92-
</p>
93-
<p>
94-
<span className="opacity-80">End:</span>
95-
{" "}
96-
<span className="font-medium">
97-
{dayjs(storm.end_date).format("DD/MM/YYYY")}
98-
</span>
99-
</p>
100-
</div>
101-
</button>
102-
))}
103-
</div>
77+
<div className="text-xs space-y-0.5 pl-1">
78+
<p>
79+
<span className="opacity-80">Start:</span>{" "}
80+
<span className="font-medium">
81+
{dayjs(storm.start_date).format("DD/MM/YYYY")}
82+
</span>
83+
</p>
84+
<p>
85+
<span className="opacity-80">End:</span>{" "}
86+
<span className="font-medium">
87+
{dayjs(storm.end_date).format("DD/MM/YYYY")}
88+
</span>
89+
</p>
90+
</div>
91+
</button>
92+
))}
10493
</div>
105-
</div>
106-
)}
107-
</>
94+
)}
95+
</div>
96+
</div>
10897
);
10998
}

src/features/weather-map/components/timeline-control.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function TimelineControl() {
4040
};
4141

4242
return (
43-
<div className="flex items-center justify-center gap-4 px-4 h-20 w-full">
43+
<div className="flex items-center justify-center gap-1 px-4 md:gap-4 md:px-4 h-20 w-full">
4444
<CircleButton onClick={handlePreviousDay}>
4545
<Icon path={mdiSkipPrevious} size={1} className="text-black" />
4646
</CircleButton>
@@ -100,6 +100,7 @@ export function TimelineControl() {
100100
disabled={sliderDisabled}
101101
/>
102102
</ConfigProvider>
103+
103104
<CircleButton onClick={handleNextDay}>
104105
<Icon path={mdiSkipNext} size={1} className="text-black" />
105106
</CircleButton>

0 commit comments

Comments
 (0)