{
- setBubbleStop((prev) =>
- prev === stop ? undefined : stop
- );
+ setBubbleStop((prev) => (prev === stop ? undefined : stop));
}}
+ busCount={buses.length}
/>
);
diff --git a/src/components/BusStops.tsx b/src/components/BusStops.tsx
index 6c8b20f..1daec2d 100644
--- a/src/components/BusStops.tsx
+++ b/src/components/BusStops.tsx
@@ -1,4 +1,4 @@
-import { Bus, ChevronDown, MapPin } from "lucide-react";
+import { Bus, ChevronDown } from "lucide-react";
import { useEffect, useId, useRef, useState } from "react";
import type { BusStop } from "../data/busStops";
@@ -7,6 +7,7 @@ type Props = {
onSelect: (stop: BusStop) => void;
onBusNumberSelect?: (n: number) => void;
onToggleBubble?: (stop?: BusStop) => void;
+ busCount?: number;
};
export default function BusStops({
@@ -14,6 +15,7 @@ export default function BusStops({
onSelect,
onBusNumberSelect,
onToggleBubble,
+ busCount = 0,
}: Props) {
const [openStops, setOpenStops] = useState(false);
const [openNumbers, setOpenNumbers] = useState(false);
@@ -73,7 +75,25 @@ export default function BusStops({
>
- {[
- { name: "죽전역" },
- { name: "치과병원" },
- { name: "정문" },
- ].map((stop) => (
+ {busStops.map((stop) => (
diff --git a/src/components/BusStopsPanel.tsx b/src/components/BusStopsPanel.tsx
index a1e2f92..d730329 100644
--- a/src/components/BusStopsPanel.tsx
+++ b/src/components/BusStopsPanel.tsx
@@ -6,11 +6,13 @@ import BusStops from "./BusStops";
interface BusStopsPanelProps {
onBusNumberSelect: (n: number) => void;
onToggleBubble: (stop?: BusStop) => void;
+ busCount: number;
}
export const BusStopsPanel = ({
onBusNumberSelect,
onToggleBubble,
+ busCount,
}: BusStopsPanelProps) => {
return (
moveToLocation(stop.lat, stop.lng)}
onBusNumberSelect={onBusNumberSelect}
onToggleBubble={onToggleBubble}
+ busCount={busCount}
/>
);
diff --git a/src/components/MapContainer.tsx b/src/components/MapContainer.tsx
index 940db01..a8a026a 100644
--- a/src/components/MapContainer.tsx
+++ b/src/components/MapContainer.tsx
@@ -9,14 +9,19 @@ import { useToast } from "./ui/use-toast";
interface MapContainerProps {
mapId: string;
children?: ReactNode;
+ selectedStopName?: string;
}
-export const MapContainer = ({ mapId, children }: MapContainerProps) => {
+export const MapContainer = ({
+ mapId,
+ children,
+ selectedStopName,
+}: MapContainerProps) => {
const { toast } = useToast();
const map = useKakaoMap({ mapId, toast });
const { data: buses = [] } = useBusLocations();
- useMapOverlays(map, [...busStops], buses);
+ useMapOverlays(map, [...busStops], buses, selectedStopName);
useMapEventHandlers(mapId);
return (
diff --git a/src/data/busStops.ts b/src/data/busStops.ts
index c9e844c..2c31d70 100644
--- a/src/data/busStops.ts
+++ b/src/data/busStops.ts
@@ -6,9 +6,7 @@ export interface BusStop {
export const busStops: BusStop[] = [
{ name: "평화의광장", lat: 37.320146, lng: 127.12884 },
- { name: "종합실험동", lat: 37.320224, lng: 127.125729 },
{ name: "치과병원", lat: 37.322292, lng: 127.125436 },
{ name: "정문", lat: 37.323352, lng: 127.125968 },
- { name: "상경관", lat: 37.32221, lng: 127.128262 },
{ name: "죽전역", lat: 37.324206, lng: 127.108205 },
];
diff --git a/src/hooks/useBusSelection.ts b/src/hooks/useBusSelection.ts
index 7f38cb2..467d7f3 100644
--- a/src/hooks/useBusSelection.ts
+++ b/src/hooks/useBusSelection.ts
@@ -15,8 +15,15 @@ export const useBusSelection = (
if (bus && Number.isFinite(bus.lat) && Number.isFinite(bus.lng)) {
moveToLocation(bus.lat, bus.lng);
try {
- const direction = bus.direction === true ? "단국대학교" : bus.direction === false ? "죽전역" : "";
- const label = direction ? `셔틀버스(${direction} 방향)` : "셔틀버스";
+ const direction =
+ bus.direction === true
+ ? "단국대학교"
+ : bus.direction === false
+ ? "죽전역"
+ : "";
+ const label = direction
+ ? `셔틀버스(${direction} 방향)`
+ : "셔틀버스";
setBubbleStop({ lat: bus.lat, lng: bus.lng, name: label });
} catch {
/* ignore */
diff --git a/src/hooks/useMapOverlays.ts b/src/hooks/useMapOverlays.ts
index 1bc0ed1..5304d58 100644
--- a/src/hooks/useMapOverlays.ts
+++ b/src/hooks/useMapOverlays.ts
@@ -10,13 +10,14 @@ import {
export const useMapOverlays = (
map: unknown,
busStops: BusStop[],
- buses: Bus[]
+ buses: Bus[],
+ selectedStopName?: string
) => {
useEffect(() => {
if (!map) return;
const overlays: OverlayHandle[] = [
- ...createBusStopOverlays(map, busStops),
+ ...createBusStopOverlays(map, busStops, selectedStopName),
...createBusOverlays(map, buses),
];
@@ -29,5 +30,5 @@ export const useMapOverlays = (
}
});
};
- }, [map, busStops, buses]);
+ }, [map, busStops, buses, selectedStopName]);
};
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 8f6d690..ec61834 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -5,7 +5,9 @@ const apiClient = ky.create({
timeout: 10000,
//credentials: "include",
headers: {
- ...(import.meta.env.VITE_API_KEY ? { "x-api-key": import.meta.env.VITE_API_KEY } : {}),
+ ...(import.meta.env.VITE_API_KEY
+ ? { "x-api-key": import.meta.env.VITE_API_KEY }
+ : {}),
},
});
diff --git a/src/utils/mapOverlays.ts b/src/utils/mapOverlays.ts
index 8c964ed..8a8ec47 100644
--- a/src/utils/mapOverlays.ts
+++ b/src/utils/mapOverlays.ts
@@ -6,26 +6,50 @@ export interface OverlayHandle {
}
// Helper to create Lucide icon as SVG element
-const createIconSVG = (iconType: "mapPin" | "bus") => {
+const createIconSVG = (iconType: "mapPin" | "bus", showCircle = false) => {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("width", "32");
- svg.setAttribute("height", "32");
- svg.setAttribute("viewBox", "0 0 24 24");
+ svg.setAttribute("width", "48");
+ svg.setAttribute("height", "56");
+ svg.setAttribute("viewBox", "0 0 24 40");
svg.setAttribute("fill", "none");
- svg.setAttribute("stroke", "#2563eb"); // text-blue-600
+ svg.setAttribute("stroke", showCircle ? "#dc2626" : "#2563eb"); // red-600 or blue-600
svg.setAttribute("stroke-width", "2.5");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.display = "block";
if (iconType === "mapPin") {
- // MapPin icon path with white fill
- const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
- path1.setAttribute("d", "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0");
- path1.setAttribute("fill", "white");
+ // Semi-transparent circle at the bottom (only for selected stop)
+ if (showCircle) {
+ const bgCircle = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "circle"
+ );
+ bgCircle.setAttribute("cx", "12");
+ bgCircle.setAttribute("cy", "24");
+ bgCircle.setAttribute("r", "14");
+ bgCircle.setAttribute("fill", "#dc2626");
+ bgCircle.setAttribute("fill-opacity", "0.2");
+ bgCircle.setAttribute("stroke", "none");
+ svg.appendChild(bgCircle);
+ }
+
+ // MapPin icon path with blue or red fill
+ const path1 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "path"
+ );
+ path1.setAttribute(
+ "d",
+ "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ );
+ path1.setAttribute("fill", showCircle ? "#dc2626" : "#2563eb");
svg.appendChild(path1);
-
- const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+
+ const circle = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "circle"
+ );
circle.setAttribute("cx", "12");
circle.setAttribute("cy", "10");
circle.setAttribute("r", "3");
@@ -34,7 +58,10 @@ const createIconSVG = (iconType: "mapPin" | "bus") => {
} else {
// Bus icon paths with white fill background
// Background rectangle for white fill
- const bgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ const bgRect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
bgRect.setAttribute("x", "1");
bgRect.setAttribute("y", "5");
bgRect.setAttribute("width", "22");
@@ -43,31 +70,52 @@ const createIconSVG = (iconType: "mapPin" | "bus") => {
bgRect.setAttribute("fill", "white");
bgRect.setAttribute("stroke", "none");
svg.appendChild(bgRect);
-
- const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ const path1 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "path"
+ );
path1.setAttribute("d", "M8 6v6");
svg.appendChild(path1);
-
- const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ const path2 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "path"
+ );
path2.setAttribute("d", "M2 12h19.6");
svg.appendChild(path2);
-
- const path3 = document.createElementNS("http://www.w3.org/2000/svg", "path");
- path3.setAttribute("d", "M18 18h3s.5-1.7.8-2.8c.1-.4.2-.8.2-1.2 0-.4-.1-.8-.2-1.2l-1.4-5C20.1 6.8 19.1 6 18 6H4a2 2 0 0 0-2 2v10h3");
+
+ const path3 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "path"
+ );
+ path3.setAttribute(
+ "d",
+ "M18 18h3s.5-1.7.8-2.8c.1-.4.2-.8.2-1.2 0-.4-.1-.8-.2-1.2l-1.4-5C20.1 6.8 19.1 6 18 6H4a2 2 0 0 0-2 2v10h3"
+ );
svg.appendChild(path3);
-
- const circle1 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+
+ const circle1 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "circle"
+ );
circle1.setAttribute("cx", "7");
circle1.setAttribute("cy", "18");
circle1.setAttribute("r", "2");
circle1.setAttribute("fill", "white");
svg.appendChild(circle1);
-
- const path4 = document.createElementNS("http://www.w3.org/2000/svg", "path");
+
+ const path4 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "path"
+ );
path4.setAttribute("d", "M9 18h5");
svg.appendChild(path4);
-
- const circle2 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+
+ const circle2 = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "circle"
+ );
circle2.setAttribute("cx", "16");
circle2.setAttribute("cy", "18");
circle2.setAttribute("r", "2");
@@ -80,21 +128,24 @@ const createIconSVG = (iconType: "mapPin" | "bus") => {
export const createBusStopOverlays = (
map: unknown,
- busStops: BusStop[]
+ busStops: BusStop[],
+ selectedStopName?: string
): OverlayHandle[] => {
if (!map || typeof window === "undefined" || !window.kakao?.maps) return [];
return busStops.map((stop) => {
+ const isSelected = selectedStopName === stop.name;
+
const busIconDiv = document.createElement("div");
- busIconDiv.style.width = "32px";
- busIconDiv.style.height = "32px";
+ busIconDiv.style.width = "48px";
+ busIconDiv.style.height = "56px";
busIconDiv.style.display = "flex";
busIconDiv.style.alignItems = "center";
busIconDiv.style.justifyContent = "center";
busIconDiv.setAttribute("role", "img");
busIconDiv.setAttribute("aria-label", `정류장: ${stop.name}`);
-
- const iconSVG = createIconSVG("mapPin");
+
+ const iconSVG = createIconSVG("mapPin", isSelected);
busIconDiv.appendChild(iconSVG);
const markerPosition = new window.kakao.maps.LatLng(stop.lat, stop.lng);