chore: add new features

This commit is contained in:
2026-03-05 19:09:16 +01:00
parent 532584edb2
commit 5ca480ea0b
23 changed files with 695 additions and 284 deletions
+12
View File
@@ -159,6 +159,18 @@ export const validateCommand = async (commandId: number) => {
return { success: true, message: data.message };
};
export const confirmReceptionAdmin = async (commandId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/orders/${commandId}/confirm-reception`,
);
return {
success: true,
message: data.message,
points_earned: data.points_earned,
client_username: data.client_username,
};
};
// ============================================
// LIVREURS
// ============================================
+30
View File
@@ -31,6 +31,18 @@ export const updateItemStatus = async (itemId: number, status: string) => {
return { success: true, message: data.message };
};
export const confirmReceptionCabine = async (commandId: number) => {
const { data } = await apiClient.post(
`${API}/commands/${commandId}/confirm-reception`,
);
return {
success: true,
message: data.message,
points_earned: data.points_earned,
client_username: data.client_username,
};
};
// ============================================
// PENALITES
// ============================================
@@ -105,6 +117,24 @@ export const deleteCommand = async (commandId: number) => {
return { success: true, message: data.message };
};
export const getCabineLivreursList = async (): Promise<
{ id: number; username: string }[]
> => {
const { data } = await apiClient.get(`${API}/all/deliveryman`);
return data.users || [];
};
export const assignDeliveryPersonByCabine = async (
commandId: number,
livreurUsername: string,
) => {
const { data } = await apiClient.post(
`${API}/commands/${commandId}/assign`,
{ livreur_username: livreurUsername },
);
return { success: true, message: data.message };
};
export const getDeliverymanLocationForCommand = async (commandId: number) => {
try {
const { data } = await apiClient.get(
@@ -18,6 +18,7 @@ import {
getCommandItems,
updateCommandStatus,
validateCommand,
confirmReceptionAdmin,
getDeliveryPersonDetails,
} from "../../api/api_admin";
import { geocodeAddress, calculateRoute } from "../../api/tomtom";
@@ -75,6 +76,16 @@ export default function OrderDetailScreen() {
load();
}, [orderId]);
useEffect(() => {
const interval = setInterval(async () => {
try {
const res = await getCommandByID(orderId);
setCommand(res.command);
} catch { /* ignore */ }
}, 20000);
return () => clearInterval(interval);
}, [orderId]);
const loadLivreurRoute = async (
livreurUsername: string,
deliveryAddress: string,
@@ -156,6 +167,20 @@ export default function OrderDetailScreen() {
}
};
const handleConfirmReception = async () => {
try {
const res = await confirmReceptionAdmin(orderId);
showSuccess(
"Réception confirmée",
`${res.points_earned} point(s) attribués au client ${res.client_username}`,
);
const updated = await getCommandByID(orderId);
setCommand(updated.command);
} catch (e: any) {
showError("Erreur", e.message);
}
};
const styles = useMemo(
() =>
StyleSheet.create({
@@ -622,6 +647,15 @@ export default function OrderDetailScreen() {
style={{ marginTop: spacing.s }}
/>
)}
{command.status === "livre" && (
<Button
title="Confirmer la réception"
onPress={handleConfirmReception}
variant="primary"
fullWidth
style={{ marginTop: spacing.s }}
/>
)}
</View>
<AlertModal
visible={alert.visible}
@@ -212,7 +212,7 @@ export default function OrdersScreen() {
{new Date(item.created_at).toLocaleString("fr-FR")}
</Text>
<View style={styles.actions}>
{item.status === "pending" && (
{!["approved", "cancelled"].includes(item.status) && (
<Button
title="Assigner"
onPress={() => openAssignModal(item.id)}
@@ -6,6 +6,7 @@ import {
FlatList,
RefreshControl,
ScrollView,
TouchableOpacity,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
@@ -14,6 +15,9 @@ import { getAllCommands } from "../../api/api_admin";
import {
getCommandItems,
deleteCommand,
confirmReceptionCabine,
getCabineLivreursList,
assignDeliveryPersonByCabine,
} from "../../api/api_cabine";
import type { CommandResponse } from "../../api/types";
import StatusBadge from "../../components/StatusBadge";
@@ -61,6 +65,11 @@ export default function OrdersScreen() {
commandInfo: null,
clientInfo: null,
});
const [assignModal, setAssignModal] = useState<{
visible: boolean;
commandId: number | null;
}>({ visible: false, commandId: null });
const [livreurs, setLivreurs] = useState<{ id: number; username: string }[]>([]);
const loadData = useCallback(async () => {
try {
@@ -81,6 +90,13 @@ export default function OrdersScreen() {
loadData();
}, [loadData]);
useEffect(() => {
const interval = setInterval(() => {
loadData();
}, 30000);
return () => clearInterval(interval);
}, [loadData]);
const onRefresh = async () => {
setRefreshing(true);
await loadData();
@@ -103,6 +119,22 @@ export default function OrdersScreen() {
};
const handleConfirmReception = (commandId: number) => {
showConfirm(
"Confirmer la réception",
`Confirmer la réception de la commande #${commandId} au nom du client ?`,
async () => {
try {
await confirmReceptionCabine(commandId);
await loadData();
} catch (e: any) {
showError("Erreur", e.message);
}
},
"Confirmer",
);
};
const handleDelete = (commandId: number) => {
showConfirm(
"Supprimer",
@@ -128,6 +160,27 @@ export default function OrdersScreen() {
clientInfo: null,
});
const openAssignModal = async (commandId: number) => {
try {
const list = await getCabineLivreursList();
setLivreurs(list);
setAssignModal({ visible: true, commandId });
} catch {
showError("Erreur", "Impossible de charger les livreurs");
}
};
const handleAssign = async (livreurUsername: string) => {
if (!assignModal.commandId) return;
try {
await assignDeliveryPersonByCabine(assignModal.commandId, livreurUsername);
setAssignModal({ visible: false, commandId: null });
await loadData();
} catch (e: any) {
showError("Erreur", e.message);
}
};
const styles = useMemo(
() =>
StyleSheet.create({
@@ -271,6 +324,16 @@ export default function OrdersScreen() {
fontSize: fontSize.xs,
fontWeight: "600",
},
livreurItem: {
flexDirection: "row",
alignItems: "center",
padding: spacing.m,
backgroundColor: colors.bgCard,
borderRadius: borderRadius.sm,
marginBottom: spacing.s,
gap: spacing.m,
},
livreurName: { color: colors.textWhite, fontSize: fontSize.md },
}),
[colors],
);
@@ -293,6 +356,20 @@ export default function OrdersScreen() {
size="sm"
variant="primary"
/>
<Button
title="Assigner"
onPress={() => openAssignModal(item.id)}
size="sm"
variant="secondary"
/>
{item.status === "livre" && (
<Button
title="Confirmer réception"
onPress={() => handleConfirmReception(item.id)}
size="sm"
variant="success"
/>
)}
<Button
title="Supprimer"
onPress={() => handleDelete(item.id)}
@@ -461,6 +538,33 @@ export default function OrdersScreen() {
</ScrollView>
</Modal>
<Modal
visible={assignModal.visible}
onClose={() =>
setAssignModal({ visible: false, commandId: null })
}
title={`Assigner commande #${assignModal.commandId}`}
icon="bicycle-outline"
>
{livreurs.map((l) => (
<TouchableOpacity
key={l.username}
style={styles.livreurItem}
onPress={() => handleAssign(l.username)}
>
<Ionicons
name="person-outline"
size={20}
color={colors.accent}
/>
<Text style={styles.livreurName}>{l.username}</Text>
</TouchableOpacity>
))}
{livreurs.length === 0 && (
<Text style={styles.empty}>Aucun livreur disponible</Text>
)}
</Modal>
<AlertModal
visible={alert.visible}
type={alert.type}
@@ -271,6 +271,12 @@ export default function DashboardScreen() {
loadData();
}, [loadData]);
useEffect(() => {
const interval = setInterval(() => {
loadData();
}, 30000);
return () => clearInterval(interval);
}, [loadData]);
// Quand GPS devient disponible, rejouer la route en attente
useEffect(() => {