client v1/notifs
This commit is contained in:
parent
bb4befc189
commit
3b8e19b211
@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import {View,Text,TextInput,Pressable,StyleSheet,Image,Alert,TouchableOpacity,} from "react-native";
|
||||
import {View,Text,TextInput,Pressable,StyleSheet,Image,Alert,TouchableOpacity,ActivityIndicator} from "react-native";
|
||||
import { Eye, EyeOff,ArrowLeft,Check } from "lucide-react-native";
|
||||
import { router } from "expo-router";
|
||||
import { signIn } from "../../../firebase/auth"; // Assure-toi que le chemin est correct
|
||||
@ -10,6 +10,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
|
||||
const SignInScreen = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form, setForm] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
@ -43,7 +44,7 @@ const SignInScreen = () => {
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const { user } = await signIn(form.email, form.password);
|
||||
|
||||
@ -57,7 +58,9 @@ const SignInScreen = () => {
|
||||
|
||||
router.replace("/screens/user/UserHomeScreen");
|
||||
} catch (error: any) {
|
||||
Alert.alert("Erreur", error.message);
|
||||
Alert.alert("Erreur", "Utilisateur Introuvable");
|
||||
} finally{
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -159,9 +162,17 @@ const SignInScreen = () => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Pressable style={styles.loginButton} onPress={handleLogin}>
|
||||
<Text style={styles.loginButtonText}>Connexion</Text>
|
||||
</Pressable>
|
||||
<TouchableOpacity
|
||||
style={[styles.loginButton, loading && { opacity: 0.7 }]}
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<Text style={styles.loginButtonText}>Connexion</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.signupContainer}>
|
||||
<Text style={styles.signupText}>Nouveau ici? </Text>
|
||||
|
@ -7,12 +7,14 @@ import {
|
||||
FlatList,
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
Button,
|
||||
Button
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useCart } from '@/context/cartContext';
|
||||
import { getAuth } from 'firebase/auth';
|
||||
import { collection, addDoc } from 'firebase/firestore';
|
||||
import { query, orderBy, limit, getDocs } from 'firebase/firestore';
|
||||
|
||||
import { db } from '@/firebase/config';
|
||||
import CartItem from '@/components/CartItem';
|
||||
import { ArrowLeft } from 'lucide-react-native';
|
||||
@ -44,39 +46,61 @@ export default function CartScreen() {
|
||||
}));
|
||||
|
||||
const handleConfirmOrder = useCallback(async () => {
|
||||
|
||||
|
||||
if (totalAmount <= 0) {
|
||||
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setIsProcessingOrder(true);
|
||||
buttonScale.value = withSequence(
|
||||
withTiming(0.95, { duration: 100 }),
|
||||
withTiming(1, { duration: 150 })
|
||||
);
|
||||
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const user = auth.currentUser;
|
||||
if (!user) {
|
||||
Alert.alert('Utilisateur non connecté', 'Veuillez vous connecter pour passer une commande.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Utilisation de query pour appliquer orderBy et limit
|
||||
const orderRef = collection(db, 'orders');
|
||||
const q = query(orderRef, orderBy('createdAt', 'desc'), limit(1));
|
||||
const orderSnapshot = await getDocs(q);
|
||||
let lastOrderId = 0;
|
||||
|
||||
if (!orderSnapshot.empty) {
|
||||
const lastOrderDoc = orderSnapshot.docs[0].data();
|
||||
lastOrderId = parseInt(lastOrderDoc.orderId.replace(/^0+/, '')) || 0; // Remove leading zeros
|
||||
}
|
||||
|
||||
const newOrderId = String(lastOrderId + 1).padStart(3, '0'); // Generate the new order ID
|
||||
|
||||
const orderData: any = {
|
||||
orderId: newOrderId, // Use the generated ID
|
||||
userId: user.uid,
|
||||
status: 'En attente',
|
||||
createdAt: new Date().toISOString(),
|
||||
totalAmount: totalAmount
|
||||
totalAmount: totalAmount,
|
||||
};
|
||||
|
||||
|
||||
// Add each item in the cart to the orderData
|
||||
cart.items.forEach((item, index) => {
|
||||
orderData[`item${index + 1}`] = {
|
||||
name: item.name,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Add the order to Firestore
|
||||
await addDoc(collection(db, 'orders'), orderData);
|
||||
Alert.alert('Commande confirmée', `Montant Total : ${totalAmount}DT`);
|
||||
|
||||
clearCart();
|
||||
} catch (error) {
|
||||
console.error('Erreur commande :', error);
|
||||
@ -84,7 +108,8 @@ export default function CartScreen() {
|
||||
} finally {
|
||||
setIsProcessingOrder(false);
|
||||
}
|
||||
}, [cart, totalAmount]);
|
||||
}, [cart, totalAmount, auth]);
|
||||
|
||||
|
||||
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
|
||||
<CartItem
|
||||
@ -169,7 +194,8 @@ export default function CartScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fff' },
|
||||
container: { flex: 1,
|
||||
backgroundColor: '#fff' },
|
||||
header: {
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
|
59
app/screens/user/OrderHistoryScreen.tsx
Normal file
59
app/screens/user/OrderHistoryScreen.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet,Text,TouchableOpacity } from 'react-native';
|
||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
import { router } from "expo-router";
|
||||
import { ArrowLeft } from 'lucide-react-native';
|
||||
import OrderList from '@/components/OrderList';
|
||||
|
||||
|
||||
export default function OrderHistoryScreen() {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||
<ArrowLeft size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Historique d'achat</Text>
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<OrderList />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
header: {
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#eee',
|
||||
marginTop: 5,
|
||||
},
|
||||
backButton: {
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
top: '50%',
|
||||
transform: [{ translateY: -12 }],
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
content: {
|
||||
padding: 15,
|
||||
backgroundColor: '#fff',
|
||||
paddingBottom: 10,
|
||||
},
|
||||
});
|
@ -1,49 +1,165 @@
|
||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StyleSheet, View, Text, TouchableOpacity, ScrollView, Alert, ActivityIndicator } from 'react-native';
|
||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import { ArrowLeft } from 'lucide-react-native';
|
||||
import { router } from "expo-router";
|
||||
import { getAuth, signOut } from "firebase/auth";
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const handleSignOut = () => {
|
||||
Alert.alert(
|
||||
"Déconnexion",
|
||||
"Êtes-vous sûr de vouloir vous déconnecter ?",
|
||||
[
|
||||
{ text: "Annuler", style: "cancel" },
|
||||
{
|
||||
text: "Se déconnecter",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
try {
|
||||
const auth = getAuth();
|
||||
await signOut(auth);
|
||||
router.replace('/'); // Replace with your actual sign-in screen route
|
||||
} catch (error) {
|
||||
Alert.alert("Erreur", "Impossible de se déconnecter.");
|
||||
console.error("Sign out error:", error);
|
||||
}
|
||||
import { getAuth, signOut } from "firebase/auth";
|
||||
import { getFirestore, doc, getDoc, collection, query, where, getDocs } from "firebase/firestore";
|
||||
|
||||
const ProfileCard = ({ cafeName, cafeAddress, city, phone }: { cafeName: string, cafeAddress: string, city: string, phone: string }) => (
|
||||
<View style={styles.profileCard}>
|
||||
<View style={styles.profileHeader}>
|
||||
<View style={styles.profileTitle}>
|
||||
<Feather name="coffee" size={20} color="#B45309" />
|
||||
<Text style={styles.cafeName}>{cafeName || "Nom De Café"}</Text>
|
||||
</View>
|
||||
<View style={styles.badge}>
|
||||
<Text style={styles.badgeText}>Validé</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.profileInfo}>
|
||||
<Feather name="map-pin" size={16} color="#4B5563" />
|
||||
<Text style={styles.infoText}>{cafeAddress || "Adresse Du Café"}, {city}</Text>
|
||||
</View>
|
||||
<View style={styles.profileInfo}>
|
||||
<Feather name="phone" size={16} color="#4B5563" />
|
||||
<Text style={styles.infoText}>+216 {phone || "+216 00 000 000"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
const handleSignOut = () => {
|
||||
Alert.alert(
|
||||
"Déconnexion",
|
||||
"Êtes-vous sûr de vouloir vous déconnecter ?",
|
||||
[
|
||||
{ text: "Annuler", style: "cancel" },
|
||||
{
|
||||
text: "Déconnexion",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
try {
|
||||
const auth = getAuth();
|
||||
await signOut(auth);
|
||||
router.replace('/');
|
||||
} catch (error) {
|
||||
Alert.alert("Erreur", "Impossible de se déconnecter.");
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const [cafeName, setCafeName] = useState('');
|
||||
const [cafeAddress, setCafeAddress] = useState('');
|
||||
const [city, setCity] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProfileData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const auth = getAuth();
|
||||
const db = getFirestore();
|
||||
const user = auth.currentUser;
|
||||
|
||||
if (!user) {
|
||||
setError("Utilisateur non connecté.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch téléphone
|
||||
const userDoc = await getDoc(doc(db, 'users', user.uid));
|
||||
if (userDoc.exists()) {
|
||||
const userData = userDoc.data();
|
||||
setPhone(userData.phone || '');
|
||||
} else {
|
||||
setError("Données utilisateur non trouvées.");
|
||||
}
|
||||
|
||||
// Fetch café
|
||||
const q = query(collection(db, 'coffee_shops'), where('ownerId', '==', user.uid));
|
||||
const querySnapshot = await getDocs(q);
|
||||
if (!querySnapshot.empty) {
|
||||
const cafeData = querySnapshot.docs[0].data();
|
||||
setCafeName(cafeData.cafeName || '');
|
||||
setCafeAddress(cafeData.cafeAddress || '');
|
||||
setCity(cafeData.city || '');
|
||||
} else {
|
||||
setError("Aucune information de café trouvée.");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
setError("Erreur lors du chargement des données.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProfileData();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<ActivityIndicator size="large" color="#B45309" />
|
||||
<Text style={{ marginTop: 12 }}>Chargement des données...</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
|
||||
<Text style={{ color: 'red', fontSize: 16, textAlign: 'center' }}>{error}</Text>
|
||||
<TouchableOpacity onPress={() => router.back()} style={{ marginTop: 20 }}>
|
||||
<Text style={{ color: '#2563EB' }}>Retour</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||
<ArrowLeft size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Espace Personnel</Text>
|
||||
</View>
|
||||
<SafeAreaProvider>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||
<ArrowLeft size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Espace Personnel</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.body}>
|
||||
<TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
|
||||
<Text style={styles.signOutButtonText}>Se déconnecter</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<ProfileCard cafeName={cafeName} cafeAddress={cafeAddress} city={city} phone={phone} />
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Commandes</Text>
|
||||
<MenuItem icon="clock" label="Historique d'achats" onPress={() => router.push('/screens/user/OrderHistoryScreen')} />
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Paramètres</Text>
|
||||
<MenuItem icon="bell" label="Notifications" badge={2}/>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleSignOut}>
|
||||
<Feather name="log-out" size={20} color="#EF4444" />
|
||||
<Text style={styles.logoutText}>Déconnexion</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -57,7 +173,6 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#eee',
|
||||
marginTop: 5,
|
||||
@ -73,20 +188,71 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
body: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
profileCard: {
|
||||
backgroundColor: '#f8f3e9',
|
||||
borderRadius: 12,
|
||||
padding: 20,
|
||||
marginBottom: 32,
|
||||
marginVertical: 25,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
profileHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 15,
|
||||
},
|
||||
profileTitle: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
signOutButton: {
|
||||
backgroundColor: '#B07B4F',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 10,
|
||||
},
|
||||
signOutButtonText: {
|
||||
color: '#fff',
|
||||
cafeName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
fontWeight: '700',
|
||||
color: '#1F2937',
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: '#10B981',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 9999,
|
||||
},
|
||||
badgeText: {
|
||||
color: 'white',
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
},
|
||||
profileInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: '#4B5563',
|
||||
},
|
||||
section: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#1F2937',
|
||||
marginBottom: 8,
|
||||
marginHorizontal: 15
|
||||
},
|
||||
logoutButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
paddingVertical: 16,
|
||||
marginHorizontal: 15
|
||||
},
|
||||
logoutText: {
|
||||
color: '#EF4444',
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
@ -52,6 +52,12 @@ export default function UserLayout() {
|
||||
href: null,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="OrderHistoryScreen"
|
||||
options={{
|
||||
href: null,
|
||||
}}
|
||||
/>
|
||||
|
||||
</Tabs>
|
||||
);
|
||||
|
70
components/MenuItem.tsx
Normal file
70
components/MenuItem.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
|
||||
type MenuItemProps = {
|
||||
icon: string;
|
||||
label: string;
|
||||
badge?: number;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const MenuItem = ({ icon, label, badge, onPress }: MenuItemProps) => {
|
||||
return (
|
||||
<TouchableOpacity style={styles.menuItem} onPress={onPress}>
|
||||
<View style={styles.menuItemLeft}>
|
||||
<Feather name={icon as any} size={20} color="#374151" />
|
||||
<Text style={styles.menuItemText}>{label}</Text>
|
||||
</View>
|
||||
<View style={styles.menuItemRight}>
|
||||
{badge ? (
|
||||
<View style={styles.menuBadge}>
|
||||
<Text style={styles.menuBadgeText}>{badge}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<Feather name="chevron-right" size={18} color="#9CA3AF" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
menuItem: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E7EB',
|
||||
marginHorizontal: 15
|
||||
},
|
||||
menuItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
menuItemText: {
|
||||
fontSize: 15,
|
||||
color: '#374151',
|
||||
},
|
||||
menuItemRight: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
menuBadge: {
|
||||
backgroundColor: '#F59E0B',
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
menuBadgeText: {
|
||||
color: 'white',
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
118
components/OrderItem.tsx
Normal file
118
components/OrderItem.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import { Order } from '@/constants/types';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
import COLORS from '@/constants/colors'; // Make sure you have COLORS imported if not already
|
||||
|
||||
interface OrderItemProps {
|
||||
order: Order;
|
||||
}
|
||||
|
||||
export default function OrderItem({ order }: OrderItemProps) {
|
||||
const scale = useSharedValue(1);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: scale.value }],
|
||||
};
|
||||
});
|
||||
|
||||
const handlePressIn = () => {
|
||||
scale.value = withTiming(0.98, {
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePressOut = () => {
|
||||
scale.value = withTiming(1, {
|
||||
duration: 200,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
||||
});
|
||||
};
|
||||
|
||||
// Conditional status logic
|
||||
const statusBackgroundColor = order.status === "Confirmée" ? "#4C7C54" : COLORS.primary;
|
||||
const statusText = order.status === "Confirmée" ? "Terminée" : order.status;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
>
|
||||
<Animated.View style={[styles.container, animatedStyle]}>
|
||||
<View style={styles.header}>
|
||||
{/* Show the orderId */}
|
||||
<Text style={styles.orderId}>Commande #{order.orderId}</Text>
|
||||
<View style={[styles.statusContainer, { backgroundColor: statusBackgroundColor }]}>
|
||||
<Text style={styles.statusText}>{statusText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.itemsContainer}>
|
||||
{order.items.map((item, index) => (
|
||||
<Text key={index} style={styles.item}>
|
||||
{item.name} x {item.quantity}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
<Text style={styles.time}>{order.timeAgo}</Text>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#F2F2F2',
|
||||
borderRadius: 12,
|
||||
padding: 25,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 1,
|
||||
},
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 3,
|
||||
elevation: 2,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
orderId: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#333333',
|
||||
},
|
||||
statusContainer: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
statusText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
},
|
||||
itemsContainer: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
item: {
|
||||
fontSize: 14,
|
||||
color: '#666666',
|
||||
lineHeight: 20,
|
||||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
color: '#999999',
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
});
|
120
components/OrderList.tsx
Normal file
120
components/OrderList.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FlatList, StyleSheet, View, Text, Alert, RefreshControl } from 'react-native';
|
||||
import Animated, { FadeInUp } from 'react-native-reanimated';
|
||||
import OrderItem from '@/components/OrderItem';
|
||||
import { getAuth } from 'firebase/auth';
|
||||
import { collection, query, where, getDocs } from 'firebase/firestore';
|
||||
import { db } from '@/firebase/config';
|
||||
import { Order } from '@/constants/types';
|
||||
import COLORS from '@/constants/colors';
|
||||
|
||||
interface OrderListProps {
|
||||
// Accept a trigger or prop to refresh data
|
||||
}
|
||||
|
||||
export default function OrderList() {
|
||||
const [orders, setOrders] = useState<Order[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const fetchOrders = async () => {
|
||||
const user = getAuth().currentUser;
|
||||
if (!user) {
|
||||
Alert.alert('Erreur', 'Utilisateur non connecté');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ordersQuery = query(
|
||||
collection(db, 'orders'),
|
||||
where('userId', '==', user.uid)
|
||||
);
|
||||
|
||||
const querySnapshot = await getDocs(ordersQuery);
|
||||
|
||||
const ordersData = querySnapshot.docs.map((doc) => {
|
||||
const data = doc.data();
|
||||
const order: Order = {
|
||||
id: doc.id,
|
||||
orderId: data.orderId || '',
|
||||
items: Object.keys(data)
|
||||
.filter(key => key.startsWith('item'))
|
||||
.map((key) => ({
|
||||
name: data[key].name,
|
||||
quantity: data[key].quantity,
|
||||
})),
|
||||
status: data.status || '',
|
||||
timeAgo: data.createdAt ? new Date(data.createdAt).toLocaleString() : '',
|
||||
userId: data.userId || '',
|
||||
createdAt: data.createdAt ? new Date(data.createdAt.seconds * 1000) : new Date(), // Ensure it is a Date object
|
||||
};
|
||||
return order;
|
||||
});
|
||||
|
||||
// Sort by orderId in descending order
|
||||
const sortedOrders = ordersData.sort((a, b) =>
|
||||
b.orderId.localeCompare(a.orderId) // reverse of localeCompare
|
||||
);
|
||||
|
||||
setOrders(sortedOrders);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des commandes:', error);
|
||||
Alert.alert('Erreur', "Impossible de récupérer les commandes.");
|
||||
} finally {
|
||||
setRefreshing(false); // stop the refreshing indicator after fetch
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fetchOrders();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchOrders();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={orders}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item, index }) => (
|
||||
<Animated.View
|
||||
entering={FadeInUp.delay(index * 100).springify()}
|
||||
style={styles.itemContainer}
|
||||
>
|
||||
<OrderItem order={item} />
|
||||
</Animated.View>
|
||||
)}
|
||||
contentContainerStyle={styles.listContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyText}>Aucune commande trouvée</Text>
|
||||
</View>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContent: {
|
||||
paddingTop: 16,
|
||||
paddingBottom: 24,
|
||||
},
|
||||
itemContainer: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
emptyContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
emptyText: {
|
||||
color: COLORS.text,
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
@ -22,7 +22,6 @@ export default function ProductCard({
|
||||
inStock = true,
|
||||
}: ProductCardProps) {
|
||||
const router = useRouter();
|
||||
console.log(image); // Log the image URL to ensure it's correct
|
||||
|
||||
|
||||
return (
|
||||
|
@ -17,4 +17,19 @@ export interface Product {
|
||||
export interface CartState {
|
||||
items: CartItem[];
|
||||
total: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Order {
|
||||
id: string;
|
||||
orderId:string,
|
||||
items: OrderItem[];
|
||||
status: string;
|
||||
timeAgo: string;
|
||||
userId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
export interface OrderItem {
|
||||
name: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user