chore: add view reason of client

This commit is contained in:
2026-03-30 16:43:10 +02:00
parent 5706416bfb
commit b98116c76f
2 changed files with 124 additions and 0 deletions
+32
View File
@@ -509,6 +509,38 @@ export const getClientReferralAdmin = async (
}
};
export interface CancelledOrder {
id: number;
username: string;
total_prix: number;
created_at: string;
updated_at: string;
items_count: number;
cancel_reason: string;
cancellation?: {
cancelled_at: string;
cancelled_by: string;
reason: string;
};
}
export const getClientCancelledOrders = async (
username: string,
): Promise<{ success: boolean; orders: CancelledOrder[] }> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/orders/cancelled`,
{ params: { username } },
);
return {
success: true,
orders: data.data?.cancelled_orders ?? [],
};
} catch {
return { success: false, orders: [] };
}
};
// ============================================
// ALERTES
// ============================================
@@ -4,9 +4,11 @@ import {
Text,
StyleSheet,
FlatList,
ScrollView,
RefreshControl,
TouchableOpacity,
TextInput as RNTextInput,
ActivityIndicator,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
@@ -21,8 +23,10 @@ import {
createClientByAdmin,
createUserByAdmin,
creditClientReferral,
getClientCancelledOrders,
getSettings,
} from "../../api/api_admin";
import type { CancelledOrder } from "../../api/api_admin";
import type { ClientResponse } from "../../api/types";
import LoadingSpinner from "../../components/ui/LoadingSpinner";
import Card from "../../components/ui/Card";
@@ -176,6 +180,14 @@ export default function UsersScreen() {
const [referralAmount, setReferralAmount] = useState("");
const [referralLoading, setReferralLoading] = useState(false);
// Cancelled orders modal
const [cancelledModal, setCancelledModal] = useState<{
visible: boolean;
username: string;
orders: CancelledOrder[];
loading: boolean;
}>({ visible: false, username: "", orders: [], loading: false });
const { alert, showError, showConfirm, hideAlert } = useAlert();
// --------------------------------------------------
@@ -417,6 +429,15 @@ export default function UsersScreen() {
}
};
// --------------------------------------------------
// Cancelled orders
// --------------------------------------------------
const openCancelledOrders = async (client: ClientResponse) => {
setCancelledModal({ visible: true, username: client.username, orders: [], loading: true });
const res = await getClientCancelledOrders(client.username);
setCancelledModal((prev) => ({ ...prev, orders: res.orders, loading: false }));
};
// --------------------------------------------------
// Create user
// --------------------------------------------------
@@ -759,6 +780,16 @@ export default function UsersScreen() {
/>
</TouchableOpacity>
)}
<TouchableOpacity
style={styles.editIconBtn}
onPress={() => openCancelledOrders(item.clientData!)}
>
<Ionicons
name="ban-outline"
size={20}
color={colors.danger}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.editIconBtn}
onPress={() => openEditClient(item.clientData!)}
@@ -1178,6 +1209,67 @@ export default function UsersScreen() {
cancelText={alert.cancelText}
/>
{/* Modal commandes annulées */}
<Modal
visible={cancelledModal.visible}
onClose={() => setCancelledModal((prev) => ({ ...prev, visible: false }))}
title={`Annulations — ${cancelledModal.username}`}
icon="ban-outline"
iconColor={colors.danger}
>
{cancelledModal.loading ? (
<ActivityIndicator color={colors.danger} style={{ marginVertical: spacing.xl }} />
) : cancelledModal.orders.length === 0 ? (
<Text style={{ color: colors.textMuted, textAlign: "center", paddingVertical: spacing.xl }}>
Aucune commande annulée
</Text>
) : (
<ScrollView style={{ maxHeight: 400 }} showsVerticalScrollIndicator={false}>
{cancelledModal.orders.map((order, idx) => {
const date = order.cancellation?.cancelled_at
? new Date(order.cancellation.cancelled_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" })
: new Date(order.updated_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" });
const reason = order.cancel_reason || order.cancellation?.reason || "";
return (
<View
key={order.id}
style={{
borderBottomWidth: idx < cancelledModal.orders.length - 1 ? 1 : 0,
borderBottomColor: colors.border,
paddingVertical: spacing.m,
gap: spacing.xs,
}}
>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: colors.textWhite, fontWeight: "700", fontSize: fontSize.sm }}>
Commande #{order.id}
</Text>
<Text style={{ color: colors.textMuted, fontSize: fontSize.xs }}>{date}</Text>
</View>
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={{ color: colors.textMuted, fontSize: fontSize.xs }}>
{order.items_count} article{order.items_count > 1 ? "s" : ""}
</Text>
<Text style={{ color: colors.danger, fontSize: fontSize.sm, fontWeight: "600" }}>
{Number(order.total_prix).toFixed(2)} €
</Text>
</View>
{reason ? (
<Text style={{ color: colors.warning, fontSize: fontSize.xs, fontStyle: "italic" }}>
Raison : {reason}
</Text>
) : (
<Text style={{ color: colors.textMuted, fontSize: fontSize.xs, fontStyle: "italic" }}>
Aucune raison fournie
</Text>
)}
</View>
);
})}
</ScrollView>
)}
</Modal>
{/* Modal crédit parrainage */}
<Modal
visible={referralModal.visible}