fix: hide unavailable by default

This commit is contained in:
devthejo 2026-03-07 20:55:37 +01:00
parent ec49fef2f3
commit 47928ce9f2
No known key found for this signature in database
GPG key ID: 00CCA7A92B1D5351
3 changed files with 144 additions and 23 deletions

View file

@ -1,11 +1,12 @@
import React, { useCallback } from "react";
import { View, FlatList, StyleSheet } from "react-native";
import { Button } from "react-native-paper";
import { Button, Switch } from "react-native-paper";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import Text from "~/components/Text";
import Loader from "~/components/Loader";
import { useTheme } from "~/theme";
import { defibsActions } from "~/stores";
import useNearbyDefibs from "./useNearbyDefibs";
import DefibRow from "./DefibRow";
@ -89,10 +90,81 @@ function EmptyNoResults() {
const keyExtractor = (item) => item.id;
function EmptyNoAvailable({ showUnavailable }) {
const { colors } = useTheme();
return (
<View style={styles.emptyContainer}>
<MaterialCommunityIcons
name="heart-pulse"
size={56}
color={colors.onSurfaceVariant || colors.grey}
style={styles.emptyIcon}
/>
<Text style={styles.emptyTitle}>Aucun défibrillateur disponible</Text>
<Text
style={[
styles.emptyText,
{ color: colors.onSurfaceVariant || colors.grey },
]}
>
Aucun défibrillateur actuellement ouvert dans un rayon de 10 km. Activez
l'option « Afficher les indisponibles » pour voir tous les
défibrillateurs.
</Text>
</View>
);
}
function AvailabilityToggle({ showUnavailable, allCount, filteredCount }) {
const { colors } = useTheme();
const onToggle = useCallback(() => {
defibsActions.setShowUnavailable(!showUnavailable);
}, [showUnavailable]);
const countLabel =
!showUnavailable && allCount > filteredCount
? ` (${allCount - filteredCount} masqués)`
: "";
return (
<View
style={[
styles.toggleRow,
{ borderBottomColor: colors.outlineVariant || colors.grey },
]}
>
<View style={styles.toggleLabelContainer}>
<MaterialCommunityIcons
name="eye-off-outline"
size={18}
color={colors.onSurfaceVariant || colors.grey}
/>
<Text
style={[
styles.toggleLabel,
{ color: colors.onSurfaceVariant || colors.grey },
]}
>
Afficher les indisponibles{countLabel}
</Text>
</View>
<Switch value={showUnavailable} onValueChange={onToggle} />
</View>
);
}
export default React.memo(function DAEListListe() {
const { colors } = useTheme();
const { defibs, loading, error, noLocation, hasLocation, reload } =
useNearbyDefibs();
const {
defibs,
allDefibs,
loading,
error,
noLocation,
hasLocation,
reload,
showUnavailable,
} = useNearbyDefibs();
const renderItem = useCallback(({ item }) => <DefibRow defib={item} />, []);
@ -102,23 +174,27 @@ export default React.memo(function DAEListListe() {
}
// Loading initial data
if (loading && defibs.length === 0) {
if (loading && allDefibs.length === 0) {
return <Loader />;
}
// Error state (non-blocking if we have stale data)
if (error && defibs.length === 0) {
if (error && allDefibs.length === 0) {
return <EmptyError error={error} onRetry={reload} />;
}
// No results
if (!loading && defibs.length === 0 && hasLocation) {
// No results at all
if (!loading && allDefibs.length === 0 && hasLocation) {
return <EmptyNoResults />;
}
// Has defibs but none available (filtered to empty)
const showEmptyAvailable =
!loading && defibs.length === 0 && allDefibs.length > 0 && !showUnavailable;
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
{error && defibs.length > 0 && (
{error && allDefibs.length > 0 && (
<View
style={[
styles.errorBanner,
@ -140,15 +216,24 @@ export default React.memo(function DAEListListe() {
</Text>
</View>
)}
<FlatList
data={defibs}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={styles.list}
initialNumToRender={15}
maxToRenderPerBatch={10}
windowSize={5}
<AvailabilityToggle
showUnavailable={showUnavailable}
allCount={allDefibs.length}
filteredCount={defibs.length}
/>
{showEmptyAvailable ? (
<EmptyNoAvailable />
) : (
<FlatList
data={defibs}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={styles.list}
initialNumToRender={15}
maxToRenderPerBatch={10}
windowSize={5}
/>
)}
</View>
);
});
@ -194,4 +279,21 @@ const styles = StyleSheet.create({
fontSize: 12,
flex: 1,
},
toggleRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
paddingVertical: 8,
borderBottomWidth: StyleSheet.hairlineWidth,
},
toggleLabelContainer: {
flexDirection: "row",
alignItems: "center",
gap: 8,
flex: 1,
},
toggleLabel: {
fontSize: 13,
},
});

View file

@ -1,20 +1,24 @@
import { useEffect, useRef, useCallback, useState } from "react";
import { useEffect, useRef, useCallback, useMemo, useState } from "react";
import useLocation from "~/hooks/useLocation";
import { defibsActions, useDefibsState } from "~/stores";
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
const RADIUS_METERS = 10_000;
/**
* Shared hook: loads defibs near user and exposes location + loading state.
* The results live in the zustand store so both Liste and Carte tabs share them.
* By default, only available (open) defibs are returned; toggle showUnavailable to see all.
*/
export default function useNearbyDefibs() {
const { coords, isLastKnown, lastKnownTimestamp } = useLocation();
const { nearUserDefibs, loadingNearUser, errorNearUser } = useDefibsState([
"nearUserDefibs",
"loadingNearUser",
"errorNearUser",
]);
const { nearUserDefibs, loadingNearUser, errorNearUser, showUnavailable } =
useDefibsState([
"nearUserDefibs",
"loadingNearUser",
"errorNearUser",
"showUnavailable",
]);
const hasLocation =
coords && coords.latitude !== null && coords.longitude !== null;
@ -58,8 +62,17 @@ export default function useNearbyDefibs() {
return () => clearTimeout(timer);
}, [hasLocation]);
const filteredDefibs = useMemo(() => {
if (showUnavailable) return nearUserDefibs;
return nearUserDefibs.filter((d) => {
const { status } = getDefibAvailability(d.horaires_std, d.disponible_24h);
return status === "open";
});
}, [nearUserDefibs, showUnavailable]);
return {
defibs: nearUserDefibs,
defibs: filteredDefibs,
allDefibs: nearUserDefibs,
loading: loadingNearUser,
error: errorNearUser,
hasLocation,
@ -68,5 +81,6 @@ export default function useNearbyDefibs() {
lastKnownTimestamp,
coords,
reload: loadDefibs,
showUnavailable,
};
}

View file

@ -26,6 +26,10 @@ export default createAtom(({ merge, reset }) => {
merge({ showDaeSuggestModal });
},
setShowUnavailable: (showUnavailable) => {
merge({ showUnavailable });
},
loadNearUser: async ({
userLonLat,
radiusMeters = DEFAULT_NEAR_USER_RADIUS_M,
@ -103,6 +107,7 @@ export default createAtom(({ merge, reset }) => {
showDefibsOnAlertMap: false,
selectedDefib: null,
showDaeSuggestModal: false,
showUnavailable: false,
loadingNearUser: false,
loadingCorridor: false,