Files
SwimBuddy/src/features/history/screens/HistoryScreen.tsx
T

121 lines
4.1 KiB
TypeScript

import { useCallback, useState } from "react";
import { View, Text, Pressable, FlatList, Alert } from "react-native";
import { useTranslation } from "react-i18next";
import { useNavigation } from "@react-navigation/native";
import { useFocusEffect } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import type { HistoryStackParamList } from "@navigation/types";
import { getAllSessions, deleteSessionAndSync } from "@db/operations";
import { formatTime } from "@utils/formatTime";
import { formatDistance } from "@utils/formatDistance";
import { SwipeableRow } from "@components/SwipeableRow";
type SessionRow = {
id: string;
name: string | null;
totalDistance: number | null;
totalDuration: number | null;
calories: number | null;
startedAt: string;
finishedAt: string | null;
};
export function HistoryScreen() {
const { t } = useTranslation("history");
const { t: tc } = useTranslation("common");
const navigation = useNavigation<NativeStackNavigationProp<HistoryStackParamList>>();
const [sessions, setSessions] = useState<SessionRow[]>([]);
const [refreshing, setRefreshing] = useState(false);
const loadSessions = useCallback(async () => {
const data = await getAllSessions();
setSessions(data as SessionRow[]);
}, []);
useFocusEffect(
useCallback(() => {
loadSessions();
}, [loadSessions])
);
const onRefresh = async () => {
setRefreshing(true);
await loadSessions();
setRefreshing(false);
};
const onDelete = (id: string) => {
Alert.alert(tc("alert.deleteWorkout"), tc("alert.areYouSure"), [
{ text: tc("cancel"), style: "cancel" },
{
text: tc("delete"),
style: "destructive",
onPress: async () => {
await deleteSessionAndSync(id);
setSessions((prev) => prev.filter((s) => s.id !== id));
},
},
]);
};
const grouped = sessions.reduce<Record<string, SessionRow[]>>((acc, s) => {
const date = new Date(s.startedAt).toLocaleDateString();
if (!acc[date]) acc[date] = [];
acc[date].push(s);
return acc;
}, {});
const sections = Object.entries(grouped);
return (
<View className="flex-1 bg-white">
<FlatList
data={sections}
keyExtractor={([date]) => date}
contentContainerClassName="px-4 pb-8"
refreshing={refreshing}
onRefresh={onRefresh}
ListEmptyComponent={
<View className="py-12 items-center">
<Text className="text-slate-400 text-lg">{t("noCompletedYet")}</Text>
</View>
}
renderItem={({ item: [date, rows] }) => (
<View className="mb-4">
<Text className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 px-1">
{date}
</Text>
{rows.map((s) => (
<SwipeableRow key={s.id} onDelete={() => onDelete(s.id)} deleteLabel={tc("delete")}>
<Pressable
onPress={() => navigation.navigate("SessionDetail", { sessionId: s.id })}
className="p-4"
>
<View className="flex-row justify-between items-center">
<View>
<Text className="font-semibold text-slate-900">
{s.name || tc("swimSession")}
</Text>
<Text className="text-slate-500 text-sm">
{formatDistance(s.totalDistance ?? 0)}
{s.totalDuration ? ` · ${formatTime(s.totalDuration)}` : ""}
{s.calories ? ` · ${tc("kcal", { count: Math.round(s.calories) })}` : ""}
</Text>
</View>
<Text className="text-slate-400 text-xs">
{new Date(s.startedAt).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</Text>
</View>
</Pressable>
</SwipeableRow>
))}
</View>
)}
/>
</View>
);
}