chore: fix map dashboard admin
This commit is contained in:
@@ -14,10 +14,8 @@ import {
|
||||
TouchableOpacity,
|
||||
Modal,
|
||||
StatusBar,
|
||||
Dimensions,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import MapView, { Marker, Polyline, PROVIDER_DEFAULT } from "react-native-maps";
|
||||
import { spacing, fontSize, borderRadius } from "../../theme";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
import {
|
||||
@@ -31,6 +29,12 @@ import { STATUS_LABELS, getStatusColors } from "../../utils/constants";
|
||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||
import Card from "../../components/ui/Card";
|
||||
import Badge from "../../components/ui/Badge";
|
||||
import TomTomMap, {
|
||||
TomTomMapRef,
|
||||
TomTomMarker,
|
||||
TomTomRoute,
|
||||
TomTomDestination,
|
||||
} from "../../components/TomTomMap";
|
||||
|
||||
const MAP_HEIGHT = 280;
|
||||
|
||||
@@ -47,9 +51,9 @@ export default function DeliveryScreen() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// Map
|
||||
const mapRef = useRef<MapView | null>(null);
|
||||
const fullscreenMapRef = useRef<MapView | null>(null);
|
||||
// TomTomMap refs
|
||||
const mapRef = useRef<TomTomMapRef>(null);
|
||||
const fullscreenMapRef = useRef<TomTomMapRef>(null);
|
||||
const [mapFullscreen, setMapFullscreen] = useState(false);
|
||||
|
||||
// Selected livreur route tracking
|
||||
@@ -88,6 +92,44 @@ export default function DeliveryScreen() {
|
||||
(l) => l.location.latitude !== 0 && l.location.longitude !== 0,
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Construire les markers TomTom pour tous les livreurs
|
||||
// --------------------------------------------------
|
||||
const tomtomMarkers: TomTomMarker[] = useMemo(() => {
|
||||
return livreursWithGPS.map((l) => ({
|
||||
id: l.username,
|
||||
latitude: l.location.latitude,
|
||||
longitude: l.location.longitude,
|
||||
color: statusColors[l.status] || colors.textMuted,
|
||||
label: l.username,
|
||||
description: `${STATUS_LABELS[l.status] || l.status}${
|
||||
l.stats.current_command
|
||||
? ` · Cmd #${l.stats.current_command}`
|
||||
: ""
|
||||
}`,
|
||||
isSelected: selectedLivreur?.username === l.username,
|
||||
}));
|
||||
}, [livreursWithGPS, selectedLivreur, statusColors, colors.textMuted]);
|
||||
|
||||
// Route TomTom
|
||||
const tomtomRoute: TomTomRoute | null = useMemo(() => {
|
||||
if (!routeInfo || routeInfo.coordinates.length === 0) return null;
|
||||
return { coordinates: routeInfo.coordinates, color: "#4285F4" };
|
||||
}, [routeInfo]);
|
||||
|
||||
// Destination TomTom
|
||||
const tomtomDestination: TomTomDestination | null = useMemo(() => {
|
||||
if (!destinationCoords) return null;
|
||||
return {
|
||||
latitude: destinationCoords.latitude,
|
||||
longitude: destinationCoords.longitude,
|
||||
color: colors.danger,
|
||||
};
|
||||
}, [destinationCoords, colors.danger]);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Track livreur — calcul de route
|
||||
// --------------------------------------------------
|
||||
const trackLivreur = useCallback(
|
||||
async (livreur: DeliveryPerson) => {
|
||||
setSelectedLivreur(livreur);
|
||||
@@ -121,30 +163,15 @@ export default function DeliveryScreen() {
|
||||
const result = await calculateRoute(origin, dest);
|
||||
if (result) {
|
||||
setRouteInfo(result.route);
|
||||
// Fit la map sur les deux points
|
||||
const ref = mapFullscreen ? fullscreenMapRef : mapRef;
|
||||
if (ref.current) {
|
||||
ref.current.fitToCoordinates(
|
||||
[
|
||||
{
|
||||
latitude: origin.latitude,
|
||||
longitude: origin.longitude,
|
||||
},
|
||||
{
|
||||
latitude: dest.latitude,
|
||||
longitude: dest.longitude,
|
||||
},
|
||||
],
|
||||
{
|
||||
edgePadding: {
|
||||
top: 80,
|
||||
right: 60,
|
||||
bottom: 80,
|
||||
left: 60,
|
||||
},
|
||||
animated: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
ref.current?.fitToCoordinates([
|
||||
{
|
||||
latitude: origin.latitude,
|
||||
longitude: origin.longitude,
|
||||
},
|
||||
{ latitude: dest.latitude, longitude: dest.longitude },
|
||||
]);
|
||||
}
|
||||
} catch {
|
||||
/* silent */
|
||||
@@ -160,21 +187,28 @@ export default function DeliveryScreen() {
|
||||
setDestinationCoords(null);
|
||||
};
|
||||
|
||||
const fitAllMarkers = (ref: React.RefObject<MapView | null>) => {
|
||||
if (ref.current && livreursWithGPS.length > 0) {
|
||||
ref.current.fitToCoordinates(
|
||||
livreursWithGPS.map((l) => ({
|
||||
latitude: l.location.latitude,
|
||||
longitude: l.location.longitude,
|
||||
})),
|
||||
{
|
||||
edgePadding: { top: 60, right: 60, bottom: 60, left: 60 },
|
||||
animated: true,
|
||||
},
|
||||
// --------------------------------------------------
|
||||
// onMarkerPress depuis TomTomMap
|
||||
// --------------------------------------------------
|
||||
const handleMarkerPress = useCallback(
|
||||
(markerId: string) => {
|
||||
const livreur = livreursWithGPS.find(
|
||||
(l) => l.username === markerId,
|
||||
);
|
||||
}
|
||||
};
|
||||
if (livreur) {
|
||||
if (selectedLivreur?.username === markerId) {
|
||||
clearRoute();
|
||||
} else {
|
||||
trackLivreur(livreur);
|
||||
}
|
||||
}
|
||||
},
|
||||
[livreursWithGPS, selectedLivreur, trackLivreur],
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Styles
|
||||
// --------------------------------------------------
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
@@ -208,46 +242,9 @@ export default function DeliveryScreen() {
|
||||
overflow: "hidden",
|
||||
marginBottom: spacing.m,
|
||||
position: "relative",
|
||||
height: MAP_HEIGHT,
|
||||
},
|
||||
map: { width: "100%", height: MAP_HEIGHT },
|
||||
|
||||
markerOuter: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 17,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
markerSelected: {
|
||||
borderWidth: 2,
|
||||
borderColor: colors.accent,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
},
|
||||
markerInner: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
destMarkerOuter: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
backgroundColor: colors.danger + "40",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
destMarkerInner: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
backgroundColor: colors.danger,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
map: { flex: 1 },
|
||||
|
||||
routeOverlay: {
|
||||
position: "absolute",
|
||||
@@ -393,6 +390,7 @@ export default function DeliveryScreen() {
|
||||
marginTop: spacing.xxl,
|
||||
},
|
||||
|
||||
// Fullscreen
|
||||
fullscreenContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.bgPrimary,
|
||||
@@ -472,77 +470,9 @@ export default function DeliveryScreen() {
|
||||
[colors],
|
||||
);
|
||||
|
||||
const renderMapMarkers = () =>
|
||||
livreursWithGPS.map((l) => {
|
||||
const isSelected = selectedLivreur?.username === l.username;
|
||||
const markerColor = statusColors[l.status] || colors.textMuted;
|
||||
return (
|
||||
<Marker
|
||||
key={l.username}
|
||||
coordinate={{
|
||||
latitude: l.location.latitude,
|
||||
longitude: l.location.longitude,
|
||||
}}
|
||||
title={l.username}
|
||||
description={`${STATUS_LABELS[l.status] || l.status}${l.stats.current_command ? ` · Cmd #${l.stats.current_command}` : ""}`}
|
||||
onPress={() => trackLivreur(l)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.markerOuter,
|
||||
{ backgroundColor: markerColor + "40" },
|
||||
isSelected && styles.markerSelected,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.markerInner,
|
||||
{ backgroundColor: markerColor },
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name="bicycle"
|
||||
size={14}
|
||||
color={colors.white}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Marker>
|
||||
);
|
||||
});
|
||||
|
||||
const renderRouteOverlay = () => {
|
||||
if (!destinationCoords) return null;
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
coordinate={{
|
||||
latitude: destinationCoords.latitude,
|
||||
longitude: destinationCoords.longitude,
|
||||
}}
|
||||
title="Destination"
|
||||
>
|
||||
<View style={styles.destMarkerOuter}>
|
||||
<View style={styles.destMarkerInner}>
|
||||
<Ionicons
|
||||
name="flag"
|
||||
size={12}
|
||||
color={colors.white}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Marker>
|
||||
{routeInfo && routeInfo.coordinates.length > 0 && (
|
||||
<Polyline
|
||||
coordinates={routeInfo.coordinates}
|
||||
strokeColor="#4285F4"
|
||||
strokeWidth={4}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// --------------------------------------------------
|
||||
// Render livreur card
|
||||
// --------------------------------------------------
|
||||
const renderLivreur = ({ item }: { item: DeliveryPerson }) => {
|
||||
const isSelected = selectedLivreur?.username === item.username;
|
||||
const hasGPS = item.location.latitude !== 0;
|
||||
@@ -658,27 +588,28 @@ export default function DeliveryScreen() {
|
||||
);
|
||||
};
|
||||
|
||||
// --------------------------------------------------
|
||||
// Header avec TomTomMap
|
||||
// --------------------------------------------------
|
||||
const renderHeader = () => (
|
||||
<View>
|
||||
{livreursWithGPS.length > 0 && (
|
||||
{livreursWithGPS.length > 0 ? (
|
||||
<View style={styles.mapContainer}>
|
||||
<MapView
|
||||
<TomTomMap
|
||||
ref={mapRef}
|
||||
provider={PROVIDER_DEFAULT}
|
||||
style={styles.map}
|
||||
initialRegion={{
|
||||
markers={tomtomMarkers}
|
||||
route={tomtomRoute}
|
||||
destination={tomtomDestination}
|
||||
initialCenter={{
|
||||
latitude: livreursWithGPS[0].location.latitude,
|
||||
longitude: livreursWithGPS[0].location.longitude,
|
||||
latitudeDelta: 0.05,
|
||||
longitudeDelta: 0.05,
|
||||
}}
|
||||
onMapReady={() => fitAllMarkers(mapRef)}
|
||||
showsUserLocation={false}
|
||||
>
|
||||
{renderMapMarkers()}
|
||||
{renderRouteOverlay()}
|
||||
</MapView>
|
||||
initialZoom={12}
|
||||
onMarkerPress={handleMarkerPress}
|
||||
/>
|
||||
|
||||
{/* Overlay info route sélectionnée */}
|
||||
{routeInfo && selectedLivreur && (
|
||||
<View style={styles.routeOverlay}>
|
||||
<Text style={styles.routeOverlayUser}>
|
||||
@@ -720,7 +651,7 @@ export default function DeliveryScreen() {
|
||||
<View style={styles.mapBtns}>
|
||||
<TouchableOpacity
|
||||
style={styles.mapBtn}
|
||||
onPress={() => fitAllMarkers(mapRef)}
|
||||
onPress={() => mapRef.current?.fitAllMarkers()}
|
||||
>
|
||||
<Ionicons
|
||||
name="locate-outline"
|
||||
@@ -740,9 +671,7 @@ export default function DeliveryScreen() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{livreursWithGPS.length === 0 && !loading && (
|
||||
) : !loading ? (
|
||||
<View style={styles.noMapBox}>
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
@@ -753,7 +682,7 @@ export default function DeliveryScreen() {
|
||||
Aucun livreur avec GPS actif
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<Text style={styles.sectionTitle}>
|
||||
Livreurs ({livreurs.length})
|
||||
@@ -765,7 +694,7 @@ export default function DeliveryScreen() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Fullscreen map modal */}
|
||||
{/* ── Modal plein écran TomTomMap ── */}
|
||||
<Modal
|
||||
visible={mapFullscreen}
|
||||
animationType="fade"
|
||||
@@ -775,27 +704,23 @@ export default function DeliveryScreen() {
|
||||
<StatusBar hidden={mapFullscreen} />
|
||||
<View style={styles.fullscreenContainer}>
|
||||
{livreursWithGPS.length > 0 && (
|
||||
<MapView
|
||||
<TomTomMap
|
||||
ref={fullscreenMapRef}
|
||||
provider={PROVIDER_DEFAULT}
|
||||
style={styles.fullscreenMap}
|
||||
initialRegion={{
|
||||
markers={tomtomMarkers}
|
||||
route={tomtomRoute}
|
||||
destination={tomtomDestination}
|
||||
initialCenter={{
|
||||
latitude: livreursWithGPS[0].location.latitude,
|
||||
longitude:
|
||||
livreursWithGPS[0].location.longitude,
|
||||
latitudeDelta: 0.05,
|
||||
longitudeDelta: 0.05,
|
||||
}}
|
||||
onMapReady={() => fitAllMarkers(fullscreenMapRef)}
|
||||
showsUserLocation={false}
|
||||
showsCompass
|
||||
showsScale
|
||||
>
|
||||
{renderMapMarkers()}
|
||||
{renderRouteOverlay()}
|
||||
</MapView>
|
||||
initialZoom={12}
|
||||
onMarkerPress={handleMarkerPress}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Top bar */}
|
||||
<View style={styles.fullscreenTopBar}>
|
||||
<TouchableOpacity
|
||||
style={styles.closeBtn}
|
||||
@@ -812,7 +737,9 @@ export default function DeliveryScreen() {
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.closeBtn}
|
||||
onPress={() => fitAllMarkers(fullscreenMapRef)}
|
||||
onPress={() =>
|
||||
fullscreenMapRef.current?.fitAllMarkers()
|
||||
}
|
||||
>
|
||||
<Ionicons
|
||||
name="locate-outline"
|
||||
@@ -822,6 +749,7 @@ export default function DeliveryScreen() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Route bar plein écran */}
|
||||
{routeInfo && selectedLivreur && (
|
||||
<View style={styles.fullscreenRouteBar}>
|
||||
<View style={{ flex: 1 }}>
|
||||
@@ -848,6 +776,7 @@ export default function DeliveryScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Bottom legend */}
|
||||
<View style={styles.fullscreenBottomBar}>
|
||||
<View style={styles.legendRow}>
|
||||
<View style={styles.legendItem}>
|
||||
@@ -888,7 +817,7 @@ export default function DeliveryScreen() {
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* Summary */}
|
||||
{/* ── Résumé stats ── */}
|
||||
<View style={styles.summaryRow}>
|
||||
<View
|
||||
style={[
|
||||
@@ -919,6 +848,7 @@ export default function DeliveryScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ── Liste livreurs ── */}
|
||||
<FlatList
|
||||
data={livreurs}
|
||||
keyExtractor={(item) => item.username}
|
||||
|
||||
Reference in New Issue
Block a user