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
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@maplibre/maplibre-gl-inspect": "^1.8.2",
"@radix-ui/react-accessible-icon": "^1.1.8",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
Expand Down
2 changes: 2 additions & 0 deletions src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const MapComponent = () => {
);
const settingsPanelOpen = useCommonStore((state) => state.settingsPanelOpen);
const updateSettings = useCommonStore((state) => state.updateSettings);
const setMapReady = useCommonStore((state) => state.setMapReady);
const { profile, style } = useSearch({ from: '/$activeTab' });
const [showInfoPopup, setShowInfoPopup] = useState(false);
const [showContextPopup, setShowContextPopup] = useState(false);
Expand Down Expand Up @@ -684,6 +685,7 @@ export const MapComponent = () => {
{...viewState}
onMove={(evt) => setViewState(evt.viewState)}
onMoveEnd={handleMoveEnd}
onLoad={() => setMapReady(true)}
onClick={handleMapClick}
onDblClick={handleMapDblClick}
onContextMenu={handleMapContextMenu}
Expand Down
51 changes: 51 additions & 0 deletions src/components/route-planner.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ vi.mock('./isochrones/isochrones', () => ({
)),
}));

vi.mock('./tiles/tiles', () => ({
TilesControl: vi.fn(() => (
<div data-testid="mock-tiles-control">Tiles Control</div>
)),
}));

vi.mock('./profile-picker', () => ({
ProfilePicker: vi.fn(({ onProfileChange }) => (
<div data-testid="mock-profile-picker">
Expand Down Expand Up @@ -99,6 +105,7 @@ describe('RoutePlanner', () => {
render(<RoutePlanner />);
expect(screen.getByTestId('directions-tab-button')).toBeInTheDocument();
expect(screen.getByTestId('isochrones-tab-button')).toBeInTheDocument();
expect(screen.getByTestId('tiles-tab-button')).toBeInTheDocument();
});

it('should render close button', () => {
Expand Down Expand Up @@ -183,4 +190,48 @@ describe('RoutePlanner', () => {
expect(screen.getByText(/Last Data Update:/)).toBeInTheDocument();
expect(screen.getByText(/2024-01-15/)).toBeInTheDocument();
});

it('should navigate to tiles tab when tiles tab button is clicked', async () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
render(<RoutePlanner />);

await user.click(screen.getByTestId('tiles-tab-button'));

expect(mockNavigate).toHaveBeenCalledWith({
params: { activeTab: 'tiles' },
});
});

describe('when on tiles tab', () => {
beforeEach(async () => {
const router = await import('@tanstack/react-router');
vi.mocked(router.useParams).mockReturnValue({ activeTab: 'tiles' });
});

it('should display Tiles text on trigger button', () => {
render(<RoutePlanner />);
expect(screen.getByTestId('open-directions-button')).toHaveTextContent(
'Tiles'
);
});

it('should not render ProfilePicker on tiles tab', () => {
render(<RoutePlanner />);
expect(
screen.queryByTestId('mock-profile-picker')
).not.toBeInTheDocument();
});

it('should not render SettingsButton on tiles tab', () => {
render(<RoutePlanner />);
expect(
screen.queryByTestId('mock-settings-button')
).not.toBeInTheDocument();
});

it('should not display last update date on tiles tab', () => {
render(<RoutePlanner />);
expect(screen.queryByText(/Last Data Update:/)).not.toBeInTheDocument();
});
});
});
95 changes: 64 additions & 31 deletions src/components/route-planner.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { lazy, Suspense } from 'react';
import { useQuery } from '@tanstack/react-query';
import { format } from 'date-fns';
import { DirectionsControl } from './directions/directions';
import { IsochronesControl } from './isochrones/isochrones';

const TilesControl = lazy(() =>
import('./tiles/tiles').then((module) => ({ default: module.TilesControl }))
);
import { useCommonStore } from '@/stores/common-store';
import { getValhallaUrl } from '@/utils/valhalla';
import {
Expand All @@ -22,6 +27,21 @@ import type { Profile } from '@/stores/common-store';
import { useDirectionsQuery } from '@/hooks/use-directions-queries';
import { useIsochronesQuery } from '@/hooks/use-isochrones-queries';

const TAB_CONFIG = {
directions: {
title: 'Directions',
description: 'Plan a route between multiple locations',
},
isochrones: {
title: 'Isochrones',
description: 'Calculate reachable areas from a location',
},
tiles: {
title: 'Tiles',
description: 'View and manage map tiles',
},
} as const;

export const RoutePlanner = () => {
const { activeTab } = useParams({ from: '/$activeTab' });
const navigate = useNavigate({ from: '/$activeTab' });
Expand All @@ -33,6 +53,8 @@ export const RoutePlanner = () => {
const loading = useCommonStore((state) => state.loading);
const toggleDirections = useCommonStore((state) => state.toggleDirections);

const tabConfig = TAB_CONFIG[activeTab as keyof typeof TAB_CONFIG];

const {
data: lastUpdate,
isLoading: isLoadingLastUpdate,
Expand Down Expand Up @@ -75,7 +97,7 @@ export const RoutePlanner = () => {
<Sheet open={directionsPanelOpen} modal={false}>
<SheetTrigger className="absolute top-4 left-4 z-10" asChild>
<Button onClick={toggleDirections} data-testid="open-directions-button">
{activeTab === 'directions' ? 'Directions' : 'Isochrones'}
{tabConfig.title}
</Button>
</SheetTrigger>
<Tabs
Expand All @@ -101,6 +123,9 @@ export const RoutePlanner = () => {
>
Isochrones
</TabsTrigger>
<TabsTrigger value="tiles" data-testid="tiles-tab-button">
Tiles
</TabsTrigger>
</TabsList>
<Button
variant="ghost"
Expand All @@ -110,48 +135,56 @@ export const RoutePlanner = () => {
>
<X className="size-4" />
</Button>
<SheetTitle className="sr-only">
{activeTab === 'directions' ? 'Directions' : 'Isochrones'}
</SheetTitle>
<SheetTitle className="sr-only">{tabConfig.title}</SheetTitle>
<SheetDescription className="sr-only">
{activeTab === 'directions'
? 'Plan a route between multiple locations'
: 'Calculate reachable areas from a location'}
{tabConfig.description}
</SheetDescription>
</SheetHeader>

<div className="flex justify-between px-2 mb-1">
<ProfilePicker
loading={loading}
onProfileChange={handleProfileChange}
/>
<SettingsButton />
</div>
{activeTab !== 'tiles' && (
<div className="flex justify-between px-2 mb-1">
<ProfilePicker
loading={loading}
onProfileChange={handleProfileChange}
/>
<SettingsButton />
</div>
)}

<TabsContent value="directions" className="flex flex-col gap-3 px-2">
<DirectionsControl />
</TabsContent>
<TabsContent value="isochrones" className="flex flex-col gap-3 px-2">
<IsochronesControl />
</TabsContent>
<TabsContent
value="tiles"
className="flex flex-col gap-3 px-2 flex-1 overflow-hidden min-h-0"
>
<Suspense fallback={<div>Loading...</div>}>
<TilesControl />
</Suspense>
</TabsContent>

<div className="flex p-2 text-sm">
{isLoadingLastUpdate && (
<span className="text-muted-foreground">
Loading last update...
</span>
)}
{isErrorLastUpdate && (
<span className="text-destructive">
Failed to load last update
</span>
)}
{lastUpdate && (
<span>
Last Data Update: {format(lastUpdate, 'yyyy-MM-dd, HH:mm')}
</span>
)}
</div>
{activeTab !== 'tiles' && (
<div className="flex p-2 text-sm">
{isLoadingLastUpdate && (
<span className="text-muted-foreground">
Loading last update...
</span>
)}
{isErrorLastUpdate && (
<span className="text-destructive">
Failed to load last update
</span>
)}
{lastUpdate && (
<span>
Last Data Update: {format(lastUpdate, 'yyyy-MM-dd, HH:mm')}
</span>
)}
</div>
)}
</SheetContent>
</Tabs>
</Sheet>
Expand Down
Loading