fix: hide unavailable by default
This commit is contained in:
parent
ec49fef2f3
commit
47928ce9f2
3 changed files with 144 additions and 23 deletions
|
|
@ -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,6 +216,14 @@ export default React.memo(function DAEListListe() {
|
|||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<AvailabilityToggle
|
||||
showUnavailable={showUnavailable}
|
||||
allCount={allDefibs.length}
|
||||
filteredCount={defibs.length}
|
||||
/>
|
||||
{showEmptyAvailable ? (
|
||||
<EmptyNoAvailable />
|
||||
) : (
|
||||
<FlatList
|
||||
data={defibs}
|
||||
keyExtractor={keyExtractor}
|
||||
|
|
@ -149,6 +233,7 @@ export default React.memo(function DAEListListe() {
|
|||
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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
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([
|
||||
const { nearUserDefibs, loadingNearUser, errorNearUser, showUnavailable } =
|
||||
useDefibsState([
|
||||
"nearUserDefibs",
|
||||
"loadingNearUser",
|
||||
"errorNearUser",
|
||||
"showUnavailable",
|
||||
]);
|
||||
|
||||
const hasLocation =
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue