Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ function App() {
bubbleStop={bubbleStop}
setBubbleStop={setBubbleStop}
/>
{import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
{import.meta.env.DEV && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</QueryClientProvider>
);
}
Expand All @@ -86,7 +88,11 @@ interface AppContentProps {
showSettings: boolean;
toggleSettings: () => void;
bubbleStop: { lat: number; lng: number; name: string } | undefined;
setBubbleStop: React.Dispatch<React.SetStateAction<{ lat: number; lng: number; name: string } | undefined>>;
setBubbleStop: React.Dispatch<
React.SetStateAction<
{ lat: number; lng: number; name: string } | undefined
>
>;
}

function AppContent({
Expand Down Expand Up @@ -126,7 +132,7 @@ function AppContent({
/>
</Suspense>
) : null}
<MapContainer mapId={mapId}>
<MapContainer mapId={mapId} selectedStopName={bubbleStop?.name}>
<Bubble
stop={bubbleStop}
onClose={() => setBubbleStop(undefined)}
Expand All @@ -135,10 +141,9 @@ function AppContent({
<BusStopsPanel
onBusNumberSelect={handleBusNumberSelect}
onToggleBubble={(stop) => {
setBubbleStop((prev) =>
prev === stop ? undefined : stop
);
setBubbleStop((prev) => (prev === stop ? undefined : stop));
}}
busCount={buses.length}
/>
</div>
);
Expand Down
75 changes: 45 additions & 30 deletions src/components/BusStops.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -7,13 +7,15 @@ type Props = {
onSelect: (stop: BusStop) => void;
onBusNumberSelect?: (n: number) => void;
onToggleBubble?: (stop?: BusStop) => void;
busCount?: number;
};

export default function BusStops({
busStops,
onSelect,
onBusNumberSelect,
onToggleBubble,
busCount = 0,
}: Props) {
const [openStops, setOpenStops] = useState(false);
const [openNumbers, setOpenNumbers] = useState(false);
Expand Down Expand Up @@ -73,7 +75,25 @@ export default function BusStops({
>
<div className="inline-flex items-center gap-3">
<div className="flex-shrink-0 transition-transform duration-200 group-hover:scale-110">
<MapPin className="h-8 w-8 text-blue-600" strokeWidth={2.5} />
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="#2563eb"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
role="img"
aria-label="정류장 아이콘"
>
<title>정류장 아이콘</title>
<path
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"
fill="#2563eb"
/>
<circle cx="12" cy="10" r="3" fill="white" />
</svg>
</div>
<span
className={`font-semibold text-gray-800 text-lg transition-opacity duration-200 ${
Expand All @@ -93,31 +113,21 @@ export default function BusStops({
<section
id={listId}
aria-label="버스 정류장 목록"
className={`grid w-full origin-top grid-cols-3 gap-3 overflow-hidden transition-[opacity,transform,max-height,margin] duration-300 ease-in-out ${
className={`grid w-full origin-top grid-cols-2 gap-3 overflow-hidden transition-[opacity,transform,max-height,margin] duration-300 ease-in-out ${
openStops
? "mt-2 max-h-[640px] scale-y-100 opacity-100"
: "max-h-0 scale-y-0 opacity-0"
}`}
>
{[
{ name: "죽전역" },
{ name: "치과병원" },
{ name: "정문" },
].map((stop) => (
{busStops.map((stop) => (
<button
key={stop.name}
type="button"
disabled={disabled}
onClick={() =>
handleClick(() => {
const realStop = busStops.find(
(s) => s.name === stop.name
);
if (realStop) {
onSelect(realStop);
if (onToggleBubble)
onToggleBubble(realStop);
}
onSelect(stop);
if (onToggleBubble) onToggleBubble(stop);
})
}
className="hover:-translate-y-0.5 min-h-[56px] cursor-pointer rounded-xl border-0 bg-blue-600 px-4 py-4 font-bold text-base text-white transition-all duration-200 hover:bg-blue-700 hover:shadow-lg active:translate-y-0 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:translate-y-0 disabled:hover:shadow-none"
Expand Down Expand Up @@ -146,7 +156,10 @@ export default function BusStops({
>
<div className="inline-flex items-center gap-3">
<div className="flex-shrink-0 transition-transform duration-200 group-hover:scale-110">
<Bus className="h-8 w-8 text-blue-600" strokeWidth={2.5} />
<Bus
className="h-8 w-8 text-blue-600"
strokeWidth={2.5}
/>
</div>
<span
className={`font-semibold text-gray-800 text-lg transition-opacity duration-200 ${
Expand All @@ -172,19 +185,21 @@ export default function BusStops({
: "hidden scale-y-0 opacity-0"
}`}
>
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
type="button"
disabled={disabled}
onClick={() =>
handleClick(() => handleNumberClick(n))
}
className="hover:-translate-y-0.5 min-h-[56px] cursor-pointer rounded-xl border-0 bg-blue-600 px-4 py-4 font-bold text-base text-white transition-all duration-200 hover:bg-blue-700 hover:shadow-lg active:translate-y-0 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:translate-y-0 disabled:hover:shadow-none"
>
{n}
</button>
))}
{Array.from({ length: busCount }, (_, i) => i + 1).map(
(n) => (
<button
key={n}
type="button"
disabled={disabled}
onClick={() =>
handleClick(() => handleNumberClick(n))
}
className="hover:-translate-y-0.5 min-h-[56px] cursor-pointer rounded-xl border-0 bg-blue-600 px-4 py-4 font-bold text-base text-white transition-all duration-200 hover:bg-blue-700 hover:shadow-lg active:translate-y-0 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:translate-y-0 disabled:hover:shadow-none"
>
{n}
</button>
)
)}
</section>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/components/BusStopsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div
Expand All @@ -28,6 +30,7 @@ export const BusStopsPanel = ({
onSelect={(stop) => moveToLocation(stop.lat, stop.lng)}
onBusNumberSelect={onBusNumberSelect}
onToggleBubble={onToggleBubble}
busCount={busCount}
/>
</div>
);
Expand Down
9 changes: 7 additions & 2 deletions src/components/MapContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 0 additions & 2 deletions src/data/busStops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
];
11 changes: 9 additions & 2 deletions src/hooks/useBusSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/useMapOverlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
];

Expand All @@ -29,5 +30,5 @@ export const useMapOverlays = (
}
});
};
}, [map, busStops, buses]);
}, [map, busStops, buses, selectedStopName]);
};
4 changes: 3 additions & 1 deletion src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
: {}),
},
});

Expand Down
Loading
Loading