@navigoo/map-components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +0 -0
  2. package/dist/components/Dashboard.d.ts +14 -0
  3. package/dist/components/Dashboard.js +52 -0
  4. package/dist/components/DetourRouteSearch.d.ts +10 -0
  5. package/dist/components/DetourRouteSearch.js +119 -0
  6. package/dist/components/MapView.d.ts +14 -0
  7. package/dist/components/MapView.js +179 -0
  8. package/dist/components/RouteSearch.d.ts +10 -0
  9. package/dist/components/RouteSearch.js +95 -0
  10. package/dist/components/SearchBar.d.ts +10 -0
  11. package/dist/components/SearchBar.js +48 -0
  12. package/dist/components/TransportOptions.d.ts +3 -0
  13. package/dist/components/TransportOptions.js +24 -0
  14. package/dist/components/TripType.d.ts +3 -0
  15. package/dist/components/TripType.js +20 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +39 -0
  18. package/dist/lib/api.d.ts +21 -0
  19. package/dist/lib/api.js +58 -0
  20. package/dist/lib/geolocalisation.d.ts +9 -0
  21. package/dist/lib/geolocalisation.js +30 -0
  22. package/dist/lib/type.d.ts +32 -0
  23. package/dist/lib/type.js +2 -0
  24. package/package.json +31 -0
  25. package/src/components/Dashboard.tsx +183 -0
  26. package/src/components/DetourRouteSearch.tsx +217 -0
  27. package/src/components/MapView.tsx +201 -0
  28. package/src/components/RouteSearch.tsx +163 -0
  29. package/src/components/SearchBar.tsx +87 -0
  30. package/src/components/TransportOptions.tsx +53 -0
  31. package/src/components/TripType.tsx +47 -0
  32. package/src/components/styles.modules.css +207 -0
  33. package/src/components/wellknown.d.ts +1 -0
  34. package/src/css-module.d.ts +4 -0
  35. package/src/index.ts +10 -0
  36. package/src/lib/api.ts +74 -0
  37. package/src/lib/geolocalisation.ts +41 -0
  38. package/src/lib/type.ts +37 -0
package/README.md ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { GeolocationResult, Place, Route } from '../lib/type';
3
+ import { ApiClient } from '../lib/api';
4
+ interface DashboardProps {
5
+ apiClient: ApiClient;
6
+ setUserLocation: (location: GeolocationResult | null) => void;
7
+ setSearchedPlace: (place: Place | null) => void;
8
+ setIsTracking: (tracking: boolean) => void;
9
+ setRoutes: (routes: Route[]) => void;
10
+ setSelectedRouteIndex: (index: number) => void;
11
+ isTracking: boolean;
12
+ }
13
+ declare const Dashboard: React.FC<DashboardProps>;
14
+ export default Dashboard;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const geolocalisation_1 = require("../lib/geolocalisation");
9
+ const SearchBar_1 = __importDefault(require("./SearchBar"));
10
+ const RouteSearch_1 = __importDefault(require("./RouteSearch"));
11
+ const DetourRouteSearch_1 = __importDefault(require("./DetourRouteSearch"));
12
+ const TransportOptions_1 = __importDefault(require("./TransportOptions"));
13
+ const TripType_1 = __importDefault(require("./TripType"));
14
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
15
+ const lucide_react_1 = require("lucide-react");
16
+ const Dashboard = ({ apiClient, setUserLocation, setSearchedPlace, setIsTracking, setRoutes, setSelectedRouteIndex, isTracking, }) => {
17
+ const [activeSection, setActiveSection] = (0, react_1.useState)(null);
18
+ const [geoLoading, setGeoLoading] = (0, react_1.useState)(false);
19
+ const [geoError, setGeoError] = (0, react_1.useState)(null);
20
+ const toggleSection = (section) => {
21
+ setActiveSection(activeSection === section ? null : section);
22
+ };
23
+ const handleToggleTracking = () => {
24
+ setIsTracking(!isTracking);
25
+ if (isTracking) {
26
+ setUserLocation(null);
27
+ setRoutes([]);
28
+ setSelectedRouteIndex(0);
29
+ }
30
+ };
31
+ const handleGeolocation = (0, react_1.useCallback)(async () => {
32
+ if (geoLoading || isTracking)
33
+ return;
34
+ setGeoLoading(true);
35
+ setGeoError(null);
36
+ try {
37
+ const position = await (0, geolocalisation_1.getCurrentPosition)();
38
+ setUserLocation(position);
39
+ setGeoError(null);
40
+ setRoutes([]);
41
+ setSelectedRouteIndex(0);
42
+ }
43
+ catch {
44
+ setGeoError('Impossible d\'obtenir votre position');
45
+ }
46
+ finally {
47
+ setGeoLoading(false);
48
+ }
49
+ }, [geoLoading, isTracking, setUserLocation, setRoutes, setSelectedRouteIndex]);
50
+ return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.header, children: (0, jsx_runtime_1.jsxs)("h1", { className: styles_module_css_1.default.headerTitle, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.titleAccent, children: "Navi" }), "goo"] }) }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.mainContainer, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.sidebar, children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => toggleSection('geolocation'), className: `${styles_module_css_1.default.sidebarButton} ${activeSection === 'geolocation' ? styles_module_css_1.default.active : ''}`, title: "G\u00E9olocalisation et suivi", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Footprints, { size: 24, color: "#000" }) }), (0, jsx_runtime_1.jsx)("button", { onClick: () => toggleSection('search'), className: `${styles_module_css_1.default.sidebarButton} ${activeSection === 'search' ? styles_module_css_1.default.active : ''}`, title: "Recherche de lieu", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 24, color: "#000" }) }), (0, jsx_runtime_1.jsx)("button", { onClick: () => toggleSection('route'), className: `${styles_module_css_1.default.sidebarButton} ${activeSection === 'route' ? styles_module_css_1.default.active : ''}`, title: "Trac\u00E9 d'itin\u00E9raire", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { size: 24, color: "#000" }) }), (0, jsx_runtime_1.jsx)("button", { onClick: () => toggleSection('routeWithDetour'), className: `${styles_module_css_1.default.sidebarButton} ${activeSection === 'routeWithDetour' ? styles_module_css_1.default.active : ''}`, title: "Trac\u00E9 d'itin\u00E9raire avec d\u00E9tour", children: (0, jsx_runtime_1.jsx)(lucide_react_1.RefreshCcw, { size: 24, color: "#000" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${styles_module_css_1.default.contentPanel} ${activeSection ? styles_module_css_1.default.open : ''}`, children: [activeSection === 'geolocation' && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.sectionContainer, children: [(0, jsx_runtime_1.jsxs)("h2", { className: styles_module_css_1.default.sectionTitle, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.titleAccent, children: "G\u00E9o" }), "localisation"] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.toggleContainer, children: [(0, jsx_runtime_1.jsxs)("label", { className: styles_module_css_1.default.switch, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: isTracking, onChange: handleToggleTracking }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.slider })] }), (0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.toggleLabel, children: ["Suivi: ", isTracking ? 'Activé' : 'Désactivé'] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: handleGeolocation, className: styles_module_css_1.default.locationButton, disabled: geoLoading || isTracking, children: geoLoading ? ('Chargement...') : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.MapPin, { size: 18, color: "#000", style: { marginRight: '8px' } }), "Ma position"] })) }), geoError && (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.error, children: geoError })] })), activeSection === 'search' && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.sectionContainer, children: [(0, jsx_runtime_1.jsxs)("h2", { className: styles_module_css_1.default.sectionTitle, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.titleAccent, children: "Re" }), "cherche"] }), (0, jsx_runtime_1.jsx)(SearchBar_1.default, { apiClient: apiClient, setUserLocation: setUserLocation, setSearchedPlace: setSearchedPlace })] })), activeSection === 'route' && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.sectionContainer, children: [(0, jsx_runtime_1.jsxs)("h2", { className: styles_module_css_1.default.sectionTitle, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.titleAccent, children: "Itin\u00E9" }), "raire"] }), (0, jsx_runtime_1.jsx)(RouteSearch_1.default, { apiClient: apiClient, setRoutes: setRoutes, setSelectedRouteIndex: setSelectedRouteIndex }), (0, jsx_runtime_1.jsx)(TransportOptions_1.default, {}), (0, jsx_runtime_1.jsx)(TripType_1.default, {})] })), activeSection === 'routeWithDetour' && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.sectionContainer, children: [(0, jsx_runtime_1.jsxs)("h2", { className: styles_module_css_1.default.sectionTitle, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.titleAccent, children: "Itin\u00E9" }), "raire avec d\u00E9tour"] }), (0, jsx_runtime_1.jsx)(DetourRouteSearch_1.default, { apiClient: apiClient, setRoutes: setRoutes, setSelectedRouteIndex: setSelectedRouteIndex }), (0, jsx_runtime_1.jsx)(TransportOptions_1.default, {}), (0, jsx_runtime_1.jsx)(TripType_1.default, {})] }))] })] })] }));
51
+ };
52
+ exports.default = Dashboard;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Route } from '../lib/type';
3
+ import { ApiClient } from '../lib/api';
4
+ interface DetourRouteSearchProps {
5
+ apiClient: ApiClient;
6
+ setRoutes: (routes: Route[]) => void;
7
+ setSelectedRouteIndex: (index: number) => void;
8
+ }
9
+ declare const DetourRouteSearch: React.FC<DetourRouteSearchProps>;
10
+ export default DetourRouteSearch;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
9
+ const DetourRouteSearch = ({ apiClient, setRoutes, setSelectedRouteIndex }) => {
10
+ const [startQuery, setStartQuery] = (0, react_1.useState)('');
11
+ const [detourQuery, setDetourQuery] = (0, react_1.useState)('');
12
+ const [endQuery, setEndQuery] = (0, react_1.useState)('');
13
+ const [startResults, setStartResults] = (0, react_1.useState)([]);
14
+ const [detourResults, setDetourResults] = (0, react_1.useState)([]);
15
+ const [endResults, setEndResults] = (0, react_1.useState)([]);
16
+ const [selectedStart, setSelectedStart] = (0, react_1.useState)(null);
17
+ const [selectedDetour, setSelectedDetour] = (0, react_1.useState)(null);
18
+ const [selectedEnd, setSelectedEnd] = (0, react_1.useState)(null);
19
+ const [loading, setLoading] = (0, react_1.useState)(false);
20
+ const [error, setError] = (0, react_1.useState)(null);
21
+ const [transportMode, setTransportMode] = (0, react_1.useState)('driving');
22
+ (0, react_1.useEffect)(() => {
23
+ const handleTransportChange = (e) => {
24
+ const customEvent = e;
25
+ if (customEvent.detail === 'taxi' || customEvent.detail === 'bus') {
26
+ setTransportMode('driving');
27
+ }
28
+ else if (customEvent.detail === 'moto') {
29
+ setTransportMode('cycling');
30
+ }
31
+ };
32
+ window.addEventListener('transportChange', handleTransportChange);
33
+ return () => window.removeEventListener('transportChange', handleTransportChange);
34
+ }, []);
35
+ const handleSearch = async (query, setResults) => {
36
+ if (!query)
37
+ return;
38
+ setLoading(true);
39
+ setError(null);
40
+ try {
41
+ const places = await apiClient.searchPlaces(query);
42
+ setResults(places);
43
+ if (places.length === 0) {
44
+ setError('Aucun lieu trouvé');
45
+ }
46
+ }
47
+ catch (err) {
48
+ setError('Erreur lors de la recherche');
49
+ }
50
+ finally {
51
+ setLoading(false);
52
+ }
53
+ };
54
+ const handleSelectStart = (place) => {
55
+ setStartQuery(place.name);
56
+ setSelectedStart(place);
57
+ setStartResults([]);
58
+ setRoutes([]);
59
+ setSelectedRouteIndex(0);
60
+ };
61
+ const handleSelectDetour = (place) => {
62
+ setDetourQuery(place.name);
63
+ setSelectedDetour(place);
64
+ setDetourResults([]);
65
+ setRoutes([]);
66
+ setSelectedRouteIndex(0);
67
+ };
68
+ const handleSelectEnd = (place) => {
69
+ setEndQuery(place.name);
70
+ setSelectedEnd(place);
71
+ setEndResults([]);
72
+ setRoutes([]);
73
+ setSelectedRouteIndex(0);
74
+ };
75
+ const handleCalculateRoute = async () => {
76
+ if (!selectedStart ||
77
+ !selectedDetour ||
78
+ !selectedEnd ||
79
+ !selectedStart.coordinates ||
80
+ !selectedDetour.coordinates ||
81
+ !selectedEnd.coordinates) {
82
+ setError('Veuillez sélectionner un départ, un détour et une destination');
83
+ return;
84
+ }
85
+ setLoading(true);
86
+ setError(null);
87
+ try {
88
+ const routes = await apiClient.calculateRouteWithDetour({
89
+ lat: selectedStart.coordinates.lat,
90
+ lng: selectedStart.coordinates.lng,
91
+ }, {
92
+ lat: selectedDetour.coordinates.lat,
93
+ lng: selectedDetour.coordinates.lng,
94
+ }, {
95
+ lat: selectedEnd.coordinates.lat,
96
+ lng: selectedEnd.coordinates.lng,
97
+ }, transportMode, selectedStart.name, selectedDetour.name, selectedEnd.name);
98
+ setRoutes(routes);
99
+ setSelectedRouteIndex(0);
100
+ }
101
+ catch (err) {
102
+ setError('Erreur lors du calcul de l\'itinéraire');
103
+ }
104
+ finally {
105
+ setLoading(false);
106
+ }
107
+ };
108
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.routeSearchSection, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "start", className: styles_module_css_1.default.label, children: "D\u00E9part" }), (0, jsx_runtime_1.jsx)("input", { id: "start", type: "text", value: startQuery, onChange: (e) => {
109
+ setStartQuery(e.target.value);
110
+ handleSearch(e.target.value, setStartResults);
111
+ }, placeholder: "Point de d\u00E9part", className: styles_module_css_1.default.searchInput }), startResults.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: startResults.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelectStart(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "detour", className: styles_module_css_1.default.label, children: "D\u00E9tour" }), (0, jsx_runtime_1.jsx)("input", { id: "detour", type: "text", value: detourQuery, onChange: (e) => {
112
+ setDetourQuery(e.target.value);
113
+ handleSearch(e.target.value, setDetourResults);
114
+ }, placeholder: "Point de d\u00E9tour", className: styles_module_css_1.default.searchInput }), detourResults.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: detourResults.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelectDetour(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "end", className: styles_module_css_1.default.label, children: "Destination" }), (0, jsx_runtime_1.jsx)("input", { id: "end", type: "text", value: endQuery, onChange: (e) => {
115
+ setEndQuery(e.target.value);
116
+ handleSearch(e.target.value, setEndResults);
117
+ }, placeholder: "Point d'arriv\u00E9e", className: styles_module_css_1.default.searchInput }), endResults.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: endResults.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelectEnd(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) }))] }), error && (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.error, children: error }), (0, jsx_runtime_1.jsx)("button", { onClick: handleCalculateRoute, disabled: loading || !selectedStart || !selectedDetour || !selectedEnd, className: `${styles_module_css_1.default.searchButton} ${loading || !selectedStart || !selectedDetour || !selectedEnd ? styles_module_css_1.default.disabled : ''}`, children: loading ? 'Calcul...' : 'Calculer l\'itinéraire' })] }));
118
+ };
119
+ exports.default = DetourRouteSearch;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import 'leaflet/dist/leaflet.css';
3
+ import { Place, Route, GeolocationResult } from '../lib/type';
4
+ import { ApiClient } from '../lib/api';
5
+ interface MapViewProps {
6
+ apiClient: ApiClient;
7
+ userLocation?: GeolocationResult | null;
8
+ searchedPlace?: Place | null;
9
+ routes?: Route[];
10
+ selectedRouteIndex: number;
11
+ setSelectedRouteIndex: (index: number) => void;
12
+ }
13
+ declare const MapView: React.FC<MapViewProps>;
14
+ export default MapView;
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const leaflet_1 = __importDefault(require("leaflet"));
9
+ require("leaflet/dist/leaflet.css");
10
+ const wellknown_1 = require("wellknown");
11
+ const MapView = ({ apiClient, userLocation, searchedPlace, routes, selectedRouteIndex, setSelectedRouteIndex, }) => {
12
+ const mapRef = (0, react_1.useRef)(null);
13
+ const mapContainerRef = (0, react_1.useRef)(null);
14
+ const markerRef = (0, react_1.useRef)(null);
15
+ const routeLayerRef = (0, react_1.useRef)(null);
16
+ const clickMarkerRef = (0, react_1.useRef)(null);
17
+ const routePolylinesRef = (0, react_1.useRef)([]);
18
+ const parseWKTLineString = (wkt) => {
19
+ try {
20
+ const geo = (0, wellknown_1.parse)(wkt);
21
+ if (geo && geo.type === 'LineString' && Array.isArray(geo.coordinates)) {
22
+ return geo.coordinates.map(([lng, lat]) => [lat, lng]);
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.error('wellknown parsing failed:', error);
27
+ }
28
+ const match = wkt.match(/LINESTRING\s*\(([^)]+)\)/);
29
+ if (match) {
30
+ return match[1]
31
+ .split(',')
32
+ .map(coord => {
33
+ const [lng, lat] = coord.trim().split(' ').map(Number);
34
+ return [lat, lng];
35
+ });
36
+ }
37
+ return [];
38
+ };
39
+ (0, react_1.useEffect)(() => {
40
+ if (mapContainerRef.current && !mapRef.current) {
41
+ mapRef.current = leaflet_1.default.map(mapContainerRef.current, {
42
+ center: [3.8480, 11.5021], // Centre par défaut : Yaoundé
43
+ zoom: 12,
44
+ minZoom: 11,
45
+ maxZoom: 16,
46
+ maxBounds: [[3.7, 11.4], [4.0, 11.6]],
47
+ maxBoundsViscosity: 1.0,
48
+ });
49
+ leaflet_1.default.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
50
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
51
+ maxZoom: 16,
52
+ }).addTo(mapRef.current);
53
+ leaflet_1.default.Icon.Default.mergeOptions({
54
+ iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
55
+ iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
56
+ shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
57
+ });
58
+ routeLayerRef.current = leaflet_1.default.layerGroup().addTo(mapRef.current);
59
+ mapRef.current.on('click', async (e) => {
60
+ const { lat, lng } = e.latlng;
61
+ const closestPlace = await apiClient.findClosestPlace(lat, lng);
62
+ const placeName = closestPlace?.name || 'Position sélectionnée';
63
+ if (clickMarkerRef.current) {
64
+ mapRef.current?.removeLayer(clickMarkerRef.current);
65
+ clickMarkerRef.current = null;
66
+ }
67
+ else {
68
+ clickMarkerRef.current = leaflet_1.default.marker([lat, lng])
69
+ .addTo(mapRef.current)
70
+ .bindPopup(`<b>${placeName}</b><br>Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}`)
71
+ .openPopup();
72
+ }
73
+ });
74
+ }
75
+ return () => {
76
+ if (mapRef.current) {
77
+ mapRef.current.remove();
78
+ mapRef.current = null;
79
+ }
80
+ };
81
+ }, [apiClient]);
82
+ (0, react_1.useEffect)(() => {
83
+ if (!mapRef.current)
84
+ return;
85
+ if (routeLayerRef.current)
86
+ routeLayerRef.current.clearLayers();
87
+ if (markerRef.current)
88
+ markerRef.current.remove();
89
+ if (clickMarkerRef.current)
90
+ clickMarkerRef.current.remove();
91
+ routePolylinesRef.current = [];
92
+ const centerOnPoint = async (lat, lng, placeName, zoom = 16) => {
93
+ let displayName = placeName;
94
+ if (placeName === 'Votre position') {
95
+ const closestPlace = await apiClient.findClosestPlace(lat, lng);
96
+ displayName = closestPlace?.name || placeName;
97
+ }
98
+ mapRef.current.setView([lat, lng], zoom, { animate: true });
99
+ markerRef.current = leaflet_1.default.marker([lat, lng])
100
+ .addTo(mapRef.current)
101
+ .bindPopup(`<b>${displayName}</b><br>Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}`)
102
+ .openPopup();
103
+ };
104
+ if (routes && routes.length > 0) {
105
+ let allCoordinates = [];
106
+ routes.forEach((route, index) => {
107
+ const coordinates = [];
108
+ route.steps.forEach((step) => {
109
+ const latLngs = parseWKTLineString(step.geometry);
110
+ if (latLngs.length > 0)
111
+ coordinates.push(...latLngs);
112
+ });
113
+ if (coordinates.length > 0) {
114
+ const color = index === selectedRouteIndex ? 'green' : 'black';
115
+ const weight = index === selectedRouteIndex ? 5 : 3;
116
+ const opacity = index === selectedRouteIndex ? 1.0 : 0.5;
117
+ const polyline = leaflet_1.default.polyline(coordinates, { color, weight, opacity })
118
+ .addTo(routeLayerRef.current)
119
+ .on('click', (e) => {
120
+ leaflet_1.default.DomEvent.stopPropagation(e);
121
+ setSelectedRouteIndex(index);
122
+ routePolylinesRef.current.forEach((pl, i) => {
123
+ pl.setStyle({
124
+ color: i === index ? 'green' : 'black',
125
+ weight: i === index ? 5 : 3,
126
+ opacity: i === index ? 1.0 : 0.5,
127
+ });
128
+ });
129
+ const bounds = polyline.getBounds();
130
+ const center = bounds.getCenter();
131
+ leaflet_1.default.popup()
132
+ .setLatLng(center)
133
+ .setContent(`
134
+ <b>Route ${index + 1}</b><br>
135
+ Distance: ${route.distance.toFixed(2)} m<br>
136
+ Durée: ${(route.duration / 60).toFixed(2)} min<br>
137
+ Départ: ${route.startPlaceName || 'Départ'}<br>
138
+ Destination: ${route.endPlaceName || 'Destination'}
139
+ `)
140
+ .openOn(mapRef.current);
141
+ });
142
+ routePolylinesRef.current.push(polyline);
143
+ allCoordinates = [...allCoordinates, ...coordinates];
144
+ if (index === selectedRouteIndex) {
145
+ const startPoint = coordinates[0];
146
+ const endPoint = coordinates[coordinates.length - 1];
147
+ (async () => {
148
+ let startPlaceName = route.startPlaceName || 'Départ';
149
+ if (route.startPlaceName === 'Votre position') {
150
+ const closestStartPlace = await apiClient.findClosestPlace(startPoint[1], startPoint[0]);
151
+ startPlaceName = closestStartPlace?.name || route.startPlaceName;
152
+ }
153
+ leaflet_1.default.marker(startPoint).addTo(routeLayerRef.current).bindPopup(`
154
+ <b>${startPlaceName}</b><br>Lat: ${startPoint[0].toFixed(6)}<br>Lng: ${startPoint[1].toFixed(6)}
155
+ `);
156
+ leaflet_1.default.marker(endPoint).addTo(routeLayerRef.current).bindPopup(`
157
+ <b>${route.endPlaceName || 'Destination'}</b><br>Lat: ${endPoint[0].toFixed(6)}<br>Lng: ${endPoint[1].toFixed(6)}
158
+ `);
159
+ })();
160
+ }
161
+ }
162
+ });
163
+ if (allCoordinates.length > 0) {
164
+ mapRef.current.fitBounds(leaflet_1.default.latLngBounds(allCoordinates));
165
+ }
166
+ }
167
+ else if (searchedPlace && searchedPlace.coordinates) {
168
+ centerOnPoint(searchedPlace.coordinates.lat, searchedPlace.coordinates.lng, searchedPlace.name);
169
+ }
170
+ else if (userLocation) {
171
+ centerOnPoint(userLocation.latitude, userLocation.longitude, 'Votre position');
172
+ }
173
+ else {
174
+ mapRef.current.setView([3.8480, 11.5021], 12, { animate: true });
175
+ }
176
+ }, [apiClient, userLocation, searchedPlace, routes, selectedRouteIndex, setSelectedRouteIndex]);
177
+ return (0, jsx_runtime_1.jsx)("div", { className: "w-full h-screen", ref: mapContainerRef });
178
+ };
179
+ exports.default = MapView;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Route } from '../lib/type';
3
+ import { ApiClient } from '../lib/api';
4
+ interface RouteSearchProps {
5
+ apiClient: ApiClient;
6
+ setRoutes: (routes: Route[]) => void;
7
+ setSelectedRouteIndex: (index: number) => void;
8
+ }
9
+ declare const RouteSearch: React.FC<RouteSearchProps>;
10
+ export default RouteSearch;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
9
+ const RouteSearch = ({ apiClient, setRoutes, setSelectedRouteIndex }) => {
10
+ const [startQuery, setStartQuery] = (0, react_1.useState)('');
11
+ const [endQuery, setEndQuery] = (0, react_1.useState)('');
12
+ const [startResults, setStartResults] = (0, react_1.useState)([]);
13
+ const [endResults, setEndResults] = (0, react_1.useState)([]);
14
+ const [selectedStart, setSelectedStart] = (0, react_1.useState)(null);
15
+ const [selectedEnd, setSelectedEnd] = (0, react_1.useState)(null);
16
+ const [loading, setLoading] = (0, react_1.useState)(false);
17
+ const [error, setError] = (0, react_1.useState)(null);
18
+ const [transportMode, setTransportMode] = (0, react_1.useState)('driving');
19
+ (0, react_1.useEffect)(() => {
20
+ const handleTransportChange = (e) => {
21
+ const customEvent = e;
22
+ if (customEvent.detail === 'taxi' || customEvent.detail === 'bus') {
23
+ setTransportMode('driving');
24
+ }
25
+ else if (customEvent.detail === 'moto') {
26
+ setTransportMode('cycling');
27
+ }
28
+ };
29
+ window.addEventListener('transportChange', handleTransportChange);
30
+ return () => window.removeEventListener('transportChange', handleTransportChange);
31
+ }, []);
32
+ const handleSearch = async (query, setResults) => {
33
+ if (!query)
34
+ return;
35
+ setLoading(true);
36
+ setError(null);
37
+ try {
38
+ const places = await apiClient.searchPlaces(query);
39
+ setResults(places);
40
+ if (places.length === 0) {
41
+ setError('Aucun lieu trouvé');
42
+ }
43
+ }
44
+ catch (err) {
45
+ setError('Erreur lors de la recherche');
46
+ }
47
+ finally {
48
+ setLoading(false);
49
+ }
50
+ };
51
+ const handleSelectStart = (place) => {
52
+ setStartQuery(place.name);
53
+ setSelectedStart(place);
54
+ setStartResults([]);
55
+ setRoutes([]);
56
+ setSelectedRouteIndex(0);
57
+ };
58
+ const handleSelectEnd = (place) => {
59
+ setEndQuery(place.name);
60
+ setSelectedEnd(place);
61
+ setEndResults([]);
62
+ setRoutes([]);
63
+ setSelectedRouteIndex(0);
64
+ };
65
+ const handleCalculateRoute = async () => {
66
+ if (!selectedStart || !selectedEnd || !selectedStart.coordinates || !selectedEnd.coordinates) {
67
+ setError('Veuillez sélectionner un point de départ et une destination');
68
+ return;
69
+ }
70
+ setLoading(true);
71
+ setError(null);
72
+ try {
73
+ const routes = await apiClient.calculateRoute([
74
+ { lat: selectedStart.coordinates.lat, lng: selectedStart.coordinates.lng },
75
+ { lat: selectedEnd.coordinates.lat, lng: selectedEnd.coordinates.lng },
76
+ ], transportMode, selectedStart.name, selectedEnd.name);
77
+ setRoutes(routes);
78
+ setSelectedRouteIndex(0);
79
+ }
80
+ catch (err) {
81
+ setError('Erreur lors du calcul de l\'itinéraire');
82
+ }
83
+ finally {
84
+ setLoading(false);
85
+ }
86
+ };
87
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.routeSearchSection, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "start", className: styles_module_css_1.default.label, children: "D\u00E9part" }), (0, jsx_runtime_1.jsx)("input", { id: "start", type: "text", value: startQuery, onChange: (e) => {
88
+ setStartQuery(e.target.value);
89
+ handleSearch(e.target.value, setStartResults);
90
+ }, placeholder: "Point de d\u00E9part", className: styles_module_css_1.default.searchInput }), startResults.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: startResults.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelectStart(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "end", className: styles_module_css_1.default.label, children: "Destination" }), (0, jsx_runtime_1.jsx)("input", { id: "end", type: "text", value: endQuery, onChange: (e) => {
91
+ setEndQuery(e.target.value);
92
+ handleSearch(e.target.value, setEndResults);
93
+ }, placeholder: "Point d'arriv\u00E9e", className: styles_module_css_1.default.searchInput }), endResults.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: endResults.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelectEnd(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) }))] }), error && (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.error, children: error }), (0, jsx_runtime_1.jsx)("button", { onClick: handleCalculateRoute, disabled: loading || !selectedStart || !selectedEnd, className: `${styles_module_css_1.default.searchButton} ${loading || !selectedStart || !selectedEnd ? styles_module_css_1.default.disabled : ''}`, children: loading ? 'Calcul...' : 'Calculer l\'itinéraire' })] }));
94
+ };
95
+ exports.default = RouteSearch;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Place, GeolocationResult } from '../lib/type';
3
+ import { ApiClient } from '../lib/api';
4
+ interface SearchBarProps {
5
+ apiClient: ApiClient;
6
+ setUserLocation: (location: GeolocationResult | null) => void;
7
+ setSearchedPlace: (place: Place | null) => void;
8
+ }
9
+ declare const SearchBar: React.FC<SearchBarProps>;
10
+ export default SearchBar;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
9
+ const SearchBar = ({ apiClient, setUserLocation, setSearchedPlace }) => {
10
+ const [query, setQuery] = (0, react_1.useState)('');
11
+ const [loading, setLoading] = (0, react_1.useState)(false);
12
+ const [results, setResults] = (0, react_1.useState)([]);
13
+ const [error, setError] = (0, react_1.useState)(null);
14
+ const handleSearch = async () => {
15
+ if (!query || loading)
16
+ return;
17
+ setLoading(true);
18
+ setError(null);
19
+ setResults([]);
20
+ setSearchedPlace(null);
21
+ try {
22
+ const places = await apiClient.searchPlaces(query);
23
+ setResults(places);
24
+ if (places.length === 0) {
25
+ setError('Aucun lieu trouvé');
26
+ }
27
+ }
28
+ catch (err) {
29
+ setError('Erreur lors de la recherche');
30
+ }
31
+ finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+ const handleSelect = (place) => {
36
+ setQuery(place.name);
37
+ if (place.coordinates) {
38
+ setUserLocation({ latitude: place.coordinates.lat, longitude: place.coordinates.lng });
39
+ setSearchedPlace(place);
40
+ }
41
+ else {
42
+ setError('Lieu sans coordonnées valides');
43
+ }
44
+ setResults([]);
45
+ };
46
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchSection, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.searchGroup, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "search", className: styles_module_css_1.default.label, children: "Nom du lieu" }), (0, jsx_runtime_1.jsx)("input", { id: "search", type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Rechercher un lieu", className: styles_module_css_1.default.searchInput })] }), error && (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.error, children: error }), results.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: styles_module_css_1.default.searchResults, children: results.map((place) => ((0, jsx_runtime_1.jsx)("li", { onClick: () => handleSelect(place), className: styles_module_css_1.default.searchResultItem, children: place.name }, place.id))) })), (0, jsx_runtime_1.jsx)("button", { onClick: handleSearch, disabled: loading || !query, className: `${styles_module_css_1.default.searchButton} ${loading || !query ? styles_module_css_1.default.disabled : ''}`, children: loading ? 'Recherche...' : 'Rechercher' })] }));
47
+ };
48
+ exports.default = SearchBar;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const TransportOptions: React.FC;
3
+ export default TransportOptions;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
9
+ const TransportOptions = () => {
10
+ const [showOptions, setShowOptions] = (0, react_1.useState)(false);
11
+ const [selectedTransport, setSelectedTransport] = (0, react_1.useState)('taxi');
12
+ const transportOptions = [
13
+ { id: 'taxi', name: 'Taxi', icon: '🚕', color: '#3498db' },
14
+ { id: 'bus', name: 'Bus', icon: '🚌', color: '#2980b9' },
15
+ { id: 'moto', name: 'Moto', icon: '🏍️', color: '#1abc9c' },
16
+ ];
17
+ (0, react_1.useEffect)(() => {
18
+ window.dispatchEvent(new CustomEvent('transportChange', { detail: selectedTransport }));
19
+ }, [selectedTransport]);
20
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.optionsSection, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.optionsHeader, children: [(0, jsx_runtime_1.jsx)("h3", { className: styles_module_css_1.default.optionsTitle, children: "Moyen de transport" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowOptions(!showOptions), className: styles_module_css_1.default.toggleButton, children: showOptions ? 'Réduire' : 'Options' })] }), showOptions && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.transportOptions, children: transportOptions.map((option) => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => setSelectedTransport(option.id), className: `${styles_module_css_1.default.transportOption} ${selectedTransport === option.id ? styles_module_css_1.default.selected : ''}`, style: {
21
+ backgroundColor: selectedTransport === option.id ? option.color : undefined,
22
+ }, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.transportIcon, children: option.icon }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.transportName, children: option.name })] }, option.id))) }))] }));
23
+ };
24
+ exports.default = TransportOptions;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const TripType: React.FC;
3
+ export default TripType;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
9
+ const TripType = () => {
10
+ const [showOptions, setShowOptions] = (0, react_1.useState)(false);
11
+ const [selectedTripType, setSelectedTripType] = (0, react_1.useState)('individuel');
12
+ const tripTypeOptions = [
13
+ { id: 'individuel', name: 'Individuel', icon: '👤', color: '#3498db' },
14
+ { id: 'ramassage', name: 'Ramassage', icon: '👥', color: '#9b59b6' },
15
+ ];
16
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.optionsSection, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.optionsHeader, children: [(0, jsx_runtime_1.jsx)("h3", { className: styles_module_css_1.default.optionsTitle, children: "Type de trajet" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setShowOptions(!showOptions), className: styles_module_css_1.default.toggleButton, children: showOptions ? 'Réduire' : 'Options' })] }), showOptions && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.transportOptions, children: tripTypeOptions.map((option) => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => setSelectedTripType(option.id), className: `${styles_module_css_1.default.transportOption} ${selectedTripType === option.id ? styles_module_css_1.default.selected : ''}`, style: {
17
+ backgroundColor: selectedTripType === option.id ? option.color : undefined,
18
+ }, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.transportIcon, children: option.icon }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.transportName, children: option.name })] }, option.id))) }))] }));
19
+ };
20
+ exports.default = TripType;
@@ -0,0 +1,10 @@
1
+ export { default as SearchBar } from './components/SearchBar';
2
+ export { default as RouteSearch } from './components/RouteSearch';
3
+ export { default as DetourRouteSearch } from './components/DetourRouteSearch';
4
+ export { default as MapView } from './components/MapView';
5
+ export { default as Dashboard } from './components/Dashboard';
6
+ export { default as TransportOptions } from './components/TransportOptions';
7
+ export { default as TripType } from './components/TripType';
8
+ export { ApiClient } from './lib/api';
9
+ export { getCurrentPosition } from './lib/geolocalisation';
10
+ export * from './lib/type';