From 59679f30d188b51bbe36a8be3e3bd3d55515afcc Mon Sep 17 00:00:00 2001 From: Kwon DaeGeun Date: Sat, 25 Oct 2025 19:33:04 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=85=94=ED=8B=80=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EC=9C=84=EC=B9=98=20=EC=A0=95=EB=B3=B4=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=ED=8C=A8=EC=B9=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.sample | 3 +- src/App.tsx | 120 ++++++++++++++++++++------------ src/api/bus.ts | 16 +++++ src/components/MapContainer.tsx | 5 +- src/data/bus.ts | 10 +-- src/hooks/useBusSelection.ts | 7 +- src/lib/api.ts | 16 +++++ src/lib/endpoints.ts | 2 +- 8 files changed, 123 insertions(+), 56 deletions(-) create mode 100644 src/api/bus.ts create mode 100644 src/lib/api.ts diff --git a/.env.sample b/.env.sample index 1bd8647..8dd3367 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,2 @@ -VITE_KAKAO_MAP_API_KEY= \ No newline at end of file +VITE_KAKAO_MAP_API_KEY= +VITE_API_URL= \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index ecbb313..4210316 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import { SettingsButton } from "./components/SettingsButton"; const SettingsPanel = lazy(() => import("./components/SettingsPanel")); import { QueryClientProvider } from "@tanstack/react-query"; +import { useBusLocations } from "./api/bus"; import { useBusSelection } from "./hooks/useBusSelection"; import { queryClient } from "./lib/query-client"; @@ -22,7 +23,6 @@ interface DevtoolsProps { initialIsOpen?: boolean; } -// Conditionally load ReactQueryDevtools only in development const ReactQueryDevtools: ComponentType = import.meta.env.DEV ? lazy(() => import("@tanstack/react-query-devtools").then((module) => ({ @@ -61,54 +61,86 @@ function App() { { lat: number; lng: number; name: string } | undefined >(undefined); - const handleBusNumberSelect = useBusSelection(setBubbleStop); - return ( -
- + + {import.meta.env.DEV && } + + ); +} - {showSettings ? ( - - setShowSettings(false)} - /> - - ) : null} - - setBubbleStop(undefined)} - /> - - { - setBubbleStop((prev) => - prev === stop ? undefined : stop - ); - }} - /> -
- {import.meta.env.DEV && ( +interface AppContentProps { + mapId: string; + langId: string; + language: string; + setLanguage: (lang: string) => void; + showSettings: boolean; + toggleSettings: () => void; + bubbleStop: { lat: number; lng: number; name: string } | undefined; + setBubbleStop: React.Dispatch>; +} + +function AppContent({ + mapId, + langId, + language, + setLanguage, + showSettings, + toggleSettings, + bubbleStop, + setBubbleStop, +}: AppContentProps) { + const { data: buses = [] } = useBusLocations(); + const handleBusNumberSelect = useBusSelection(buses, setBubbleStop); + + return ( +
+ + + {showSettings ? ( - + - )} - + ) : null} + + setBubbleStop(undefined)} + /> + + { + setBubbleStop((prev) => + prev === stop ? undefined : stop + ); + }} + /> +
); } diff --git a/src/api/bus.ts b/src/api/bus.ts new file mode 100644 index 0000000..a5c2928 --- /dev/null +++ b/src/api/bus.ts @@ -0,0 +1,16 @@ +import { useQuery } from "@tanstack/react-query"; +import type { Bus } from "../data/bus"; +import { apiGet } from "../lib/api"; +import { API_ENDPOINTS } from "../lib/endpoints"; + +export const useBusLocations = () => { + return useQuery({ + queryKey: ["busLocations"], + queryFn: async () => { + const data = await apiGet(API_ENDPOINTS.BUS.LOCATION); + return Array.isArray(data) ? data : []; + }, + refetchInterval: 20000, + refetchIntervalInBackground: true, + }); +}; diff --git a/src/components/MapContainer.tsx b/src/components/MapContainer.tsx index 1c3962f..940db01 100644 --- a/src/components/MapContainer.tsx +++ b/src/components/MapContainer.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { buses } from "../data/bus"; +import { useBusLocations } from "../api/bus"; import { busStops } from "../data/busStops"; import { useKakaoMap } from "../hooks/useKakaoMap"; import { useMapEventHandlers } from "../hooks/useMapEventHandlers"; @@ -14,8 +14,9 @@ interface MapContainerProps { export const MapContainer = ({ mapId, children }: MapContainerProps) => { const { toast } = useToast(); const map = useKakaoMap({ mapId, toast }); + const { data: buses = [] } = useBusLocations(); - useMapOverlays(map, [...busStops], [...buses]); + useMapOverlays(map, [...busStops], buses); useMapEventHandlers(mapId); return ( diff --git a/src/data/bus.ts b/src/data/bus.ts index 8e83183..37e3cc3 100644 --- a/src/data/bus.ts +++ b/src/data/bus.ts @@ -2,7 +2,7 @@ export interface Bus { shuttleId: string; lat: number; lng: number; - direction: string | null; + direction: boolean | null; } export const buses: ReadonlyArray = [ @@ -16,24 +16,24 @@ export const buses: ReadonlyArray = [ shuttleId: "bus2", lat: 37.323637, lng: 127.120047, - direction: "단국대학교", + direction: true, // 단국대학교 }, { shuttleId: "bus3", lat: 37.323779, lng: 127.117087, - direction: "죽전역", + direction: false, // 죽전역 }, { shuttleId: "bus4", lat: 37.323921, lng: 127.114126, - direction: "단국대학교", + direction: true, // 단국대학교 }, { shuttleId: "bus5", lat: 37.324063, lng: 127.111166, - direction: "죽전역", + direction: false, // 죽전역 }, ]; diff --git a/src/hooks/useBusSelection.ts b/src/hooks/useBusSelection.ts index b70da98..7f38cb2 100644 --- a/src/hooks/useBusSelection.ts +++ b/src/hooks/useBusSelection.ts @@ -1,8 +1,9 @@ import type { Dispatch, SetStateAction } from "react"; -import { buses } from "../data/bus"; +import type { Bus } from "../data/bus"; import { moveToLocation } from "./useMapMovement"; export const useBusSelection = ( + buses: Bus[], setBubbleStop: Dispatch< SetStateAction<{ lat: number; lng: number; name: string } | undefined> > @@ -14,8 +15,8 @@ export const useBusSelection = ( if (bus && Number.isFinite(bus.lat) && Number.isFinite(bus.lng)) { moveToLocation(bus.lat, bus.lng); try { - const dir = bus.direction?.trim() ?? ""; - const label = dir ? `셔틀버스(${dir} 방향)` : "셔틀버스"; + 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/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..1205d73 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,16 @@ +import ky from "ky"; + +const apiClient = ky.create({ + prefixUrl: import.meta.env.VITE_API_BASE_URL || "", + timeout: 10000, +}); + +export const apiGet = async ( + url: string, + params?: P +): Promise => { + const response = await apiClient.get(url, { + searchParams: params as Record, + }); + return response.json(); +}; diff --git a/src/lib/endpoints.ts b/src/lib/endpoints.ts index 5bc1057..742acf7 100644 --- a/src/lib/endpoints.ts +++ b/src/lib/endpoints.ts @@ -1,6 +1,6 @@ export const API_ENDPOINTS = { // 시내 버스 관련 BUS: { - SEARCH: "/bus/search", + LOCATION: "api/shuttle/location", }, } as const; From 79acfc0331135ab548fc80df9dec18a6aad42e49 Mon Sep 17 00:00:00 2001 From: Kwon DaeGeun Date: Sat, 25 Oct 2025 19:37:15 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 8dd3367..4f01c63 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,2 @@ VITE_KAKAO_MAP_API_KEY= -VITE_API_URL= \ No newline at end of file +VITE_API_BASE_URL= \ No newline at end of file