chore: add select
This commit is contained in:
@@ -28,7 +28,6 @@ import type { AdminStackParamList } from "../../navigation/types";
|
|||||||
import StatusBadge from "../../components/StatusBadge";
|
import StatusBadge from "../../components/StatusBadge";
|
||||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||||
import Modal from "../../components/ui/Modal";
|
import Modal from "../../components/ui/Modal";
|
||||||
import Button from "../../components/ui/Button";
|
|
||||||
import Card from "../../components/ui/Card";
|
import Card from "../../components/ui/Card";
|
||||||
import AlertModal from "../../components/ui/AlertModal";
|
import AlertModal from "../../components/ui/AlertModal";
|
||||||
import { useAlert } from "../../hooks/useAlert";
|
import { useAlert } from "../../hooks/useAlert";
|
||||||
@@ -55,6 +54,7 @@ export default function OrdersScreen() {
|
|||||||
const [filter, setFilter] = useState("all");
|
const [filter, setFilter] = useState("all");
|
||||||
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
|
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
|
||||||
|
|
||||||
|
const [openMenuId, setOpenMenuId] = useState<number | null>(null);
|
||||||
const [assignModal, setAssignModal] = useState<{
|
const [assignModal, setAssignModal] = useState<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
commandId: number | null;
|
commandId: number | null;
|
||||||
@@ -243,11 +243,47 @@ export default function OrdersScreen() {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginBottom: spacing.s,
|
marginBottom: spacing.s,
|
||||||
},
|
},
|
||||||
actions: {
|
selectBtn: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
flexWrap: "wrap",
|
alignItems: "center",
|
||||||
gap: spacing.s,
|
justifyContent: "space-between",
|
||||||
marginTop: spacing.m,
|
marginTop: spacing.m,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingVertical: spacing.s,
|
||||||
|
backgroundColor: colors.bgPrimary,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
selectBtnText: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
marginTop: 2,
|
||||||
|
backgroundColor: colors.bgCard,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
dropdownItem: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingVertical: spacing.m,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
},
|
||||||
|
dropdownItemLast: { borderBottomWidth: 0 },
|
||||||
|
dropdownText: {
|
||||||
|
color: colors.textWhite,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
},
|
||||||
|
dropdownTextDanger: {
|
||||||
|
color: colors.error,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
},
|
},
|
||||||
empty: {
|
empty: {
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
@@ -313,12 +349,60 @@ export default function OrdersScreen() {
|
|||||||
[colors],
|
[colors],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderOrder = ({ item }: { item: CommandResponse }) => (
|
const renderOrder = ({ item }: { item: CommandResponse }) => {
|
||||||
|
const isOpen = openMenuId === item.id;
|
||||||
|
const isDone = ["approved", "cancelled"].includes(item.status);
|
||||||
|
|
||||||
|
type Action = {
|
||||||
|
label: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
onPress: () => void;
|
||||||
|
danger?: boolean;
|
||||||
|
condition?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions: Action[] = [
|
||||||
|
{
|
||||||
|
label: "Voir items",
|
||||||
|
icon: "receipt-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); openItems(item.id); },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Le client est là",
|
||||||
|
icon: "notifications-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); handleNotifyClient(item.id); },
|
||||||
|
condition: !isDone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Assigner livreur",
|
||||||
|
icon: "bicycle-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); openAssignModal(item.id); },
|
||||||
|
condition: !isDone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Passer en route",
|
||||||
|
icon: "car-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); handleStatusUpdate(item.id, "en_route"); },
|
||||||
|
condition: item.status === "assigned",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Confirmer réception",
|
||||||
|
icon: "checkmark-circle-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); handleConfirmReception(item.id); },
|
||||||
|
condition: item.status === "livre",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Supprimer",
|
||||||
|
icon: "trash-outline",
|
||||||
|
onPress: () => { setOpenMenuId(null); handleDelete(item.id); },
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
].filter((a) => a.condition !== false);
|
||||||
|
|
||||||
|
return (
|
||||||
<Card style={{ marginBottom: spacing.m }}>
|
<Card style={{ marginBottom: spacing.m }}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() =>
|
onPress={() => navigation.navigate("OrderDetail", { orderId: item.id })}
|
||||||
navigation.navigate("OrderDetail", { orderId: item.id })
|
|
||||||
}
|
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
@@ -331,62 +415,51 @@ export default function OrdersScreen() {
|
|||||||
Total: {item.total_prix.toFixed(2)} €
|
Total: {item.total_prix.toFixed(2)} €
|
||||||
</Text>
|
</Text>
|
||||||
{item.livreur_assign && (
|
{item.livreur_assign && (
|
||||||
<Text style={styles.cardText}>
|
<Text style={styles.cardText}>Livreur: {item.livreur_assign}</Text>
|
||||||
Livreur: {item.livreur_assign}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
<Text style={styles.cardDate}>
|
<Text style={styles.cardDate}>
|
||||||
{new Date(item.created_at).toLocaleString("fr-FR")}
|
{new Date(item.created_at).toLocaleString("fr-FR")}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.actions}>
|
|
||||||
<Button
|
<TouchableOpacity
|
||||||
title="Voir items"
|
style={styles.selectBtn}
|
||||||
onPress={() => openItems(item.id)}
|
onPress={() => setOpenMenuId(isOpen ? null : item.id)}
|
||||||
size="sm"
|
>
|
||||||
variant="primary"
|
<Text style={styles.selectBtnText}>Actions</Text>
|
||||||
|
<Ionicons
|
||||||
|
name={isOpen ? "chevron-up" : "chevron-down"}
|
||||||
|
size={14}
|
||||||
|
color={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
{!["approved", "cancelled"].includes(item.status) && (
|
</TouchableOpacity>
|
||||||
<Button
|
|
||||||
title="Le client est là"
|
{isOpen && (
|
||||||
onPress={() => handleNotifyClient(item.id)}
|
<View style={styles.dropdown}>
|
||||||
size="sm"
|
{actions.map((action, index) => (
|
||||||
variant="warning"
|
<TouchableOpacity
|
||||||
/>
|
key={action.label}
|
||||||
)}
|
style={[
|
||||||
{!["approved", "cancelled"].includes(item.status) && (
|
styles.dropdownItem,
|
||||||
<Button
|
index === actions.length - 1 && styles.dropdownItemLast,
|
||||||
title="Assigner"
|
]}
|
||||||
onPress={() => openAssignModal(item.id)}
|
onPress={action.onPress}
|
||||||
size="sm"
|
>
|
||||||
variant="secondary"
|
<Ionicons
|
||||||
/>
|
name={action.icon}
|
||||||
)}
|
size={16}
|
||||||
{item.status === "assigned" && (
|
color={action.danger ? colors.error : colors.accent}
|
||||||
<Button
|
|
||||||
title="En route"
|
|
||||||
onPress={() => handleStatusUpdate(item.id, "en_route")}
|
|
||||||
size="sm"
|
|
||||||
variant="secondary"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{item.status === "livre" && (
|
|
||||||
<Button
|
|
||||||
title="Confirmer"
|
|
||||||
onPress={() => handleConfirmReception(item.id)}
|
|
||||||
size="sm"
|
|
||||||
variant="success"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
title="Supprimer"
|
|
||||||
onPress={() => handleDelete(item.id)}
|
|
||||||
size="sm"
|
|
||||||
variant="danger"
|
|
||||||
/>
|
/>
|
||||||
|
<Text style={action.danger ? styles.dropdownTextDanger : styles.dropdownText}>
|
||||||
|
{action.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner message="Chargement commandes..." />;
|
if (loading) return <LoadingSpinner message="Chargement commandes..." />;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import type { CommandResponse } from "../../api/types";
|
|||||||
import StatusBadge from "../../components/StatusBadge";
|
import StatusBadge from "../../components/StatusBadge";
|
||||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||||
import Modal from "../../components/ui/Modal";
|
import Modal from "../../components/ui/Modal";
|
||||||
import Button from "../../components/ui/Button";
|
|
||||||
import Card from "../../components/ui/Card";
|
import Card from "../../components/ui/Card";
|
||||||
import AlertModal from "../../components/ui/AlertModal";
|
import AlertModal from "../../components/ui/AlertModal";
|
||||||
import { useAlert } from "../../hooks/useAlert";
|
import { useAlert } from "../../hooks/useAlert";
|
||||||
@@ -52,6 +51,7 @@ export default function OrdersScreen() {
|
|||||||
const [commands, setCommands] = useState<CommandResponse[]>([]);
|
const [commands, setCommands] = useState<CommandResponse[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [openMenuId, setOpenMenuId] = useState<number | null>(null);
|
||||||
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
|
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
|
||||||
const [itemsModal, setItemsModal] = useState<{
|
const [itemsModal, setItemsModal] = useState<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -105,6 +105,7 @@ export default function OrdersScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openItems = async (commandId: number) => {
|
const openItems = async (commandId: number) => {
|
||||||
|
setOpenMenuId(null);
|
||||||
try {
|
try {
|
||||||
const result = await getCommandItems(commandId);
|
const result = await getCommandItems(commandId);
|
||||||
setItemsModal({
|
setItemsModal({
|
||||||
@@ -119,8 +120,8 @@ export default function OrdersScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleConfirmReception = (commandId: number) => {
|
const handleConfirmReception = (commandId: number) => {
|
||||||
|
setOpenMenuId(null);
|
||||||
showConfirm(
|
showConfirm(
|
||||||
"Confirmer la réception",
|
"Confirmer la réception",
|
||||||
`Confirmer la réception de la commande #${commandId} au nom du client ?`,
|
`Confirmer la réception de la commande #${commandId} au nom du client ?`,
|
||||||
@@ -137,6 +138,7 @@ export default function OrdersScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNotifyClient = async (commandId: number) => {
|
const handleNotifyClient = async (commandId: number) => {
|
||||||
|
setOpenMenuId(null);
|
||||||
try {
|
try {
|
||||||
await notifyClientToDescendCabine(commandId);
|
await notifyClientToDescendCabine(commandId);
|
||||||
showSuccess("Notification envoyée", "Le client a été prévenu de descendre");
|
showSuccess("Notification envoyée", "Le client a été prévenu de descendre");
|
||||||
@@ -146,6 +148,7 @@ export default function OrdersScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (commandId: number) => {
|
const handleDelete = (commandId: number) => {
|
||||||
|
setOpenMenuId(null);
|
||||||
showConfirm(
|
showConfirm(
|
||||||
"Supprimer",
|
"Supprimer",
|
||||||
`Supprimer la commande #${commandId} ?`,
|
`Supprimer la commande #${commandId} ?`,
|
||||||
@@ -171,6 +174,7 @@ export default function OrdersScreen() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openAssignModal = async (commandId: number) => {
|
const openAssignModal = async (commandId: number) => {
|
||||||
|
setOpenMenuId(null);
|
||||||
try {
|
try {
|
||||||
const list = await getCabineLivreursList();
|
const list = await getCabineLivreursList();
|
||||||
setLivreurs(list);
|
setLivreurs(list);
|
||||||
@@ -211,18 +215,56 @@ export default function OrdersScreen() {
|
|||||||
fontSize: fontSize.sm,
|
fontSize: fontSize.sm,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
gap: spacing.s,
|
|
||||||
marginTop: spacing.m,
|
|
||||||
},
|
|
||||||
empty: {
|
empty: {
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
marginTop: spacing.xl,
|
marginTop: spacing.xl,
|
||||||
},
|
},
|
||||||
|
// Select actions
|
||||||
|
selectBtn: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: spacing.m,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingVertical: spacing.s,
|
||||||
|
backgroundColor: colors.bgPrimary,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
},
|
||||||
|
selectBtnText: {
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
marginTop: 2,
|
||||||
|
backgroundColor: colors.bgCard,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
dropdownItem: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
paddingHorizontal: spacing.m,
|
||||||
|
paddingVertical: spacing.m,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
},
|
||||||
|
dropdownItemLast: {
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
},
|
||||||
|
dropdownText: {
|
||||||
|
color: colors.textWhite,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
},
|
||||||
|
dropdownTextDanger: {
|
||||||
|
color: colors.error,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
},
|
||||||
// Modal summary
|
// Modal summary
|
||||||
modalSummary: {
|
modalSummary: {
|
||||||
backgroundColor: colors.bgCard,
|
backgroundColor: colors.bgCard,
|
||||||
@@ -247,11 +289,7 @@ export default function OrdersScreen() {
|
|||||||
fontWeight: "700",
|
fontWeight: "700",
|
||||||
marginTop: spacing.xs,
|
marginTop: spacing.xs,
|
||||||
},
|
},
|
||||||
|
progressContainer: { marginBottom: spacing.m },
|
||||||
// Modal progress bar
|
|
||||||
progressContainer: {
|
|
||||||
marginBottom: spacing.m,
|
|
||||||
},
|
|
||||||
progressLabel: {
|
progressLabel: {
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
fontSize: fontSize.xs,
|
fontSize: fontSize.xs,
|
||||||
@@ -263,21 +301,14 @@ export default function OrdersScreen() {
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
progressFill: {
|
progressFill: { height: "100%", borderRadius: 2 },
|
||||||
height: "100%",
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Item card
|
|
||||||
itemCard: {
|
itemCard: {
|
||||||
backgroundColor: colors.bgCard,
|
backgroundColor: colors.bgCard,
|
||||||
borderRadius: borderRadius.sm,
|
borderRadius: borderRadius.sm,
|
||||||
marginBottom: spacing.s,
|
marginBottom: spacing.s,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
itemCardAccent: {
|
itemCardAccent: { height: 3 },
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
itemCardBody: {
|
itemCardBody: {
|
||||||
padding: spacing.m,
|
padding: spacing.m,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -315,26 +346,7 @@ export default function OrdersScreen() {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 5,
|
gap: 5,
|
||||||
},
|
},
|
||||||
itemStatusText: {
|
itemStatusText: { fontSize: fontSize.xs, fontWeight: "600" },
|
||||||
fontSize: fontSize.xs,
|
|
||||||
fontWeight: "600",
|
|
||||||
},
|
|
||||||
itemActions: {
|
|
||||||
gap: spacing.xs,
|
|
||||||
},
|
|
||||||
statusBtn: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 4,
|
|
||||||
paddingHorizontal: spacing.s,
|
|
||||||
paddingVertical: 5,
|
|
||||||
borderRadius: borderRadius.sm,
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
statusBtnText: {
|
|
||||||
fontSize: fontSize.xs,
|
|
||||||
fontWeight: "600",
|
|
||||||
},
|
|
||||||
livreurItem: {
|
livreurItem: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -349,7 +361,48 @@ export default function OrdersScreen() {
|
|||||||
[colors],
|
[colors],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderOrder = ({ item }: { item: CommandResponse }) => (
|
const renderOrder = ({ item }: { item: CommandResponse }) => {
|
||||||
|
const isOpen = openMenuId === item.id;
|
||||||
|
|
||||||
|
type Action = {
|
||||||
|
label: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
onPress: () => void;
|
||||||
|
danger?: boolean;
|
||||||
|
condition?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions: Action[] = [
|
||||||
|
{
|
||||||
|
label: "Voir items",
|
||||||
|
icon: "receipt-outline",
|
||||||
|
onPress: () => openItems(item.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Le client est là",
|
||||||
|
icon: "notifications-outline",
|
||||||
|
onPress: () => handleNotifyClient(item.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Assigner livreur",
|
||||||
|
icon: "bicycle-outline",
|
||||||
|
onPress: () => openAssignModal(item.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Confirmer réception",
|
||||||
|
icon: "checkmark-circle-outline",
|
||||||
|
onPress: () => handleConfirmReception(item.id),
|
||||||
|
condition: item.status === "livre",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Supprimer",
|
||||||
|
icon: "trash-outline",
|
||||||
|
onPress: () => handleDelete(item.id),
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
].filter((a) => a.condition !== false);
|
||||||
|
|
||||||
|
return (
|
||||||
<Card style={{ marginBottom: spacing.m }}>
|
<Card style={{ marginBottom: spacing.m }}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={styles.orderId}>#{item.id}</Text>
|
<Text style={styles.orderId}>#{item.id}</Text>
|
||||||
@@ -360,46 +413,53 @@ export default function OrdersScreen() {
|
|||||||
<Text style={styles.info}>
|
<Text style={styles.info}>
|
||||||
Total: {item.total_prix.toFixed(2)} €
|
Total: {item.total_prix.toFixed(2)} €
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.actions}>
|
|
||||||
<Button
|
<TouchableOpacity
|
||||||
title="Voir items"
|
style={styles.selectBtn}
|
||||||
onPress={() => openItems(item.id)}
|
onPress={() => setOpenMenuId(isOpen ? null : item.id)}
|
||||||
size="sm"
|
>
|
||||||
variant="primary"
|
<Text style={styles.selectBtnText}>Actions</Text>
|
||||||
|
<Ionicons
|
||||||
|
name={isOpen ? "chevron-up" : "chevron-down"}
|
||||||
|
size={14}
|
||||||
|
color={colors.textSecondary}
|
||||||
/>
|
/>
|
||||||
<Button
|
</TouchableOpacity>
|
||||||
title="Le client est là"
|
|
||||||
onPress={() => handleNotifyClient(item.id)}
|
{isOpen && (
|
||||||
size="sm"
|
<View style={styles.dropdown}>
|
||||||
variant="warning"
|
{actions.map((action, index) => (
|
||||||
/>
|
<TouchableOpacity
|
||||||
<Button
|
key={action.label}
|
||||||
title="Assigner"
|
style={[
|
||||||
onPress={() => openAssignModal(item.id)}
|
styles.dropdownItem,
|
||||||
size="sm"
|
index === actions.length - 1 && styles.dropdownItemLast,
|
||||||
variant="secondary"
|
]}
|
||||||
/>
|
onPress={action.onPress}
|
||||||
{item.status === "livre" && (
|
>
|
||||||
<Button
|
<Ionicons
|
||||||
title="Confirmer réception"
|
name={action.icon}
|
||||||
onPress={() => handleConfirmReception(item.id)}
|
size={16}
|
||||||
size="sm"
|
color={action.danger ? colors.error : colors.accent}
|
||||||
variant="success"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
title="Supprimer"
|
|
||||||
onPress={() => handleDelete(item.id)}
|
|
||||||
size="sm"
|
|
||||||
variant="danger"
|
|
||||||
/>
|
/>
|
||||||
|
<Text
|
||||||
|
style={
|
||||||
|
action.danger
|
||||||
|
? styles.dropdownTextDanger
|
||||||
|
: styles.dropdownText
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const readyCount = itemsModal.items.filter(
|
const readyCount = itemsModal.items.filter((i) => i.status === "ready").length;
|
||||||
(i) => i.status === "ready",
|
|
||||||
).length;
|
|
||||||
const totalCount = itemsModal.items.length;
|
const totalCount = itemsModal.items.length;
|
||||||
const progress = totalCount > 0 ? readyCount / totalCount : 0;
|
const progress = totalCount > 0 ? readyCount / totalCount : 0;
|
||||||
|
|
||||||
@@ -430,20 +490,12 @@ export default function OrdersScreen() {
|
|||||||
title={`Commande #${itemsModal.commandId}`}
|
title={`Commande #${itemsModal.commandId}`}
|
||||||
icon="receipt-outline"
|
icon="receipt-outline"
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView showsVerticalScrollIndicator={false} bounces={false}>
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
bounces={false}
|
|
||||||
>
|
|
||||||
{/* Résumé commande / client */}
|
|
||||||
{(itemsModal.commandInfo || itemsModal.clientInfo) && (
|
{(itemsModal.commandInfo || itemsModal.clientInfo) && (
|
||||||
<View style={styles.modalSummary}>
|
<View style={styles.modalSummary}>
|
||||||
{itemsModal.clientInfo?.username && (
|
{itemsModal.clientInfo?.username && (
|
||||||
<View style={styles.modalSummaryRow}>
|
<View style={styles.modalSummaryRow}>
|
||||||
<Ionicons
|
<Ionicons name="person-outline" size={14} color={colors.textMuted} />
|
||||||
name="person-outline"
|
|
||||||
size={14}
|
|
||||||
color={colors.textMuted}
|
|
||||||
/>
|
|
||||||
<Text style={styles.modalSummaryText}>
|
<Text style={styles.modalSummaryText}>
|
||||||
{itemsModal.clientInfo.prenom}{" "}
|
{itemsModal.clientInfo.prenom}{" "}
|
||||||
{itemsModal.clientInfo.nom} ·{" "}
|
{itemsModal.clientInfo.nom} ·{" "}
|
||||||
@@ -453,11 +505,7 @@ export default function OrdersScreen() {
|
|||||||
)}
|
)}
|
||||||
{(itemsModal.commandInfo?.address || itemsModal.commandInfo?.adresse) && (
|
{(itemsModal.commandInfo?.address || itemsModal.commandInfo?.adresse) && (
|
||||||
<View style={styles.modalSummaryRow}>
|
<View style={styles.modalSummaryRow}>
|
||||||
<Ionicons
|
<Ionicons name="location-outline" size={14} color={colors.textMuted} />
|
||||||
name="location-outline"
|
|
||||||
size={14}
|
|
||||||
color={colors.textMuted}
|
|
||||||
/>
|
|
||||||
<Text style={styles.modalSummaryText}>
|
<Text style={styles.modalSummaryText}>
|
||||||
{itemsModal.commandInfo.address || itemsModal.commandInfo.adresse}
|
{itemsModal.commandInfo.address || itemsModal.commandInfo.adresse}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -465,16 +513,12 @@ export default function OrdersScreen() {
|
|||||||
)}
|
)}
|
||||||
{itemsModal.commandInfo?.total_prix != null && (
|
{itemsModal.commandInfo?.total_prix != null && (
|
||||||
<Text style={styles.modalTotal}>
|
<Text style={styles.modalTotal}>
|
||||||
{Number(
|
{Number(itemsModal.commandInfo.total_prix).toFixed(2)} €
|
||||||
itemsModal.commandInfo.total_prix,
|
|
||||||
).toFixed(2)}{" "}
|
|
||||||
€
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Barre de progression */}
|
|
||||||
{totalCount > 0 && (
|
{totalCount > 0 && (
|
||||||
<View style={styles.progressContainer}>
|
<View style={styles.progressContainer}>
|
||||||
<Text style={styles.progressLabel}>
|
<Text style={styles.progressLabel}>
|
||||||
@@ -487,9 +531,7 @@ export default function OrdersScreen() {
|
|||||||
{
|
{
|
||||||
width: `${progress * 100}%`,
|
width: `${progress * 100}%`,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
progress === 1
|
progress === 1 ? STATUS_COLORS.ready : colors.info,
|
||||||
? STATUS_COLORS.ready
|
|
||||||
: colors.info,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -497,23 +539,14 @@ export default function OrdersScreen() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Liste des items */}
|
|
||||||
{itemsModal.items.map((item: any, index: number) => {
|
{itemsModal.items.map((item: any, index: number) => {
|
||||||
const statusColor =
|
const statusColor = STATUS_COLORS[item.status] || colors.textMuted;
|
||||||
STATUS_COLORS[item.status] || colors.textMuted;
|
|
||||||
return (
|
return (
|
||||||
<View key={item.id} style={styles.itemCard}>
|
<View key={item.id} style={styles.itemCard}>
|
||||||
<View
|
<View style={[styles.itemCardAccent, { backgroundColor: statusColor }]} />
|
||||||
style={[
|
|
||||||
styles.itemCardAccent,
|
|
||||||
{ backgroundColor: statusColor },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View style={styles.itemCardBody}>
|
<View style={styles.itemCardBody}>
|
||||||
<View style={styles.itemIndex}>
|
<View style={styles.itemIndex}>
|
||||||
<Text style={styles.itemIndexText}>
|
<Text style={styles.itemIndexText}>{index + 1}</Text>
|
||||||
{index + 1}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.itemInfo}>
|
<View style={styles.itemInfo}>
|
||||||
<Text style={styles.itemName}>
|
<Text style={styles.itemName}>
|
||||||
@@ -525,22 +558,12 @@ export default function OrdersScreen() {
|
|||||||
</Text>
|
</Text>
|
||||||
<View style={styles.itemStatusRow}>
|
<View style={styles.itemStatusRow}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={
|
name={STATUS_ICONS[item.status] || "ellipse-outline"}
|
||||||
STATUS_ICONS[
|
|
||||||
item.status
|
|
||||||
] || "ellipse-outline"
|
|
||||||
}
|
|
||||||
size={13}
|
size={13}
|
||||||
color={statusColor}
|
color={statusColor}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text style={[styles.itemStatusText, { color: statusColor }]}>
|
||||||
style={[
|
{STATUS_LABELS[item.status] || item.status}
|
||||||
styles.itemStatusText,
|
|
||||||
{ color: statusColor },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{STATUS_LABELS[item.status] ||
|
|
||||||
item.status}
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -557,9 +580,7 @@ export default function OrdersScreen() {
|
|||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
visible={assignModal.visible}
|
visible={assignModal.visible}
|
||||||
onClose={() =>
|
onClose={() => setAssignModal({ visible: false, commandId: null })}
|
||||||
setAssignModal({ visible: false, commandId: null })
|
|
||||||
}
|
|
||||||
title={`Assigner commande #${assignModal.commandId}`}
|
title={`Assigner commande #${assignModal.commandId}`}
|
||||||
icon="bicycle-outline"
|
icon="bicycle-outline"
|
||||||
>
|
>
|
||||||
@@ -569,11 +590,7 @@ export default function OrdersScreen() {
|
|||||||
style={styles.livreurItem}
|
style={styles.livreurItem}
|
||||||
onPress={() => handleAssign(l.username)}
|
onPress={() => handleAssign(l.username)}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons name="person-outline" size={20} color={colors.accent} />
|
||||||
name="person-outline"
|
|
||||||
size={20}
|
|
||||||
color={colors.accent}
|
|
||||||
/>
|
|
||||||
<Text style={styles.livreurName}>{l.username}</Text>
|
<Text style={styles.livreurName}>{l.username}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ export default function DashboardScreen() {
|
|||||||
const pendingRouteAddress = useRef<string | null>(null);
|
const pendingRouteAddress = useRef<string | null>(null);
|
||||||
const { alert, showError, showSuccess, hideAlert } = useAlert();
|
const { alert, showError, showSuccess, hideAlert } = useAlert();
|
||||||
|
|
||||||
const [detailsDelivery, setDetailsDelivery] = useState<EnrichedDelivery | null>(null);
|
const [detailsDelivery, setDetailsDelivery] =
|
||||||
|
useState<EnrichedDelivery | null>(null);
|
||||||
|
|
||||||
const STATUS_COLORS: Record<string, string> = useMemo(
|
const STATUS_COLORS: Record<string, string> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -235,7 +236,8 @@ export default function DashboardScreen() {
|
|||||||
? `${client.prenom || ""} ${client.nom}`.trim()
|
? `${client.prenom || ""} ${client.nom}`.trim()
|
||||||
: client?.username || undefined,
|
: client?.username || undefined,
|
||||||
clientPhone: client?.telephone || undefined,
|
clientPhone: client?.telephone || undefined,
|
||||||
clientUsername: client?.username || undefined,
|
clientUsername:
|
||||||
|
client?.username || undefined,
|
||||||
clientNom: client?.nom || undefined,
|
clientNom: client?.nom || undefined,
|
||||||
clientPrenom: client?.prenom || undefined,
|
clientPrenom: client?.prenom || undefined,
|
||||||
items:
|
items:
|
||||||
@@ -320,7 +322,8 @@ export default function DashboardScreen() {
|
|||||||
longitude: position.coords.longitude,
|
longitude: position.coords.longitude,
|
||||||
altitude: position.coords.altitude ?? 0,
|
altitude: position.coords.altitude ?? 0,
|
||||||
accuracy: position.coords.accuracy ?? 0,
|
accuracy: position.coords.accuracy ?? 0,
|
||||||
altitudeAccuracy: position.coords.altitudeAccuracy ?? 0,
|
altitudeAccuracy:
|
||||||
|
position.coords.altitudeAccuracy ?? 0,
|
||||||
heading: position.coords.heading ?? 0,
|
heading: position.coords.heading ?? 0,
|
||||||
speed: position.coords.speed ?? 0,
|
speed: position.coords.speed ?? 0,
|
||||||
},
|
},
|
||||||
@@ -329,7 +332,11 @@ export default function DashboardScreen() {
|
|||||||
(error) => reject(new Error(error.message)),
|
(error) => reject(new Error(error.message)),
|
||||||
// enableHighAccuracy: false → provider réseau/cell (1-3s)
|
// enableHighAccuracy: false → provider réseau/cell (1-3s)
|
||||||
// au lieu du GPS pur qui peut prendre 30-60s (cold start)
|
// au lieu du GPS pur qui peut prendre 30-60s (cold start)
|
||||||
{ enableHighAccuracy: false, timeout: 15000, maximumAge: 10000 },
|
{
|
||||||
|
enableHighAccuracy: false,
|
||||||
|
timeout: 15000,
|
||||||
|
maximumAge: 10000,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
@@ -636,7 +643,11 @@ export default function DashboardScreen() {
|
|||||||
onPress={() => setDetailsDelivery(item)}
|
onPress={() => setDetailsDelivery(item)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<Ionicons name="list-outline" size={16} color={colors.accent} />
|
<Ionicons
|
||||||
|
name="list-outline"
|
||||||
|
size={16}
|
||||||
|
color={colors.accent}
|
||||||
|
/>
|
||||||
<Text style={styles.detailsBtnText}>Détails</Text>
|
<Text style={styles.detailsBtnText}>Détails</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
@@ -1578,7 +1589,11 @@ export default function DashboardScreen() {
|
|||||||
{/* Infos client */}
|
{/* Infos client */}
|
||||||
<View style={styles.detailSection}>
|
<View style={styles.detailSection}>
|
||||||
<Text style={styles.detailSectionTitle}>
|
<Text style={styles.detailSectionTitle}>
|
||||||
<Ionicons name="person-outline" size={14} color={colors.accent} />
|
<Ionicons
|
||||||
|
name="person-outline"
|
||||||
|
size={14}
|
||||||
|
color={colors.accent}
|
||||||
|
/>
|
||||||
{" "}Client
|
{" "}Client
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.detailRow}>
|
<View style={styles.detailRow}>
|
||||||
@@ -1606,10 +1621,15 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
{/* Produits */}
|
{/* Produits */}
|
||||||
<Text style={styles.detailSectionTitle}>
|
<Text style={styles.detailSectionTitle}>
|
||||||
<Ionicons name="bag-outline" size={14} color={colors.success} />
|
<Ionicons
|
||||||
|
name="bag-outline"
|
||||||
|
size={14}
|
||||||
|
color={colors.success}
|
||||||
|
/>
|
||||||
{" "}Produits
|
{" "}Produits
|
||||||
</Text>
|
</Text>
|
||||||
{detailsDelivery?.items && detailsDelivery.items.length > 0 ? (
|
{detailsDelivery?.items &&
|
||||||
|
detailsDelivery.items.length > 0 ? (
|
||||||
detailsDelivery.items.map((prod, idx) => (
|
detailsDelivery.items.map((prod, idx) => (
|
||||||
<View key={idx} style={styles.detailProductRow}>
|
<View key={idx} style={styles.detailProductRow}>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user