chore: add new features
This commit is contained in:
@@ -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
|
||||
// ============================================
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user