chore: build
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user