Compare commits
No commits in common. "113d20efe10ebdfdba6954fe407d8243eaafc34d" and "59ccc13836cc82c16770a7b3ef9317a28b5c220a" have entirely different histories.
113d20efe1
...
59ccc13836
10 changed files with 57 additions and 54 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
|
|
@ -10,7 +10,6 @@ import markerRedDisabled from "~/assets/img/marker-red-disabled.png";
|
||||||
import markerYellowDisabled from "~/assets/img/marker-yellow-disabled.png";
|
import markerYellowDisabled from "~/assets/img/marker-yellow-disabled.png";
|
||||||
import markerGreenDisabled from "~/assets/img/marker-green-disabled.png";
|
import markerGreenDisabled from "~/assets/img/marker-green-disabled.png";
|
||||||
import markerOrigin from "~/assets/img/marker-origin.png";
|
import markerOrigin from "~/assets/img/marker-origin.png";
|
||||||
import markerDae from "~/assets/img/marker-dae.png";
|
|
||||||
|
|
||||||
const images = {
|
const images = {
|
||||||
red: markerRed,
|
red: markerRed,
|
||||||
|
|
@ -21,7 +20,6 @@ const images = {
|
||||||
yellowDisabled: markerYellowDisabled,
|
yellowDisabled: markerYellowDisabled,
|
||||||
greenDisabled: markerGreenDisabled,
|
greenDisabled: markerGreenDisabled,
|
||||||
origin: markerOrigin,
|
origin: markerOrigin,
|
||||||
dae: markerDae,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FeatureImages() {
|
export default function FeatureImages() {
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@ const iconStyle = {
|
||||||
iconSize: 0.5,
|
iconSize: 0.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defibStyle = {
|
const defibCircleStyle = {
|
||||||
iconImage: "dae",
|
circleRadius: 8,
|
||||||
iconSize: 0.5,
|
circleColor: ["get", "defibColor"],
|
||||||
iconAllowOverlap: true,
|
circleStrokeColor: "#FFFFFF",
|
||||||
|
circleStrokeWidth: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = createStyles(({ theme: { colors } }) => ({
|
const useStyles = createStyles(({ theme: { colors } }) => ({
|
||||||
|
|
@ -65,12 +66,12 @@ export default function ShapePoints({ shape, children, ...shapeSourceProps }) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Defibrillators (DAE) – separate layer (non-clustered) */}
|
{/* Defibrillators (DAE) – separate layer (non-clustered) */}
|
||||||
<Maplibre.SymbolLayer
|
<Maplibre.CircleLayer
|
||||||
filter={["==", ["get", "isDefib"], true]}
|
filter={["==", ["get", "isDefib"], true]}
|
||||||
key="points-defib"
|
key="points-defib"
|
||||||
id="points-defib"
|
id="points-defib"
|
||||||
aboveLayerID="points-origin"
|
aboveLayerID="points-origin"
|
||||||
style={defibStyle}
|
style={defibCircleStyle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import {
|
||||||
} from "@expo/vector-icons";
|
} from "@expo/vector-icons";
|
||||||
import { useNavigation, CommonActions } from "@react-navigation/native";
|
import { useNavigation, CommonActions } from "@react-navigation/native";
|
||||||
|
|
||||||
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
|
|
||||||
|
|
||||||
import DrawerContent from "~/navigation/DrawerNav/DrawerContent";
|
import DrawerContent from "~/navigation/DrawerNav/DrawerContent";
|
||||||
import { useDrawerState } from "~/navigation/Context";
|
import { useDrawerState } from "~/navigation/Context";
|
||||||
import getDefaultDrawerWidth from "~/navigation/DrawerNav/getDefaultDrawerWidth";
|
import getDefaultDrawerWidth from "~/navigation/DrawerNav/getDefaultDrawerWidth";
|
||||||
|
|
@ -377,16 +375,11 @@ export default React.memo(function DrawerNav() {
|
||||||
options={{
|
options={{
|
||||||
drawerLabel: "Défibrillateurs",
|
drawerLabel: "Défibrillateurs",
|
||||||
drawerIcon: ({ focused }) => (
|
drawerIcon: ({ focused }) => (
|
||||||
<FontAwesome6
|
<MaterialCommunityIcons
|
||||||
name="heart-circle-bolt"
|
name="heart-pulse"
|
||||||
{...iconProps}
|
{...iconProps}
|
||||||
{...(focused ? iconFocusedProps : {})}
|
{...(focused ? iconFocusedProps : {})}
|
||||||
/>
|
/>
|
||||||
// <MaterialCommunityIcons
|
|
||||||
// name="heart-flash"
|
|
||||||
// {...iconProps}
|
|
||||||
// {...(focused ? iconFocusedProps : {})}
|
|
||||||
// />
|
|
||||||
),
|
),
|
||||||
unmountOnBlur: true,
|
unmountOnBlur: true,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ export default function useFeatures({
|
||||||
id,
|
id,
|
||||||
properties: {
|
properties: {
|
||||||
id,
|
id,
|
||||||
|
defibColor: "#4CAF50",
|
||||||
defib,
|
defib,
|
||||||
isDefib: true,
|
isDefib: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import IconTouchTarget from "~/components/IconTouchTarget";
|
||||||
import { useTheme } from "~/theme";
|
import { useTheme } from "~/theme";
|
||||||
import { useDefibsState, useNetworkState } from "~/stores";
|
import { useDefibsState, useNetworkState } from "~/stores";
|
||||||
import useLocation from "~/hooks/useLocation";
|
import useLocation from "~/hooks/useLocation";
|
||||||
|
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
|
||||||
import {
|
import {
|
||||||
osmProfileUrl,
|
osmProfileUrl,
|
||||||
profileDefaultModes,
|
profileDefaultModes,
|
||||||
|
|
@ -32,7 +33,6 @@ import {
|
||||||
setA11yFocusAfterInteractions,
|
setA11yFocusAfterInteractions,
|
||||||
} from "~/lib/a11y";
|
} from "~/lib/a11y";
|
||||||
|
|
||||||
import markerDae from "~/assets/img/marker-dae.png";
|
|
||||||
import RoutingSteps from "~/scenes/AlertCurMap/RoutingSteps";
|
import RoutingSteps from "~/scenes/AlertCurMap/RoutingSteps";
|
||||||
import MapHeadRouting from "~/scenes/AlertCurMap/MapHeadRouting";
|
import MapHeadRouting from "~/scenes/AlertCurMap/MapHeadRouting";
|
||||||
|
|
||||||
|
|
@ -42,6 +42,11 @@ import {
|
||||||
STATE_CALCULATING_LOADING,
|
STATE_CALCULATING_LOADING,
|
||||||
} from "~/scenes/AlertCurMap/constants";
|
} from "~/scenes/AlertCurMap/constants";
|
||||||
|
|
||||||
|
const STATUS_COLORS = {
|
||||||
|
open: "#4CAF50",
|
||||||
|
closed: "#F44336",
|
||||||
|
unknown: "#9E9E9E",
|
||||||
|
};
|
||||||
|
|
||||||
export default React.memo(function DAEItemCarte() {
|
export default React.memo(function DAEItemCarte() {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
@ -201,6 +206,10 @@ export default React.memo(function DAEItemCarte() {
|
||||||
// Defib marker GeoJSON
|
// Defib marker GeoJSON
|
||||||
const defibGeoJSON = useMemo(() => {
|
const defibGeoJSON = useMemo(() => {
|
||||||
if (!hasDefibCoords) return null;
|
if (!hasDefibCoords) return null;
|
||||||
|
const { status } = getDefibAvailability(
|
||||||
|
defib.horaires_std,
|
||||||
|
defib.disponible_24h,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: [
|
features: [
|
||||||
|
|
@ -213,6 +222,7 @@ export default React.memo(function DAEItemCarte() {
|
||||||
properties: {
|
properties: {
|
||||||
id: defib.id,
|
id: defib.id,
|
||||||
nom: defib.nom || "Défibrillateur",
|
nom: defib.nom || "Défibrillateur",
|
||||||
|
color: STATUS_COLORS[status],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -360,17 +370,22 @@ export default React.memo(function DAEItemCarte() {
|
||||||
</Maplibre.ShapeSource>
|
</Maplibre.ShapeSource>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Maplibre.Images images={{ dae: markerDae }} />
|
|
||||||
|
|
||||||
{/* Defib marker */}
|
{/* Defib marker */}
|
||||||
{defibGeoJSON && (
|
{defibGeoJSON && (
|
||||||
<Maplibre.ShapeSource id="defibItemSource" shape={defibGeoJSON}>
|
<Maplibre.ShapeSource id="defibItemSource" shape={defibGeoJSON}>
|
||||||
<Maplibre.SymbolLayer
|
<Maplibre.CircleLayer
|
||||||
id="defibItemSymbol"
|
id="defibItemCircle"
|
||||||
|
style={{
|
||||||
|
circleRadius: 10,
|
||||||
|
circleColor: ["get", "color"],
|
||||||
|
circleStrokeColor: "#FFFFFF",
|
||||||
|
circleStrokeWidth: 2.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Maplibre.SymbolLayer
|
||||||
|
id="defibItemLabel"
|
||||||
|
aboveLayerID="defibItemCircle"
|
||||||
style={{
|
style={{
|
||||||
iconImage: "dae",
|
|
||||||
iconSize: 0.5,
|
|
||||||
iconAllowOverlap: true,
|
|
||||||
textField: ["get", "nom"],
|
textField: ["get", "nom"],
|
||||||
textSize: 12,
|
textSize: 12,
|
||||||
textOffset: [0, 1.8],
|
textOffset: [0, 1.8],
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,21 @@ import Text from "~/components/Text";
|
||||||
import Loader from "~/components/Loader";
|
import Loader from "~/components/Loader";
|
||||||
import { useTheme } from "~/theme";
|
import { useTheme } from "~/theme";
|
||||||
import { defibsActions } from "~/stores";
|
import { defibsActions } from "~/stores";
|
||||||
|
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
|
||||||
import markerDae from "~/assets/img/marker-dae.png";
|
|
||||||
|
|
||||||
import useNearbyDefibs from "./useNearbyDefibs";
|
import useNearbyDefibs from "./useNearbyDefibs";
|
||||||
|
|
||||||
|
const STATUS_COLORS = {
|
||||||
|
open: "#4CAF50",
|
||||||
|
closed: "#F44336",
|
||||||
|
unknown: "#9E9E9E",
|
||||||
|
};
|
||||||
|
|
||||||
function defibsToGeoJSON(defibs) {
|
function defibsToGeoJSON(defibs) {
|
||||||
return {
|
return {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: defibs.map((d) => {
|
features: defibs.map((d) => {
|
||||||
|
const { status } = getDefibAvailability(d.horaires_std, d.disponible_24h);
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: d.id,
|
id: d.id,
|
||||||
|
|
@ -39,6 +45,8 @@ function defibsToGeoJSON(defibs) {
|
||||||
properties: {
|
properties: {
|
||||||
id: d.id,
|
id: d.id,
|
||||||
nom: d.nom || "Défibrillateur",
|
nom: d.nom || "Défibrillateur",
|
||||||
|
status,
|
||||||
|
color: STATUS_COLORS[status],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
@ -168,19 +176,27 @@ export default React.memo(function DAEListCarte() {
|
||||||
detached={false}
|
detached={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Maplibre.Images images={{ dae: markerDae }} />
|
|
||||||
|
|
||||||
{geoJSON.features.length > 0 && (
|
{geoJSON.features.length > 0 && (
|
||||||
<Maplibre.ShapeSource
|
<Maplibre.ShapeSource
|
||||||
id="defibSource"
|
id="defibSource"
|
||||||
shape={geoJSON}
|
shape={geoJSON}
|
||||||
onPress={onMarkerPress}
|
onPress={onMarkerPress}
|
||||||
>
|
>
|
||||||
|
<Maplibre.CircleLayer
|
||||||
|
id="defibCircleLayer"
|
||||||
|
style={{
|
||||||
|
circleRadius: 8,
|
||||||
|
circleColor: ["get", "color"],
|
||||||
|
circleStrokeColor: "#FFFFFF",
|
||||||
|
circleStrokeWidth: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Maplibre.SymbolLayer
|
<Maplibre.SymbolLayer
|
||||||
id="defibSymbolLayer"
|
id="defibSymbolLayer"
|
||||||
|
aboveLayerID="defibCircleLayer"
|
||||||
style={{
|
style={{
|
||||||
iconImage: "dae",
|
iconImage: "heart-pulse",
|
||||||
iconSize: 0.5,
|
iconSize: 0.6,
|
||||||
iconAllowOverlap: true,
|
iconAllowOverlap: true,
|
||||||
textField: ["get", "nom"],
|
textField: ["get", "nom"],
|
||||||
textSize: 11,
|
textSize: 11,
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ function EmptyError({ error, onRetry }) {
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Impossible de charger les défibrillateurs.{"\n"}
|
Impossible de charger les défibrillateurs.{"\n"}
|
||||||
Veuillez réessayer ultérieurement.
|
{error?.message || "Veuillez réessayer."}
|
||||||
</Text>
|
</Text>
|
||||||
{onRetry && (
|
{onRetry && (
|
||||||
<Button mode="contained" onPress={onRetry} style={styles.retryButton}>
|
<Button mode="contained" onPress={onRetry} style={styles.retryButton}>
|
||||||
|
|
|
||||||
|
|
@ -15,27 +15,6 @@ const DEFAULT_LIMIT = 200;
|
||||||
|
|
||||||
const AUTO_DISMISS_DELAY = 4_000;
|
const AUTO_DISMISS_DELAY = 4_000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a technical DAE update error into a user-friendly French message.
|
|
||||||
* The raw technical details are already logged via console.warn in updateDaeDb.
|
|
||||||
*/
|
|
||||||
function userFriendlyDaeError(error) {
|
|
||||||
const msg = error?.message || "";
|
|
||||||
if (msg.includes("Network") || msg.includes("network")) {
|
|
||||||
return "Impossible de contacter le serveur. Vérifiez votre connexion internet et réessayez.";
|
|
||||||
}
|
|
||||||
if (msg.includes("HTTP")) {
|
|
||||||
return "Le serveur a rencontré un problème. Veuillez réessayer ultérieurement.";
|
|
||||||
}
|
|
||||||
if (msg.includes("Download failed") || msg.includes("file is empty")) {
|
|
||||||
return "Le téléchargement a échoué. Veuillez réessayer.";
|
|
||||||
}
|
|
||||||
if (msg.includes("failed validation")) {
|
|
||||||
return "Le fichier téléchargé est corrompu. Veuillez réessayer.";
|
|
||||||
}
|
|
||||||
return "La mise à jour a échoué. Veuillez réessayer ultérieurement.";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createAtom(({ merge, reset }) => {
|
export default createAtom(({ merge, reset }) => {
|
||||||
const actions = {
|
const actions = {
|
||||||
reset,
|
reset,
|
||||||
|
|
@ -174,7 +153,7 @@ export default createAtom(({ merge, reset }) => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
merge({
|
merge({
|
||||||
daeUpdateState: "error",
|
daeUpdateState: "error",
|
||||||
daeUpdateError: userFriendlyDaeError(result.error),
|
daeUpdateError: result.error?.message || "Erreur inconnue",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue