chore: build

This commit is contained in:
2026-06-16 21:17:54 +02:00
parent 440792a066
commit ec7e59550b
11 changed files with 868 additions and 1 deletions
+24
View File
@@ -993,6 +993,30 @@ export const claimMyReward = async (poolKey: string, productId?: number): Promis
}
};
export const submitLivreurRating = async (
orderId: number,
rating: number,
comment: string,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(`${V1}/orders/${orderId}/rate`, { rating, comment });
return { success: true };
} catch (e: any) {
return { success: false, error: e?.response?.data?.error || "Erreur" };
}
};
export const getOrderRatingStatus = async (
orderId: number,
): Promise<{ rated: boolean; rating?: number; comment?: string }> => {
try {
const { data } = await apiClient.get(`${V1}/orders/${orderId}/rating`);
return data;
} catch {
return { rated: false };
}
};
export const calculateOrderTotal = (order: any): number => {
if (typeof order.total_prix === "number" && order.total_prix > 0) return order.total_prix;
if (typeof order.total === "number" && order.total > 0) return order.total;
@@ -1,5 +1,15 @@
import React, { useState, useEffect, useMemo } from "react";
import { View, Text, ScrollView, Image, StyleSheet } from "react-native";
import {
View,
Text,
ScrollView,
Image,
StyleSheet,
Modal,
TouchableOpacity,
TextInput,
Pressable,
} from "react-native";
import { useRoute } from "@react-navigation/native";
import type { RouteProp } from "@react-navigation/native";
import { Ionicons } from "@expo/vector-icons";
@@ -8,6 +18,8 @@ import {
getProductById,
formatOrderDate,
formatPrice,
submitLivreurRating,
getOrderRatingStatus,
} from "../../api/api";
import type { ClientStackParamList } from "../../navigation/types";
import StatusBadge from "../../components/StatusBadge";
@@ -54,6 +66,11 @@ export default function OrderDetailsScreen() {
const [products, setProducts] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [ratingModal, setRatingModal] = useState(false);
const [selectedStars, setSelectedStars] = useState(0);
const [ratingComment, setRatingComment] = useState("");
const [ratingSubmitting, setRatingSubmitting] = useState(false);
const [ratingDone, setRatingDone] = useState<{ rating: number; comment: string } | null>(null);
useEffect(() => {
(async () => {
@@ -97,6 +114,10 @@ export default function OrderDetailsScreen() {
}),
);
setProducts(enriched);
if (cmd.status === "approved" && cmd.livreur_assign) {
const r = await getOrderRatingStatus(params.orderId);
if (r.rated) setRatingDone({ rating: r.rating!, comment: r.comment ?? "" });
}
} else {
setError("Commande introuvable");
}
@@ -108,6 +129,17 @@ export default function OrderDetailsScreen() {
})();
}, [params.orderId]);
const handleSubmitRating = async () => {
if (selectedStars === 0) return;
setRatingSubmitting(true);
const res = await submitLivreurRating(params.orderId, selectedStars, ratingComment);
setRatingSubmitting(false);
if (res.success) {
setRatingDone({ rating: selectedStars, comment: ratingComment });
setRatingModal(false);
}
};
const styles = useMemo(
() =>
StyleSheet.create({
@@ -296,6 +328,89 @@ export default function OrderDetailsScreen() {
fontSize: fontSize.xxl,
fontWeight: fontWeight.bold,
},
ratingBtn: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: spacing.xs,
backgroundColor: "#f59e0b",
borderRadius: borderRadius.sm,
paddingVertical: spacing.m,
marginTop: spacing.m,
},
ratingBtnText: {
color: "#fff",
fontSize: fontSize.sm,
fontWeight: fontWeight.bold,
},
ratingDoneRow: {
flexDirection: "row",
alignItems: "center",
gap: spacing.xs,
marginTop: spacing.m,
padding: spacing.s,
backgroundColor: "rgba(245,158,11,0.1)",
borderRadius: borderRadius.sm,
},
ratingDoneText: {
color: "#f59e0b",
fontSize: fontSize.sm,
flex: 1,
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.7)",
justifyContent: "flex-end",
},
modalSheet: {
backgroundColor: colors.bgCard,
borderTopLeftRadius: borderRadius.xl,
borderTopRightRadius: borderRadius.xl,
padding: spacing.l,
paddingBottom: spacing.xxxl,
},
modalTitle: {
color: colors.textWhite,
fontSize: fontSize.lg,
fontWeight: fontWeight.bold,
marginBottom: spacing.xs,
},
modalSubtitle: {
color: colors.textMuted,
fontSize: fontSize.sm,
marginBottom: spacing.l,
},
starsRow: {
flexDirection: "row",
gap: spacing.s,
marginBottom: spacing.l,
},
commentInput: {
backgroundColor: colors.bgInput,
borderRadius: borderRadius.sm,
borderWidth: 1,
borderColor: colors.borderLight,
color: colors.textPrimary,
padding: spacing.m,
fontSize: fontSize.sm,
minHeight: 80,
textAlignVertical: "top",
marginBottom: spacing.l,
},
submitBtn: {
backgroundColor: "#f59e0b",
borderRadius: borderRadius.sm,
paddingVertical: spacing.m,
alignItems: "center",
marginBottom: spacing.s,
},
submitBtnText: {
color: "#fff",
fontSize: fontSize.md,
fontWeight: fontWeight.bold,
},
cancelBtn: { alignItems: "center", paddingVertical: spacing.s },
cancelBtnText: { color: colors.textMuted, fontSize: fontSize.sm },
}),
[colors],
);
@@ -454,6 +569,21 @@ export default function OrderDetailsScreen() {
</Text>
</View>
)}
{order.status === "approved" && order.livreur_assign && (
ratingDone ? (
<View style={styles.ratingDoneRow}>
<Ionicons name="star" size={14} color="#f59e0b" />
<Text style={styles.ratingDoneText}>
Noté {ratingDone.rating}/5{ratingDone.comment ? ` · "${ratingDone.comment}"` : ""}
</Text>
</View>
) : (
<TouchableOpacity style={styles.ratingBtn} onPress={() => setRatingModal(true)}>
<Ionicons name="star-outline" size={14} color="#fff" />
<Text style={styles.ratingBtnText}>Noter le livreur</Text>
</TouchableOpacity>
)
)}
{(order.client_prenom || order.first_name) && (
<View style={styles.infoRow}>
<Ionicons
@@ -540,5 +670,48 @@ export default function OrderDetailsScreen() {
</View>
</Card>
</ScrollView>
<Modal visible={ratingModal} transparent animationType="slide" onRequestClose={() => setRatingModal(false)}>
<Pressable style={styles.modalOverlay} onPress={() => setRatingModal(false)}>
<Pressable onPress={() => {}}>
<View style={styles.modalSheet}>
<Text style={styles.modalTitle}>Noter le livreur</Text>
<Text style={styles.modalSubtitle}>{order.livreur_assign}</Text>
<View style={styles.starsRow}>
{[1, 2, 3, 4, 5].map((star) => (
<TouchableOpacity key={star} onPress={() => setSelectedStars(star)}>
<Ionicons
name={star <= selectedStars ? "star" : "star-outline"}
size={36}
color="#f59e0b"
/>
</TouchableOpacity>
))}
</View>
<TextInput
style={styles.commentInput}
placeholder="Commentaire (optionnel)"
placeholderTextColor={colors.textMuted}
value={ratingComment}
onChangeText={setRatingComment}
multiline
maxLength={500}
/>
<TouchableOpacity
style={[styles.submitBtn, (selectedStars === 0 || ratingSubmitting) && { opacity: 0.5 }]}
onPress={handleSubmitRating}
disabled={selectedStars === 0 || ratingSubmitting}
>
<Text style={styles.submitBtnText}>
{ratingSubmitting ? "Envoi..." : "Envoyer ma note"}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.cancelBtn} onPress={() => setRatingModal(false)}>
<Text style={styles.cancelBtnText}>Annuler</Text>
</TouchableOpacity>
</View>
</Pressable>
</Pressable>
</Modal>
);
}