chore: build
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
import { spacing, fontSize } from "../../theme";
|
||||
import { getMyRatings } from "../../api/api_delivery";
|
||||
import type { LivreurRating } from "../../api/api_delivery";
|
||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||
|
||||
const STAR_COLOR = "#f59e0b";
|
||||
const STAR_EMPTY = "#374151";
|
||||
|
||||
function Stars({ value }: { value: number }) {
|
||||
return (
|
||||
<View style={{ flexDirection: "row", gap: 2 }}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Ionicons
|
||||
key={i}
|
||||
name={i <= value ? "star" : "star-outline"}
|
||||
size={14}
|
||||
color={i <= value ? STAR_COLOR : STAR_EMPTY}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export default function RatingsScreen() {
|
||||
const { colors } = useTheme();
|
||||
const [ratings, setRatings] = useState<LivreurRating[]>([]);
|
||||
const [average, setAverage] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const load = useCallback(async (silent = false) => {
|
||||
if (!silent) setLoading(true);
|
||||
const res = await getMyRatings();
|
||||
if (res.success) {
|
||||
setRatings(res.ratings);
|
||||
setAverage(res.average);
|
||||
}
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}, []);
|
||||
|
||||
useFocusEffect(useCallback(() => { load(); }, [load]));
|
||||
|
||||
const onRefresh = () => { setRefreshing(true); load(true); };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bgPrimary },
|
||||
content: { padding: spacing.l, paddingBottom: spacing.xxxl },
|
||||
headerCard: {
|
||||
backgroundColor: colors.bgCard,
|
||||
borderRadius: 14,
|
||||
padding: spacing.l,
|
||||
marginBottom: spacing.l,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderLight,
|
||||
},
|
||||
avgNumber: { fontSize: 48, fontWeight: "800", color: STAR_COLOR, lineHeight: 56 },
|
||||
avgLabel: { fontSize: fontSize.sm, color: colors.textMuted, marginTop: spacing.xs },
|
||||
countLabel: { fontSize: fontSize.xs, color: colors.textMuted, marginTop: 4 },
|
||||
starsRow: { flexDirection: "row", gap: 4, marginTop: spacing.s },
|
||||
card: {
|
||||
backgroundColor: colors.bgCard,
|
||||
borderRadius: 12,
|
||||
padding: spacing.l,
|
||||
marginBottom: spacing.m,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderLight,
|
||||
},
|
||||
cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: spacing.s },
|
||||
client: { fontSize: fontSize.sm, fontWeight: "600", color: colors.textPrimary },
|
||||
date: { fontSize: fontSize.xs, color: colors.textMuted },
|
||||
orderRef: { fontSize: fontSize.xs, color: colors.textMuted, marginBottom: spacing.s },
|
||||
comment: { fontSize: fontSize.sm, color: colors.textSecondary, fontStyle: "italic", marginTop: spacing.s, lineHeight: 20 },
|
||||
emptyWrap: { alignItems: "center", paddingVertical: spacing.xxxl },
|
||||
emptyText: { color: colors.textMuted, fontSize: fontSize.md, marginTop: spacing.m, textAlign: "center" },
|
||||
});
|
||||
|
||||
if (loading) return <LoadingSpinner message="Chargement des avis..." />;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.content}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={STAR_COLOR} />}
|
||||
>
|
||||
{/* Résumé */}
|
||||
<View style={styles.headerCard}>
|
||||
<Text style={styles.avgNumber}>
|
||||
{average > 0 ? average.toFixed(1) : "—"}
|
||||
</Text>
|
||||
<View style={styles.starsRow}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Ionicons
|
||||
key={i}
|
||||
name={i <= Math.round(average) ? "star" : "star-outline"}
|
||||
size={22}
|
||||
color={i <= Math.round(average) ? STAR_COLOR : STAR_EMPTY}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<Text style={styles.avgLabel}>Note moyenne</Text>
|
||||
<Text style={styles.countLabel}>{ratings.length} avis client{ratings.length > 1 ? "s" : ""}</Text>
|
||||
</View>
|
||||
|
||||
{/* Liste */}
|
||||
{ratings.length === 0 ? (
|
||||
<View style={styles.emptyWrap}>
|
||||
<Ionicons name="chatbubble-ellipses-outline" size={48} color={colors.textMuted} />
|
||||
<Text style={styles.emptyText}>Aucun avis reçu pour l'instant</Text>
|
||||
</View>
|
||||
) : (
|
||||
ratings.map((r) => (
|
||||
<View key={r.id} style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.client}>{r.client_username}</Text>
|
||||
<Text style={styles.date}>{formatDate(r.created_at)}</Text>
|
||||
</View>
|
||||
<Text style={styles.orderRef}>Commande #{r.order_id}</Text>
|
||||
<Stars value={r.rating} />
|
||||
{r.comment ? (
|
||||
<Text style={styles.comment}>"{r.comment}"</Text>
|
||||
) : null}
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -89,6 +89,7 @@ export default function StatsScreen() {
|
||||
const [byDay, setByDay] = useState<StatPoint[]>([]);
|
||||
const [byWeek, setByWeek] = useState<StatPoint[]>([]);
|
||||
const [byMonth, setByMonth] = useState<StatPoint[]>([]);
|
||||
const [todayCount, setTodayCount] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [period, setPeriod] = useState<Period>("week");
|
||||
@@ -104,6 +105,7 @@ export default function StatsScreen() {
|
||||
setByDay(statsRes.by_day ?? []);
|
||||
setByWeek(statsRes.by_week ?? []);
|
||||
setByMonth(statsRes.by_month ?? []);
|
||||
setTodayCount(statsRes.today_count ?? 0);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
@@ -205,6 +207,7 @@ export default function StatsScreen() {
|
||||
);
|
||||
|
||||
const summaryCards = [
|
||||
{ label: "Livraisons du jour", value: todayCount.toString(), icon: "today-outline" as const, color: colors.accent },
|
||||
{ label: "Total livraisons", value: total.toString(), icon: "cube-outline" as const, color: colors.accent },
|
||||
{ label: "Complétées", value: completed.toString(), icon: "checkmark-circle-outline" as const, color: colors.success },
|
||||
{ label: "En cours", value: inProgress.toString(), icon: "time-outline" as const, color: colors.warning },
|
||||
|
||||
Reference in New Issue
Block a user