chore: fix comming soon

This commit is contained in:
2026-03-07 18:42:18 +01:00
parent ab7d8929a8
commit 91ad46c0f8
4 changed files with 315 additions and 131 deletions
@@ -9,7 +9,11 @@ import {
StatusBar,
Alert,
} from "react-native";
import { useRoute, useNavigation, type RouteProp } from "@react-navigation/native";
import {
useRoute,
useNavigation,
type RouteProp,
} from "@react-navigation/native";
import { Ionicons } from "@expo/vector-icons";
import MapView, { Marker, Polyline, PROVIDER_DEFAULT } from "react-native-maps";
import { spacing, fontSize, borderRadius } from "../../theme";
@@ -87,7 +91,9 @@ export default function OrderDetailScreen() {
try {
const res = await getCommandByID(orderId);
setCommand(res.command);
} catch { /* ignore */ }
} catch {
/* ignore */
}
}, 20000);
return () => clearInterval(interval);
}, [orderId]);
@@ -176,7 +182,10 @@ export default function OrderDetailScreen() {
const handleNotifyClient = async () => {
try {
await notifyClientToDescend(orderId);
showSuccess("Notification envoyée", "Le client a été prévenu de descendre");
showSuccess(
"Notification envoyée",
"Le client a été prévenu de descendre",
);
} catch (e: any) {
showError("Erreur", e.message);
}
@@ -773,7 +782,7 @@ export default function OrderDetailScreen() {
)}
{!["approved", "cancelled"].includes(command.status) && (
<Button
title="Le client est là"
title="Le livreur est là"
onPress={handleNotifyClient}
variant="warning"
fullWidth
@@ -823,9 +832,17 @@ export default function OrderDetailScreen() {
</View>
<ScrollView showsVerticalScrollIndicator={false}>
{items.map((item: any) => (
<View key={item.id} style={styles.itemsModalCard}>
<View
key={item.id}
style={styles.itemsModalCard}
>
<View style={styles.row}>
<Text style={[styles.itemName, { flex: 1 }]}>
<Text
style={[
styles.itemName,
{ flex: 1 },
]}
>
{item.produit ?? item.product_name}
</Text>
<TouchableOpacity
@@ -834,7 +851,8 @@ export default function OrderDetailScreen() {
setShowItemsModal(false);
handleDeleteItem(
item.id,
item.produit ?? item.product_name,
item.produit ??
item.product_name,
);
}}
>
+134 -60
View File
@@ -55,7 +55,8 @@ export default function OrdersScreen() {
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
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<{
@@ -162,7 +163,10 @@ export default function OrdersScreen() {
const handleNotifyClient = async (commandId: number) => {
try {
await notifyClientToDescend(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",
);
} catch (e: any) {
showError("Erreur", e.message);
}
@@ -191,15 +195,25 @@ export default function OrdersScreen() {
const handleProposeAddress = async () => {
if (!addressModal.commandId || !addressModal.input.trim()) return;
try {
await proposeAddressChangeAdmin(addressModal.commandId, addressModal.input.trim());
await proposeAddressChangeAdmin(
addressModal.commandId,
addressModal.input.trim(),
);
setAddressModal({ visible: false, commandId: null, input: "" });
showSuccess("Proposition envoyée", "Le client a été notifié de la nouvelle adresse proposée");
showSuccess(
"Proposition envoyée",
"Le client a été notifié de la nouvelle adresse proposée",
);
} catch (e: any) {
showError("Erreur", e.message);
}
};
const handleDeleteItem = (commandId: number, itemId: number, itemName: string) => {
const handleDeleteItem = (
commandId: number,
itemId: number,
itemName: string,
) => {
showConfirm(
"Supprimer l'article",
`Supprimer "${itemName}" de la commande #${commandId} ?`,
@@ -429,54 +443,83 @@ export default function OrdersScreen() {
condition?: boolean;
};
const actions: Action[] = ([
{
label: "Voir items",
icon: "receipt-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); openItems(item.id); },
},
{
label: "Proposer adresse",
icon: "location-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); setAddressModal({ visible: true, commandId: item.id, input: "" }); },
condition: !isDone,
},
{
label: "Le client est là",
icon: "notifications-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); handleNotifyClient(item.id); },
condition: !isDone,
},
{
label: "Assigner livreur",
icon: "bicycle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); openAssignModal(item.id); },
condition: !isDone,
},
{
label: "Passer en route",
icon: "car-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); handleStatusUpdate(item.id, "en_route"); },
condition: item.status === "assigned",
},
{
label: "Confirmer réception",
icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); handleConfirmReception(item.id); },
condition: item.status === "livre",
},
{
label: "Supprimer",
icon: "trash-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); handleDelete(item.id); },
danger: true,
},
] as Action[]).filter((a) => a.condition !== false);
const actions: Action[] = (
[
{
label: "Voir items",
icon: "receipt-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
openItems(item.id);
},
},
{
label: "Proposer adresse",
icon: "location-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
setAddressModal({
visible: true,
commandId: item.id,
input: "",
});
},
condition: !isDone,
},
{
label: "Le livreur est là",
icon: "notifications-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
handleNotifyClient(item.id);
},
condition: !isDone,
},
{
label: "Assigner livreur",
icon: "bicycle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
openAssignModal(item.id);
},
condition: !isDone,
},
{
label: "Passer en route",
icon: "car-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
handleStatusUpdate(item.id, "en_route");
},
condition: item.status === "assigned",
},
{
label: "Confirmer réception",
icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
handleConfirmReception(item.id);
},
condition: item.status === "livre",
},
{
label: "Supprimer",
icon: "trash-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
handleDelete(item.id);
},
danger: true,
},
] as Action[]
).filter((a) => a.condition !== false);
return (
<Card style={{ marginBottom: spacing.m }}>
<TouchableOpacity
onPress={() => navigation.navigate("OrderDetail", { orderId: item.id })}
onPress={() =>
navigation.navigate("OrderDetail", { orderId: item.id })
}
activeOpacity={0.7}
>
<View style={styles.row}>
@@ -489,7 +532,9 @@ export default function OrdersScreen() {
Total: {item.total_prix.toFixed(2)}
</Text>
{item.livreur_assign && (
<Text style={styles.cardText}>Livreur: {item.livreur_assign}</Text>
<Text style={styles.cardText}>
Livreur: {item.livreur_assign}
</Text>
)}
<Text style={styles.cardDate}>
{new Date(item.created_at).toLocaleString("fr-FR")}
@@ -515,16 +560,27 @@ export default function OrdersScreen() {
key={action.label}
style={[
styles.dropdownItem,
index === actions.length - 1 && styles.dropdownItemLast,
index === actions.length - 1 &&
styles.dropdownItemLast,
]}
onPress={action.onPress}
>
<Ionicons
name={action.icon}
size={16}
color={action.danger ? colors.danger : colors.accent}
color={
action.danger
? colors.danger
: colors.accent
}
/>
<Text style={action.danger ? styles.dropdownTextDanger : styles.dropdownText}>
<Text
style={
action.danger
? styles.dropdownTextDanger
: styles.dropdownText
}
>
{action.label}
</Text>
</TouchableOpacity>
@@ -588,7 +644,10 @@ export default function OrdersScreen() {
title={`Commande #${itemsModal.commandId}`}
icon="receipt-outline"
>
<ScrollView showsVerticalScrollIndicator={false} bounces={false}>
<ScrollView
showsVerticalScrollIndicator={false}
bounces={false}
>
{(itemsModal.commandInfo || itemsModal.clientInfo) && (
<View style={styles.modalSummary}>
{itemsModal.clientInfo?.username && (
@@ -638,8 +697,12 @@ export default function OrdersScreen() {
{item.produit ?? item.product_name}
</Text>
<Text style={styles.itemMeta}>
Qté: {item.quantite ?? item.quantity} ·{" "}
{(item.prix ?? item.price)?.toFixed(2)}
Qté:{" "}
{item.quantite ?? item.quantity} ·{" "}
{(item.prix ?? item.price)?.toFixed(
2,
)}{" "}
</Text>
</View>
<TouchableOpacity
@@ -648,7 +711,8 @@ export default function OrdersScreen() {
handleDeleteItem(
itemsModal.commandId!,
item.id,
item.produit ?? item.product_name,
item.produit ??
item.product_name,
)
}
>
@@ -699,7 +763,13 @@ export default function OrdersScreen() {
{/* Modal proposition adresse */}
<Modal
visible={addressModal.visible}
onClose={() => setAddressModal({ visible: false, commandId: null, input: "" })}
onClose={() =>
setAddressModal({
visible: false,
commandId: null,
input: "",
})
}
title={`Proposer adresse — commande #${addressModal.commandId}`}
icon="location-outline"
>
@@ -708,14 +778,18 @@ export default function OrdersScreen() {
placeholder="Nouvelle adresse..."
placeholderTextColor={colors.textMuted}
value={addressModal.input}
onChangeText={(t) => setAddressModal((prev) => ({ ...prev, input: t }))}
onChangeText={(t) =>
setAddressModal((prev) => ({ ...prev, input: t }))
}
multiline
/>
<TouchableOpacity
style={styles.addressConfirmBtn}
onPress={handleProposeAddress}
>
<Text style={styles.addressConfirmText}>Envoyer la proposition</Text>
<Text style={styles.addressConfirmText}>
Envoyer la proposition
</Text>
</TouchableOpacity>
</Modal>
@@ -54,7 +54,8 @@ export default function OrdersScreen() {
const [loading, setLoading] = useState(true);
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<{
visible: boolean;
commandId: number | null;
@@ -77,7 +78,9 @@ export default function OrdersScreen() {
commandId: number | null;
input: string;
}>({ visible: false, commandId: null, input: "" });
const [livreurs, setLivreurs] = useState<{ id: number; username: string }[]>([]);
const [livreurs, setLivreurs] = useState<
{ id: number; username: string }[]
>([]);
const loadData = useCallback(async () => {
try {
@@ -148,7 +151,10 @@ export default function OrdersScreen() {
setOpenMenuId(null);
try {
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",
);
} catch (e: any) {
showError("Erreur", e.message);
}
@@ -157,9 +163,15 @@ export default function OrdersScreen() {
const handleProposeAddress = async () => {
if (!addressModal.commandId || !addressModal.input.trim()) return;
try {
await proposeAddressChangeCabine(addressModal.commandId, addressModal.input.trim());
await proposeAddressChangeCabine(
addressModal.commandId,
addressModal.input.trim(),
);
setAddressModal({ visible: false, commandId: null, input: "" });
showSuccess("Proposition envoyée", "Le client a été notifié de la nouvelle adresse proposée");
showSuccess(
"Proposition envoyée",
"Le client a été notifié de la nouvelle adresse proposée",
);
} catch (e: any) {
showError("Erreur", e.message);
}
@@ -205,7 +217,10 @@ export default function OrdersScreen() {
const handleAssign = async (livreurUsername: string) => {
if (!assignModal.commandId) return;
try {
await assignDeliveryPersonByCabine(assignModal.commandId, livreurUsername);
await assignDeliveryPersonByCabine(
assignModal.commandId,
livreurUsername,
);
setAssignModal({ visible: false, commandId: null });
await loadData();
} catch (e: any) {
@@ -412,40 +427,49 @@ export default function OrdersScreen() {
condition?: boolean;
};
const actions: Action[] = ([
{
label: "Voir items",
icon: "receipt-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => openItems(item.id),
},
{
label: "Proposer adresse",
icon: "location-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => { setOpenMenuId(null); setAddressModal({ visible: true, commandId: item.id, input: "" }); },
},
{
label: "Le client est là",
icon: "notifications-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleNotifyClient(item.id),
},
{
label: "Assigner livreur",
icon: "bicycle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => openAssignModal(item.id),
},
{
label: "Confirmer réception",
icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleConfirmReception(item.id),
condition: item.status === "livre",
},
{
label: "Supprimer",
icon: "trash-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleDelete(item.id),
danger: true,
},
] as Action[]).filter((a) => a.condition !== false);
const actions: Action[] = (
[
{
label: "Voir items",
icon: "receipt-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => openItems(item.id),
},
{
label: "Proposer adresse",
icon: "location-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
setAddressModal({
visible: true,
commandId: item.id,
input: "",
});
},
},
{
label: "Le livreur est là",
icon: "notifications-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleNotifyClient(item.id),
},
{
label: "Assigner livreur",
icon: "bicycle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => openAssignModal(item.id),
},
{
label: "Confirmer réception",
icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleConfirmReception(item.id),
condition: item.status === "livre",
},
{
label: "Supprimer",
icon: "trash-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => handleDelete(item.id),
danger: true,
},
] as Action[]
).filter((a) => a.condition !== false);
return (
<Card style={{ marginBottom: spacing.m }}>
@@ -478,14 +502,19 @@ export default function OrdersScreen() {
key={action.label}
style={[
styles.dropdownItem,
index === actions.length - 1 && styles.dropdownItemLast,
index === actions.length - 1 &&
styles.dropdownItemLast,
]}
onPress={action.onPress}
>
<Ionicons
name={action.icon}
size={16}
color={action.danger ? colors.danger : colors.accent}
color={
action.danger
? colors.danger
: colors.accent
}
/>
<Text
style={
@@ -504,7 +533,9 @@ export default function OrdersScreen() {
);
};
const readyCount = itemsModal.items.filter((i) => i.status === "ready").length;
const readyCount = itemsModal.items.filter(
(i) => i.status === "ready",
).length;
const totalCount = itemsModal.items.length;
const progress = totalCount > 0 ? readyCount / totalCount : 0;
@@ -535,12 +566,19 @@ export default function OrdersScreen() {
title={`Commande #${itemsModal.commandId}`}
icon="receipt-outline"
>
<ScrollView showsVerticalScrollIndicator={false} bounces={false}>
<ScrollView
showsVerticalScrollIndicator={false}
bounces={false}
>
{(itemsModal.commandInfo || itemsModal.clientInfo) && (
<View style={styles.modalSummary}>
{itemsModal.clientInfo?.username && (
<View style={styles.modalSummaryRow}>
<Ionicons name="person-outline" size={14} color={colors.textMuted} />
<Ionicons
name="person-outline"
size={14}
color={colors.textMuted}
/>
<Text style={styles.modalSummaryText}>
{itemsModal.clientInfo.prenom}{" "}
{itemsModal.clientInfo.nom} ·{" "}
@@ -548,17 +586,26 @@ export default function OrdersScreen() {
</Text>
</View>
)}
{(itemsModal.commandInfo?.address || itemsModal.commandInfo?.adresse) && (
{(itemsModal.commandInfo?.address ||
itemsModal.commandInfo?.adresse) && (
<View style={styles.modalSummaryRow}>
<Ionicons name="location-outline" size={14} color={colors.textMuted} />
<Ionicons
name="location-outline"
size={14}
color={colors.textMuted}
/>
<Text style={styles.modalSummaryText}>
{itemsModal.commandInfo.address || itemsModal.commandInfo.adresse}
{itemsModal.commandInfo.address ||
itemsModal.commandInfo.adresse}
</Text>
</View>
)}
{itemsModal.commandInfo?.total_prix != null && (
<Text style={styles.modalTotal}>
{Number(itemsModal.commandInfo.total_prix).toFixed(2)}
{Number(
itemsModal.commandInfo.total_prix,
).toFixed(2)}{" "}
</Text>
)}
</View>
@@ -576,7 +623,9 @@ export default function OrdersScreen() {
{
width: `${progress * 100}%`,
backgroundColor:
progress === 1 ? STATUS_COLORS.ready : colors.info,
progress === 1
? STATUS_COLORS.ready
: colors.info,
},
]}
/>
@@ -585,30 +634,51 @@ export default function OrdersScreen() {
)}
{itemsModal.items.map((item: any, index: number) => {
const statusColor = STATUS_COLORS[item.status] || colors.textMuted;
const statusColor =
STATUS_COLORS[item.status] || colors.textMuted;
return (
<View key={item.id} style={styles.itemCard}>
<View style={[styles.itemCardAccent, { backgroundColor: statusColor }]} />
<View
style={[
styles.itemCardAccent,
{ backgroundColor: statusColor },
]}
/>
<View style={styles.itemCardBody}>
<View style={styles.itemIndex}>
<Text style={styles.itemIndexText}>{index + 1}</Text>
<Text style={styles.itemIndexText}>
{index + 1}
</Text>
</View>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.produit ?? item.product_name}
</Text>
<Text style={styles.itemMeta}>
Qté: {item.quantite ?? item.quantity} ·{" "}
{(item.prix ?? item.price)?.toFixed(2)}
Qté:{" "}
{item.quantite ?? item.quantity} ·{" "}
{(item.prix ?? item.price)?.toFixed(
2,
)}{" "}
</Text>
<View style={styles.itemStatusRow}>
<Ionicons
name={STATUS_ICONS[item.status] || "ellipse-outline"}
name={
STATUS_ICONS[item.status] ||
"ellipse-outline"
}
size={13}
color={statusColor}
/>
<Text style={[styles.itemStatusText, { color: statusColor }]}>
{STATUS_LABELS[item.status] || item.status}
<Text
style={[
styles.itemStatusText,
{ color: statusColor },
]}
>
{STATUS_LABELS[item.status] ||
item.status}
</Text>
</View>
</View>
@@ -625,7 +695,9 @@ export default function OrdersScreen() {
<Modal
visible={assignModal.visible}
onClose={() => setAssignModal({ visible: false, commandId: null })}
onClose={() =>
setAssignModal({ visible: false, commandId: null })
}
title={`Assigner commande #${assignModal.commandId}`}
icon="bicycle-outline"
>
@@ -635,7 +707,11 @@ export default function OrdersScreen() {
style={styles.livreurItem}
onPress={() => handleAssign(l.username)}
>
<Ionicons name="person-outline" size={20} color={colors.accent} />
<Ionicons
name="person-outline"
size={20}
color={colors.accent}
/>
<Text style={styles.livreurName}>{l.username}</Text>
</TouchableOpacity>
))}
@@ -647,7 +723,13 @@ export default function OrdersScreen() {
{/* Modal proposition adresse */}
<Modal
visible={addressModal.visible}
onClose={() => setAddressModal({ visible: false, commandId: null, input: "" })}
onClose={() =>
setAddressModal({
visible: false,
commandId: null,
input: "",
})
}
title={`Proposer adresse — commande #${addressModal.commandId}`}
icon="location-outline"
>
@@ -656,14 +738,18 @@ export default function OrdersScreen() {
placeholder="Nouvelle adresse..."
placeholderTextColor={colors.textMuted}
value={addressModal.input}
onChangeText={(t) => setAddressModal((prev) => ({ ...prev, input: t }))}
onChangeText={(t) =>
setAddressModal((prev) => ({ ...prev, input: t }))
}
multiline
/>
<TouchableOpacity
style={styles.addressConfirmBtn}
onPress={handleProposeAddress}
>
<Text style={styles.addressConfirmText}>Envoyer la proposition</Text>
<Text style={styles.addressConfirmText}>
Envoyer la proposition
</Text>
</TouchableOpacity>
</Modal>
+7 -1
View File
@@ -94,9 +94,15 @@ export default function ProductsScreen() {
}, [selectedCategory]);
useEffect(() => {
const catObj = categories.find((c) => c.name === selectedCategory);
if (catObj?.is_coming_soon) {
setLoading(false);
setProducts([]);
return;
}
setLoading(true);
fetchProducts();
}, [fetchProducts]);
}, [selectedCategory, categories, fetchProducts]);
useEffect(() => {
if (carouselRef.current && products.length > 0) {