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
+15
View File
@@ -299,6 +299,21 @@ export const getDeliveryPersonDetails = async (username: string) => {
return data.deliveryman || data;
};
export const getLivreurRatings = async (username: string): Promise<{
ratings: { id: number; order_id: number; client_username: string; rating: number; comment: string; created_at: string }[];
average: number;
count: number;
}> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons/${username}/ratings`,
);
return data;
} catch {
return { ratings: [], average: 0, count: 0 };
}
};
const parseStatus = (status: any): "available" | "busy" | "offline" => {
if (!status) return "offline";
if (status === "available" || status === "busy" || status === "offline")
@@ -15,6 +15,8 @@ import {
Modal,
StatusBar,
useWindowDimensions,
ScrollView,
Pressable,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
@@ -22,6 +24,7 @@ import { useTheme } from "../../context/ThemeContext";
import {
getAllDeliveryPersonsWithDetails,
getCommandByID,
getLivreurRatings,
} from "../../api/api_admin";
import { geocodeAddress, calculateRoute } from "../../api/tomtom";
import type { RouteInfo } from "../../api/tomtom";
@@ -61,6 +64,22 @@ export default function DeliveryScreen() {
const [routeInfo, setRouteInfo] = useState<RouteInfo | null>(null);
const [routeLoading, setRouteLoading] = useState(false);
// Avis livreur
const [ratingsModal, setRatingsModal] = useState<{
username: string;
ratings: { id: number; order_id: number; client_username: string; rating: number; comment: string; created_at: string }[];
average: number;
count: number;
} | null>(null);
const [ratingsLoading, setRatingsLoading] = useState(false);
const openRatings = async (username: string) => {
setRatingsLoading(true);
const data = await getLivreurRatings(username);
setRatingsModal({ username, ...data });
setRatingsLoading(false);
};
const loadData = useCallback(async () => {
try {
const result = await getAllDeliveryPersonsWithDetails();
@@ -338,6 +357,24 @@ export default function DeliveryScreen() {
fontWeight: "500",
},
ratingsBtn: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: spacing.s,
marginTop: spacing.m,
paddingVertical: spacing.s,
paddingHorizontal: spacing.m,
borderRadius: borderRadius.sm,
borderWidth: 1,
borderColor: "#f59e0b",
},
ratingsBtnText: {
color: "#f59e0b",
fontSize: fontSize.sm,
fontWeight: "600",
},
trackBtn: {
flexDirection: "row",
alignItems: "center",
@@ -356,6 +393,80 @@ export default function DeliveryScreen() {
fontWeight: "600",
},
ratingsOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.7)",
justifyContent: "flex-end",
},
ratingsSheet: {
backgroundColor: colors.bgCard,
borderTopLeftRadius: borderRadius.xl,
borderTopRightRadius: borderRadius.xl,
padding: spacing.l,
maxHeight: "80%",
},
ratingsHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: spacing.s,
},
ratingsTitle: {
color: colors.textWhite,
fontSize: fontSize.lg,
fontWeight: "700",
},
ratingsAvg: {
flexDirection: "row",
alignItems: "center",
gap: spacing.xs,
marginBottom: spacing.l,
},
ratingsAvgText: {
color: "#f59e0b",
fontSize: fontSize.md,
fontWeight: "700",
},
ratingsCount: {
color: colors.textMuted,
fontSize: fontSize.sm,
},
ratingItem: {
borderTopWidth: 1,
borderTopColor: colors.borderLight,
paddingVertical: spacing.m,
},
ratingItemHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: spacing.xs,
},
ratingItemClient: {
color: colors.textPrimary,
fontSize: fontSize.sm,
fontWeight: "600",
},
ratingItemDate: {
color: colors.textMuted,
fontSize: fontSize.xs,
},
ratingStarsRow: {
flexDirection: "row",
gap: 2,
marginBottom: spacing.xs,
},
ratingItemComment: {
color: colors.textSecondary,
fontSize: fontSize.sm,
fontStyle: "italic",
},
ratingsEmpty: {
color: colors.textMuted,
textAlign: "center",
paddingVertical: spacing.xl,
},
empty: {
color: colors.textMuted,
textAlign: "center",
@@ -523,6 +634,15 @@ export default function DeliveryScreen() {
</Text>
)}
<TouchableOpacity
style={styles.ratingsBtn}
onPress={() => openRatings(item.username)}
activeOpacity={0.7}
>
<Ionicons name="star-outline" size={14} color="#f59e0b" />
<Text style={styles.ratingsBtnText}>Voir les avis</Text>
</TouchableOpacity>
{hasGPS && (
<TouchableOpacity
style={[
@@ -835,6 +955,78 @@ export default function DeliveryScreen() {
<Text style={styles.empty}>Aucun livreur</Text>
}
/>
{/* ── Modal avis livreur ── */}
<Modal
visible={ratingsModal !== null}
transparent
animationType="slide"
onRequestClose={() => setRatingsModal(null)}
>
<Pressable style={styles.ratingsOverlay} onPress={() => setRatingsModal(null)}>
<Pressable onPress={() => {}}>
<View style={styles.ratingsSheet}>
<View style={styles.ratingsHeader}>
<Text style={styles.ratingsTitle}>
Avis — {ratingsModal?.username}
</Text>
<TouchableOpacity onPress={() => setRatingsModal(null)}>
<Ionicons name="close" size={22} color={colors.textMuted} />
</TouchableOpacity>
</View>
{ratingsLoading ? (
<Text style={styles.ratingsEmpty}>Chargement...</Text>
) : ratingsModal && ratingsModal.count > 0 ? (
<>
<View style={styles.ratingsAvg}>
{[1,2,3,4,5].map((s) => (
<Ionicons
key={s}
name={s <= Math.round(ratingsModal.average) ? "star" : "star-outline"}
size={20}
color="#f59e0b"
/>
))}
<Text style={styles.ratingsAvgText}>
{ratingsModal.average.toFixed(1)}
</Text>
<Text style={styles.ratingsCount}>
({ratingsModal.count} avis)
</Text>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
{ratingsModal.ratings.map((r) => (
<View key={r.id} style={styles.ratingItem}>
<View style={styles.ratingItemHeader}>
<Text style={styles.ratingItemClient}>{r.client_username}</Text>
<Text style={styles.ratingItemDate}>
{new Date(r.created_at).toLocaleDateString("fr-FR")}
</Text>
</View>
<View style={styles.ratingStarsRow}>
{[1,2,3,4,5].map((s) => (
<Ionicons
key={s}
name={s <= r.rating ? "star" : "star-outline"}
size={14}
color="#f59e0b"
/>
))}
</View>
{r.comment !== "" && (
<Text style={styles.ratingItemComment}>"{r.comment}"</Text>
)}
</View>
))}
</ScrollView>
</>
) : (
<Text style={styles.ratingsEmpty}>Aucun avis pour ce livreur</Text>
)}
</View>
</Pressable>
</Pressable>
</Modal>
</View>
);
}