Skip to content

Commit 62608f7

Browse files
committed
AlertMap using leaflet
1 parent 71ffcfc commit 62608f7

File tree

7 files changed

+133
-14
lines changed

7 files changed

+133
-14
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
"@mui/icons-material": "^7.2.0",
2020
"@mui/material": "^7.2.0",
2121
"@tanstack/react-query": "^5.82.0",
22+
"@types/leaflet": "^1.9.20",
2223
"axios": "^1.10.0",
2324
"i18next": "^25.3.2",
2425
"i18next-browser-languagedetector": "^8.2.0",
26+
"leaflet": "^1.9.4",
2527
"moment-timezone": "^0.6.0",
2628
"react": "^19.1.0",
2729
"react-dom": "^19.1.0",
2830
"react-i18next": "^15.6.0",
31+
"react-leaflet": "^5.0.0",
2932
"react-router-dom": "^7.6.3",
3033
"zod": "^3.25.76"
3134
},

pnpm-lock.yaml

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/site-icon.png

71.6 KB
Loading

src/components/Alerts/AlertDetails/AlertContainer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const AlertContainer = ({ alert, resetAlert }: AlertContainerType) => {
2626

2727
// TODO : fetch detections of the selected sequence
2828

29+
console.log('AlertContainer', alert);
30+
2931
return (
3032
<>
3133
{selectedSequence && (
@@ -42,7 +44,10 @@ export const AlertContainer = ({ alert, resetAlert }: AlertContainerType) => {
4244
<AlertImages sequence={selectedSequence} />
4345
</Grid>
4446
<Grid size={{ xs: 12, md: 3 }}>
45-
<AlertInfos sequence={selectedSequence} />
47+
<AlertInfos
48+
sequence={selectedSequence}
49+
sequences={alert.sequences}
50+
/>
4651
</Grid>
4752
</Grid>
4853
)}

src/components/Alerts/AlertDetails/AlertInfos.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
import SportsEsportsIcon from '@mui/icons-material/SportsEsports';
2-
import {
3-
Box,
4-
Button,
5-
Divider,
6-
Grid,
7-
Skeleton,
8-
Typography,
9-
} from '@mui/material';
2+
import { Button, Divider, Grid, Typography } from '@mui/material';
103
import Paper from '@mui/material/Paper';
114
import { useTheme } from '@mui/material/styles';
125

136
import type { SequenceWithCameraInfoType } from '../../../utils/alerts';
147
import { formatToDateTime } from '../../../utils/dates';
158
import { useTranslationPrefix } from '../../../utils/useTranslationPrefix';
169
import { AlertInfosSection } from './AlertInfosSection';
10+
import AlertMap from './AlertMap';
1711

1812
interface AlertInfosType {
1913
sequence: SequenceWithCameraInfoType;
14+
sequences?: SequenceWithCameraInfoType[];
2015
}
2116

22-
export const AlertInfos = ({ sequence }: AlertInfosType) => {
17+
export const AlertInfos = ({ sequence, sequences }: AlertInfosType) => {
2318
const theme = useTheme();
2419
const { t } = useTranslationPrefix('alerts');
2520

@@ -59,10 +54,7 @@ export const AlertInfos = ({ sequence }: AlertInfosType) => {
5954
{sequence.camera?.lat && `${sequence.camera.lat.toString()}, `}
6055
{sequence.camera?.lon.toString()}
6156
</AlertInfosSection>
62-
<Box width="100%" height={200} bgcolor="white" borderRadius={1}>
63-
{/* One Skeleton in place of the map */}
64-
<Skeleton variant="rectangular" width="100%" height={200} />
65-
</Box>
57+
<AlertMap sequences={sequences ?? []} height={200} />
6658
</Grid>
6759
<Grid container spacing={2} direction="column">
6860
<Button
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'leaflet/dist/leaflet.css';
2+
3+
import L from 'leaflet';
4+
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet';
5+
6+
import siteIcon from '@/assets/site-icon.png';
7+
8+
import type { SequenceWithCameraInfoType } from '../../../utils/alerts';
9+
10+
const customIcon = new L.Icon({
11+
iconUrl: siteIcon,
12+
iconSize: [32, 32],
13+
iconAnchor: [16, 32],
14+
popupAnchor: [0, -32],
15+
});
16+
17+
interface CameraMapProps {
18+
sequences: SequenceWithCameraInfoType[];
19+
height?: number;
20+
}
21+
22+
export default function AlertMap({ sequences, height = 200 }: CameraMapProps) {
23+
const coordinates = sequences.map(
24+
(seq) => [seq.camera?.lat, seq.camera?.lon] as [number, number]
25+
);
26+
27+
return (
28+
<MapContainer
29+
bounds={coordinates}
30+
boundsOptions={{ padding: [20, 20] }}
31+
style={{ height, width: '100%', borderRadius: 4 }}
32+
>
33+
<TileLayer
34+
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
35+
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
36+
/>
37+
{sequences.map((sequence) => (
38+
<Marker
39+
key={sequence.id}
40+
position={
41+
[sequence.camera?.lat, sequence.camera?.lon] as [number, number]
42+
}
43+
icon={customIcon}
44+
>
45+
<Popup>
46+
<div>
47+
<strong>{sequence.camera?.name}</strong>
48+
<br />
49+
Elevation: {sequence.camera?.elevation}m
50+
<br />
51+
Angle of view: {sequence.camera?.angle_of_view}°
52+
<br />
53+
Location: {sequence.camera?.lat.toFixed(6)},{' '}
54+
{sequence.camera?.lon.toFixed(6)}
55+
</div>
56+
</Popup>
57+
</Marker>
58+
))}
59+
</MapContainer>
60+
);
61+
}

vite.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export default defineConfig({
1515
}),
1616
checker({ typescript: true }),
1717
],
18+
resolve: {
19+
alias: {
20+
'@': '/src',
21+
},
22+
},
1823
// https://vitest.dev/config/
1924
test: {
2025
globals: true,

0 commit comments

Comments
 (0)