diff --git a/app/(tabs)/cart.tsx b/app/(tabs)/cart.tsx
index a28e492..846f849 100644
--- a/app/(tabs)/cart.tsx
+++ b/app/(tabs)/cart.tsx
@@ -11,11 +11,12 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
-import { useFocusEffect } from '@react-navigation/native';
+import { useFocusEffect, useRouter } from 'expo-router';
import { useCart } from '../../hooks/useCart';
import CartItemCard from '../../components/CartItemCard';
export default function CartScreen() {
+ const router = useRouter();
const {
cart,
loading,
@@ -42,7 +43,7 @@ export default function CartScreen() {
{ text: 'Hủy', style: 'cancel' },
{
text: 'Xóa tất cả',
- onPress: clearCart,
+ onPress: () => clearCart(),
style: 'destructive'
},
]
@@ -50,7 +51,13 @@ export default function CartScreen() {
};
const handleCheckout = () => {
- Alert.alert('Thông báo', 'Chức năng thanh toán đang được phát triển');
+ if (!cart || cart.items.length === 0) {
+ Alert.alert('Thông báo', 'Giỏ hàng của bạn đang trống');
+ return;
+ }
+
+ // Navigate to checkout screen
+ router.push('/checkout');
};
const formatPrice = (price: number) => {
@@ -84,6 +91,12 @@ export default function CartScreen() {
Hãy thêm sản phẩm vào giỏ hàng để mua sắm
+ router.push('/')}
+ >
+ Tiếp tục mua sắm
+
);
@@ -202,6 +215,18 @@ const styles = StyleSheet.create({
marginTop: 8,
textAlign: 'center',
},
+ shopButton: {
+ backgroundColor: '#ff6b6b',
+ paddingHorizontal: 32,
+ paddingVertical: 14,
+ borderRadius: 12,
+ marginTop: 24,
+ },
+ shopButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '700',
+ },
listContent: {
padding: 16,
},
diff --git a/app/(tabs)/checkout.tsx b/app/(tabs)/checkout.tsx
new file mode 100644
index 0000000..1e727e3
--- /dev/null
+++ b/app/(tabs)/checkout.tsx
@@ -0,0 +1,434 @@
+import React, { useState } from 'react';
+import { getUser } from '@/services/auth';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TextInput,
+ TouchableOpacity,
+ Alert,
+ ActivityIndicator,
+ KeyboardAvoidingView,
+ Platform,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { Ionicons } from '@expo/vector-icons';
+import { useCart } from '../../hooks/useCart';
+import orderApi from '../../services/orderApi';
+
+interface PaymentMethod {
+ id: string;
+ name: string;
+ icon: string;
+}
+
+const PAYMENT_METHODS: PaymentMethod[] = [
+ { id: 'COD', name: 'Thanh toán khi nhận hàng', icon: 'cash-outline' },
+ { id: 'BANK', name: 'Chuyển khoản ngân hàng', icon: 'card-outline' },
+ { id: 'MOMO', name: 'Ví MoMo', icon: 'wallet-outline' },
+ { id: 'VNPAY', name: 'VNPay', icon: 'card-outline' },
+];
+
+export default function CheckoutScreen() {
+ const router = useRouter();
+ const { cart, refreshCart, clearCart } = useCart();
+
+ const [loading, setLoading] = useState(false);
+ const [shippingAddress, setShippingAddress] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [note, setNote] = useState('');
+ const [selectedPayment, setSelectedPayment] = useState('COD');
+
+ const formatPrice = (price: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND',
+ }).format(price);
+ };
+
+ const validateForm = () => {
+ if (!shippingAddress.trim()) {
+ Alert.alert('Lỗi', 'Vui lòng nhập địa chỉ giao hàng');
+ return false;
+ }
+ if (!phoneNumber.trim()) {
+ Alert.alert('Lỗi', 'Vui lòng nhập số điện thoại');
+ return false;
+ }
+ if (!/^[0-9]{10}$/.test(phoneNumber)) {
+ Alert.alert('Lỗi', 'Số điện thoại không hợp lệ (10 chữ số)');
+ return false;
+ }
+ return true;
+ };
+
+ const handleCreateOrder = async () => {
+ if (!validateForm() || !cart) return;
+
+ setLoading(true);
+
+ try {
+ // LẤY userId từ AsyncStorage
+ const user = await getUser();
+ if (!user) {
+ Alert.alert("Lỗi", "Bạn chưa đăng nhập!");
+ return;
+ }
+ const userId = user.userId;
+
+ const fullAddress = `${shippingAddress}${note ? ` - Ghi chú: ${note}` : ''} - SĐT: ${phoneNumber}`;
+
+ const orderData = {
+ shippingAddress: fullAddress,
+ items: cart.items.map(item => ({
+ productId: item.product.productId,
+ quantity: item.quantity,
+ })),
+ };
+
+ // Gọi API đúng với 2 tham số
+ const response = await orderApi.createOrder(userId, orderData);
+
+ if (response.success && response.data) {
+ await clearCart(false);
+ await refreshCart();
+ router.replace({
+ pathname: '/order-success',
+ params: { orderId: response.data.orderId.toString() }
+ });
+ } else {
+ Alert.alert("Lỗi", response.message || "Đặt hàng thất bại");
+ }
+ } catch (error: any) {
+ console.error("Create order error:", error);
+ Alert.alert("Lỗi", error.message || "Không thể kết nối đến server");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+
+
+ if (!cart || cart.items.length === 0) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header */}
+
+ router.back()}>
+
+
+ Thanh toán
+
+
+
+
+ {/* Shipping Address */}
+
+
+
+ Địa chỉ giao hàng
+
+
+
+
+
+
+
+
+
+ {/* Order Items */}
+
+
+
+
+ Sản phẩm ({cart.items.length})
+
+
+
+ {cart.items.map((item) => (
+
+
+
+ {item.product.productName}
+
+
+ {formatPrice(item.product.price)} x {item.quantity}
+
+
+
+ {formatPrice(item.product.price * item.quantity)}
+
+
+ ))}
+
+
+ {/* Payment Method */}
+
+
+
+ Phương thức thanh toán
+
+
+ {PAYMENT_METHODS.map((method) => (
+ setSelectedPayment(method.id)}
+ >
+
+
+ {method.name}
+
+ {selectedPayment === method.id && (
+
+ )}
+
+ ))}
+
+
+ {/* Order Summary */}
+
+ Chi tiết thanh toán
+
+
+ Tạm tính
+
+ {formatPrice(cart.totalAmount)}
+
+
+
+
+ Phí vận chuyển
+ Miễn phí
+
+
+
+ Tổng cộng
+
+ {formatPrice(cart.totalAmount)}
+
+
+
+
+
+
+
+ {/* Order Button */}
+
+
+ {loading ? (
+
+ ) : (
+ <>
+ Đặt hàng
+
+ {formatPrice(cart.totalAmount)}
+
+ >
+ )}
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: 16,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#e0e0e0',
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#333',
+ },
+ scrollView: {
+ flex: 1,
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ section: {
+ backgroundColor: '#fff',
+ marginTop: 12,
+ padding: 16,
+ },
+ sectionHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: '700',
+ marginLeft: 8,
+ color: '#333',
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#e0e0e0',
+ borderRadius: 8,
+ padding: 12,
+ marginBottom: 12,
+ fontSize: 15,
+ backgroundColor: '#fafafa',
+ },
+ cartItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#f0f0f0',
+ },
+ itemInfo: {
+ flex: 1,
+ marginRight: 12,
+ },
+ itemName: {
+ fontSize: 15,
+ marginBottom: 4,
+ color: '#333',
+ },
+ itemPrice: {
+ fontSize: 13,
+ color: '#666',
+ },
+ itemTotal: {
+ fontSize: 15,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ paymentMethod: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: 14,
+ borderWidth: 1,
+ borderColor: '#e0e0e0',
+ borderRadius: 8,
+ marginBottom: 10,
+ backgroundColor: '#fafafa',
+ },
+ paymentMethodActive: {
+ borderColor: '#ff6b6b',
+ backgroundColor: '#fff5f5',
+ },
+ paymentMethodInfo: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ paymentMethodName: {
+ marginLeft: 12,
+ fontSize: 15,
+ color: '#333',
+ },
+ summaryRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 10,
+ },
+ summaryLabel: {
+ fontSize: 15,
+ color: '#666',
+ },
+ summaryValue: {
+ fontSize: 15,
+ color: '#333',
+ },
+ totalRow: {
+ borderTopWidth: 1,
+ borderTopColor: '#e0e0e0',
+ marginTop: 8,
+ paddingTop: 16,
+ },
+ totalLabel: {
+ fontSize: 17,
+ fontWeight: '700',
+ color: '#333',
+ },
+ totalValue: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ footer: {
+ backgroundColor: '#fff',
+ padding: 16,
+ borderTopWidth: 1,
+ borderTopColor: '#e0e0e0',
+ },
+ orderButton: {
+ backgroundColor: '#ff6b6b',
+ padding: 16,
+ borderRadius: 12,
+ alignItems: 'center',
+ },
+ orderButtonDisabled: {
+ opacity: 0.6,
+ },
+ orderButtonText: {
+ color: '#fff',
+ fontSize: 17,
+ fontWeight: '700',
+ },
+ orderButtonSubtext: {
+ color: '#fff',
+ fontSize: 14,
+ marginTop: 4,
+ },
+});
\ No newline at end of file
diff --git a/app/(tabs)/orders.tsx b/app/(tabs)/orders.tsx
new file mode 100644
index 0000000..ec95d47
--- /dev/null
+++ b/app/(tabs)/orders.tsx
@@ -0,0 +1,375 @@
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ FlatList,
+ TouchableOpacity,
+ ActivityIndicator,
+ RefreshControl,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { Ionicons } from '@expo/vector-icons';
+import orderApi, { Order } from '../../services/orderApi';
+
+const STATUS_COLORS: Record = {
+ PENDING: '#FFA726',
+ CONFIRMED: '#42A5F5',
+ SHIPPING: '#AB47BC',
+ DELIVERED: '#66BB6A',
+ CANCELLED: '#EF5350',
+};
+
+const STATUS_LABELS: Record = {
+ PENDING: 'Chờ xác nhận',
+ CONFIRMED: 'Đã xác nhận',
+ SHIPPING: 'Đang giao',
+ DELIVERED: 'Đã giao',
+ CANCELLED: 'Đã hủy',
+};
+
+const FILTER_TABS = [
+ { key: 'ALL', label: 'Tất cả' },
+ { key: 'PENDING', label: 'Chờ xác nhận' },
+ { key: 'SHIPPING', label: 'Đang giao' },
+ { key: 'DELIVERED', label: 'Đã giao' },
+ { key: 'CANCELLED', label: 'Đã hủy' },
+];
+
+export default function OrdersListScreen() {
+ const router = useRouter();
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+ const [activeTab, setActiveTab] = useState('ALL');
+
+ useEffect(() => {
+ loadOrders();
+ }, [activeTab]);
+
+ const loadOrders = async () => {
+ try {
+ const response = activeTab === 'ALL'
+ ? await orderApi.getAllOrders()
+ : await orderApi.getOrdersByStatus(activeTab);
+
+ if (response.success && response.data) {
+ setOrders(response.data);
+ }
+ } catch (error: any) {
+ console.error('Load orders error:', error);
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
+ }
+ };
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ loadOrders();
+ };
+
+ const formatDate = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleDateString('vi-VN', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ const formatPrice = (price: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND',
+ }).format(price);
+ };
+
+ const renderOrderItem = ({ item }: { item: Order }) => (
+ router.push(`/order-detail?id=${item.orderId}`)}
+ >
+
+ Đơn hàng #{item.orderId}
+
+
+ {STATUS_LABELS[item.orderStatus]}
+
+
+
+
+ {formatDate(item.createdAt)}
+
+
+ {item.orderItems.slice(0, 2).map((orderItem) => (
+
+ • {orderItem.productName} x{orderItem.quantity}
+
+ ))}
+ {item.orderItems.length > 2 && (
+
+ và {item.orderItems.length - 2} sản phẩm khác
+
+ )}
+
+
+
+ Tổng tiền:
+
+ {formatPrice(item.totalPrice)}
+
+
+
+
+ Xem chi tiết
+
+
+
+ );
+
+ const renderEmptyList = () => (
+
+
+ Chưa có đơn hàng nào
+ router.push('/')}
+ >
+ Mua sắm ngay
+
+
+ );
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+ router.back()}>
+
+
+ Đơn hàng của tôi
+
+
+
+ {/* Filter Tabs */}
+
+ item.key}
+ renderItem={({ item }) => (
+ setActiveTab(item.key)}
+ >
+
+ {item.label}
+
+
+ )}
+ />
+
+
+ {/* Orders List */}
+ item.orderId.toString()}
+ contentContainerStyle={orders.length > 0 ? styles.listContainer : styles.emptyListContainer}
+ ListEmptyComponent={renderEmptyList}
+ refreshControl={
+
+ }
+ />
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: 16,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#e0e0e0',
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#333',
+ },
+ tabContainer: {
+ backgroundColor: '#fff',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#e0e0e0',
+ },
+ tab: {
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ marginHorizontal: 4,
+ borderRadius: 20,
+ },
+ activeTab: {
+ backgroundColor: '#ff6b6b',
+ },
+ tabText: {
+ fontSize: 14,
+ color: '#666',
+ },
+ activeTabText: {
+ color: '#fff',
+ fontWeight: '700',
+ },
+ listContainer: {
+ padding: 12,
+ },
+ emptyListContainer: {
+ flex: 1,
+ },
+ orderCard: {
+ backgroundColor: '#fff',
+ borderRadius: 12,
+ padding: 16,
+ marginBottom: 12,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 3,
+ },
+ orderHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 8,
+ },
+ orderId: {
+ fontSize: 16,
+ fontWeight: '700',
+ color: '#333',
+ },
+ statusBadge: {
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 12,
+ },
+ statusText: {
+ color: '#fff',
+ fontSize: 12,
+ fontWeight: '700',
+ },
+ orderDate: {
+ fontSize: 13,
+ color: '#999',
+ marginBottom: 12,
+ },
+ orderItems: {
+ marginBottom: 12,
+ },
+ productName: {
+ fontSize: 14,
+ color: '#333',
+ marginBottom: 4,
+ },
+ moreItems: {
+ fontSize: 13,
+ color: '#666',
+ fontStyle: 'italic',
+ },
+ orderFooter: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingTop: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#f0f0f0',
+ },
+ totalLabel: {
+ fontSize: 15,
+ color: '#666',
+ },
+ totalPrice: {
+ fontSize: 18,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ viewDetailButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginTop: 12,
+ },
+ viewDetailText: {
+ color: '#ff6b6b',
+ fontSize: 14,
+ fontWeight: '700',
+ marginRight: 4,
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 60,
+ },
+ emptyText: {
+ fontSize: 16,
+ color: '#999',
+ marginTop: 16,
+ marginBottom: 24,
+ },
+ shopButton: {
+ backgroundColor: '#ff6b6b',
+ paddingHorizontal: 32,
+ paddingVertical: 14,
+ borderRadius: 12,
+ },
+ shopButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+});
\ No newline at end of file
diff --git a/app/order-detail.tsx b/app/order-detail.tsx
new file mode 100644
index 0000000..3ed9265
--- /dev/null
+++ b/app/order-detail.tsx
@@ -0,0 +1,509 @@
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ ActivityIndicator,
+ Alert,
+} from 'react-native';
+import { useRouter, useLocalSearchParams } from 'expo-router';
+import { Ionicons } from '@expo/vector-icons';
+import api from '../services/api';
+
+interface OrderDetail {
+ orderId: number;
+ userId: number;
+ userName: string;
+ totalPrice: number;
+ orderStatus: string;
+ shippingAddress: string;
+ createdAt: string;
+ updatedAt: string;
+ orderItems: {
+ orderItemId: number;
+ productId: number;
+ productName: string;
+ quantity: number;
+ price: number;
+ subtotal: number;
+ }[];
+}
+
+interface Timeline {
+ status: string;
+ timestamp: string | null;
+ completed: boolean;
+}
+
+const STATUS_COLORS: Record = {
+ PENDING: '#FFA726',
+ CONFIRMED: '#42A5F5',
+ SHIPPING: '#AB47BC',
+ DELIVERED: '#66BB6A',
+ CANCELLED: '#EF5350',
+};
+
+const STATUS_LABELS: Record = {
+ PENDING: 'Chờ xác nhận',
+ CONFIRMED: 'Đã xác nhận',
+ SHIPPING: 'Đang giao hàng',
+ DELIVERED: 'Đã giao hàng',
+ CANCELLED: 'Đã hủy',
+};
+
+export default function OrderDetailScreen() {
+ const router = useRouter();
+ const params = useLocalSearchParams();
+ const orderId = params.id;
+
+ const [order, setOrder] = useState(null);
+ const [timeline, setTimeline] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [cancelling, setCancelling] = useState(false);
+
+ useEffect(() => {
+ if (orderId) {
+ loadOrderDetail();
+ loadOrderProgress();
+ }
+ }, [orderId]);
+
+ /** 🔹 Load chi tiết đơn hàng */
+ const loadOrderDetail = async () => {
+ try {
+ const res = await api.get(`/orders/${orderId}`, {
+ requireAuth: true,
+ });
+ // Fix: Safely check 'res' type before accessing properties
+ if (res && typeof res === 'object' && 'success' in res && (res as any).success) {
+ setOrder((res as any).data);
+ }
+ } catch (error) {
+ console.error('Load order detail error:', error);
+ Alert.alert('Lỗi', 'Không thể tải chi tiết đơn hàng.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /** 🔹 Load tiến trình đơn hàng */
+ const loadOrderProgress = async () => {
+ try {
+ const res = await api.get(`/orders/${orderId}/progress`, {
+ requireAuth: true,
+ });
+ // Fix: safely check 'res' type before accessing properties
+ if (res && typeof res === 'object' && 'success' in res && (res as any).success) {
+ setTimeline((res as any).data.timeline);
+ }
+ } catch (error) {
+ console.error('Load order progress error:', error);
+ }
+ };
+
+ /** 🔹 Hủy đơn hàng */
+ const handleCancelOrder = () => {
+ Alert.alert(
+ 'Hủy đơn hàng',
+ 'Bạn có chắc chắn muốn hủy đơn hàng này?',
+ [
+ { text: 'Không', style: 'cancel' },
+ { text: 'Có', onPress: cancelOrder, style: 'destructive' },
+ ]
+ );
+ };
+
+ const cancelOrder = async () => {
+ setCancelling(true);
+
+ try {
+ const res = await api.put(`/orders/${orderId}/cancel`, undefined, {
+ requireAuth: true,
+ });
+
+ // Fix: 'res' is of type 'unknown', so first safely cast/type guard
+ if (res && typeof res === 'object' && 'success' in res && (res as any).success) {
+ Alert.alert('Thành công', 'Đơn hàng đã được hủy');
+ loadOrderDetail();
+ loadOrderProgress();
+ } else {
+ let message = 'Không thể hủy đơn hàng';
+ if (res && typeof res === 'object' && 'message' in res) {
+ // @ts-ignore
+ message = res.message || message;
+ }
+ Alert.alert('Lỗi', message);
+ }
+ } catch (error) {
+ Alert.alert('Lỗi', 'Không thể hủy đơn hàng');
+ } finally {
+ setCancelling(false);
+ }
+ };
+
+ const formatDate = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleDateString('vi-VN', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!order) {
+ return (
+
+ Không tìm thấy đơn hàng
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+ router.back()}>
+
+
+ Chi tiết đơn hàng
+
+
+
+ {/* Order status */}
+
+
+
+ {STATUS_LABELS[order.orderStatus]}
+
+
+ Đơn hàng #{order.orderId}
+
+ Đặt hàng: {formatDate(order.createdAt)}
+
+
+
+ {/* Timeline */}
+ {order.orderStatus !== 'CANCELLED' && (
+
+ Tiến độ đơn hàng
+ {timeline.map((step, idx) => (
+
+
+
+ {step.completed && (
+
+ )}
+
+ {idx < timeline.length - 1 && (
+
+ )}
+
+
+
+ {STATUS_LABELS[step.status]}
+
+ {step.timestamp && (
+
+ {formatDate(step.timestamp)}
+
+ )}
+
+
+ ))}
+
+ )}
+
+ {/* Shipping address */}
+
+
+ Địa chỉ giao hàng
+
+ {order.shippingAddress}
+
+
+ {/* Items */}
+
+
+ Sản phẩm
+
+ {order.orderItems.map((item) => (
+
+
+ {item.productName}
+ x{item.quantity}
+
+
+
+ {item.price.toLocaleString('vi-VN')}₫
+
+
+ {item.subtotal.toLocaleString('vi-VN')}₫
+
+
+
+ ))}
+
+
+ {/* Summary */}
+
+ Thanh toán
+
+ Tạm tính
+
+ {order.totalPrice.toLocaleString('vi-VN')}₫
+
+
+
+ Phí vận chuyển
+ 0₫
+
+
+ Tổng cộng
+
+ {order.totalPrice.toLocaleString('vi-VN')}₫
+
+
+
+
+ {/* Cancel Order */}
+ {(order.orderStatus === 'PENDING' ||
+ order.orderStatus === 'CONFIRMED') && (
+
+ {cancelling ? (
+
+ ) : (
+ Hủy đơn hàng
+ )}
+
+ )}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#F5F5F5',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: 16,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#E0E0E0',
+ },
+ headerTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ },
+ statusSection: {
+ backgroundColor: '#fff',
+ padding: 20,
+ alignItems: 'center',
+ marginTop: 10,
+ },
+ statusBadge: {
+ paddingHorizontal: 20,
+ paddingVertical: 8,
+ borderRadius: 20,
+ marginBottom: 12,
+ },
+ statusText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: 'bold',
+ },
+ orderId: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginBottom: 4,
+ },
+ orderDate: {
+ fontSize: 14,
+ color: '#666',
+ },
+ section: {
+ backgroundColor: '#fff',
+ marginTop: 10,
+ padding: 16,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ marginBottom: 12,
+ },
+ timelineItem: {
+ flexDirection: 'row',
+ marginBottom: 8,
+ },
+ timelineLeft: {
+ alignItems: 'center',
+ marginRight: 12,
+ },
+ timelineDot: {
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ backgroundColor: '#E0E0E0',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ timelineDotActive: {
+ backgroundColor: '#4CAF50',
+ },
+ timelineLine: {
+ width: 2,
+ flex: 1,
+ backgroundColor: '#E0E0E0',
+ marginTop: 4,
+ },
+ timelineLineActive: {
+ backgroundColor: '#4CAF50',
+ },
+ timelineRight: {
+ flex: 1,
+ paddingBottom: 16,
+ },
+ timelineStatus: {
+ fontSize: 14,
+ color: '#999',
+ marginBottom: 4,
+ },
+ timelineStatusActive: {
+ color: '#333',
+ fontWeight: 'bold',
+ },
+ timelineDate: {
+ fontSize: 12,
+ color: '#999',
+ },
+ addressText: {
+ fontSize: 14,
+ color: '#333',
+ lineHeight: 20,
+ },
+ productItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#F0F0F0',
+ },
+ productInfo: {
+ flex: 1,
+ marginRight: 12,
+ },
+ productName: {
+ fontSize: 14,
+ marginBottom: 4,
+ },
+ productQuantity: {
+ fontSize: 12,
+ color: '#666',
+ },
+ productPrices: {
+ alignItems: 'flex-end',
+ },
+ productPrice: {
+ fontSize: 12,
+ color: '#666',
+ marginBottom: 4,
+ },
+ productSubtotal: {
+ fontSize: 14,
+ fontWeight: 'bold',
+ color: '#FF6B6B',
+ },
+ summaryRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 8,
+ },
+ summaryLabel: {
+ fontSize: 14,
+ color: '#666',
+ },
+ summaryValue: {
+ fontSize: 14,
+ },
+ totalRow: {
+ borderTopWidth: 1,
+ borderTopColor: '#E0E0E0',
+ marginTop: 8,
+ paddingTop: 12,
+ },
+ totalLabel: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ },
+ totalValue: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#FF6B6B',
+ },
+ cancelButton: {
+ backgroundColor: '#EF5350',
+ margin: 16,
+ padding: 16,
+ borderRadius: 8,
+ alignItems: 'center',
+ },
+ buttonDisabled: {
+ opacity: 0.6,
+ },
+ cancelButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: 'bold',
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ errorContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+});
\ No newline at end of file
diff --git a/app/order-success.tsx b/app/order-success.tsx
new file mode 100644
index 0000000..d4991b8
--- /dev/null
+++ b/app/order-success.tsx
@@ -0,0 +1,530 @@
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ ActivityIndicator,
+ Alert,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter, useLocalSearchParams } from 'expo-router';
+import { Ionicons } from '@expo/vector-icons';
+import orderApi, { Order, OrderProgress } from '../services/orderApi';
+
+const STATUS_COLORS: Record = {
+ PENDING: '#FFA726',
+ CONFIRMED: '#42A5F5',
+ SHIPPING: '#AB47BC',
+ DELIVERED: '#66BB6A',
+ CANCELLED: '#EF5350',
+};
+
+const STATUS_LABELS: Record = {
+ PENDING: 'Chờ xác nhận',
+ CONFIRMED: 'Đã xác nhận',
+ SHIPPING: 'Đang giao hàng',
+ DELIVERED: 'Đã giao hàng',
+ CANCELLED: 'Đã hủy',
+};
+
+export default function OrderDetailScreen() {
+ const router = useRouter();
+ const params = useLocalSearchParams();
+ const orderId = params.orderId as string;
+
+ const [order, setOrder] = useState(null);
+ const [timeline, setTimeline] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [cancelling, setCancelling] = useState(false);
+
+ useEffect(() => {
+ if (orderId) {
+ console.log("Order Success Params:", params);
+console.log("Order ID:", orderId);
+
+ loadOrderDetail();
+ loadOrderProgress();
+ }
+ }, [orderId]);
+
+ const loadOrderDetail = async () => {
+ try {
+ const response = await orderApi.getOrderDetail(Number(orderId));
+ if (response.success && response.data) {
+ setOrder(response.data);
+ }
+ } catch (error: any) {
+ console.error('Load order detail error:', error);
+ Alert.alert('Lỗi', 'Không thể tải thông tin đơn hàng');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const loadOrderProgress = async () => {
+ try {
+ const response = await orderApi.getOrderProgress(Number(orderId));
+ if (response.success && response.data) {
+ setTimeline(response.data.timeline);
+ }
+ } catch (error: any) {
+ console.error('Load order progress error:', error);
+ }
+ };
+
+ const handleCancelOrder = () => {
+ Alert.alert(
+ 'Hủy đơn hàng',
+ 'Bạn có chắc chắn muốn hủy đơn hàng này?',
+ [
+ { text: 'Không', style: 'cancel' },
+ { text: 'Có', onPress: cancelOrder, style: 'destructive' },
+ ]
+ );
+ };
+
+ const cancelOrder = async () => {
+ setCancelling(true);
+ try {
+ const response = await orderApi.cancelOrder(Number(orderId));
+ if (response.success) {
+ Alert.alert('Thành công', 'Đơn hàng đã được hủy');
+ loadOrderDetail();
+ loadOrderProgress();
+ } else {
+ Alert.alert('Lỗi', response.message || 'Không thể hủy đơn hàng');
+ }
+ } catch (error: any) {
+ Alert.alert('Lỗi', error.message || 'Không thể hủy đơn hàng');
+ } finally {
+ setCancelling(false);
+ }
+ };
+
+ const formatDate = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleDateString('vi-VN', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ const formatPrice = (price: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND',
+ }).format(price);
+ };
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ if (!order) {
+ return (
+
+
+
+ Không tìm thấy đơn hàng
+ router.back()}
+ >
+ Quay lại
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+ router.push("/orders")}>
+
+
+ Chi tiết đơn hàng
+
+
+
+
+ {/* Order Status */}
+
+
+
+ {STATUS_LABELS[order.orderStatus]}
+
+
+ Đơn hàng #{order.orderId}
+
+ Đặt hàng: {formatDate(order.createdAt)}
+
+
+
+ {/* Timeline */}
+ {order.orderStatus !== 'CANCELLED' && timeline.length > 0 && (
+
+ Tiến độ đơn hàng
+ {timeline.map((step, index) => (
+
+
+
+ {step.completed && (
+
+ )}
+
+ {index < timeline.length - 1 && (
+
+ )}
+
+
+
+ {STATUS_LABELS[step.status]}
+
+ {step.timestamp && (
+
+ {formatDate(step.timestamp)}
+
+ )}
+
+
+ ))}
+
+ )}
+
+ {/* Shipping Address */}
+
+
+
+ Địa chỉ giao hàng
+
+ {order.shippingAddress}
+
+
+ {/* Order Items */}
+
+
+
+ Sản phẩm
+
+ {order.orderItems.map((item) => (
+
+
+ {item.productName}
+ x{item.quantity}
+
+
+
+ {formatPrice(item.price)}
+
+
+ {formatPrice(item.subtotal)}
+
+
+
+ ))}
+
+
+ {/* Payment Summary */}
+
+ Thanh toán
+
+ Tạm tính
+
+ {formatPrice(order.totalPrice)}
+
+
+
+ Phí vận chuyển
+ Miễn phí
+
+
+ Tổng cộng
+
+ {formatPrice(order.totalPrice)}
+
+
+
+
+
+
+
+ {/* Cancel Button */}
+ {(order.orderStatus === 'PENDING' ||
+ order.orderStatus === 'CONFIRMED') && (
+
+
+ {cancelling ? (
+
+ ) : (
+ Hủy đơn hàng
+ )}
+
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: 16,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#e0e0e0',
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#333',
+ },
+ scrollView: {
+ flex: 1,
+ },
+ statusSection: {
+ backgroundColor: '#fff',
+ padding: 20,
+ alignItems: 'center',
+ marginTop: 12,
+ },
+ statusBadge: {
+ paddingHorizontal: 20,
+ paddingVertical: 8,
+ borderRadius: 20,
+ marginBottom: 12,
+ },
+ statusText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '700',
+ },
+ orderId: {
+ fontSize: 18,
+ fontWeight: '700',
+ marginBottom: 4,
+ color: '#333',
+ },
+ orderDate: {
+ fontSize: 14,
+ color: '#666',
+ },
+ section: {
+ backgroundColor: '#fff',
+ marginTop: 12,
+ padding: 16,
+ },
+ sectionHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: '700',
+ marginLeft: 8,
+ color: '#333',
+ },
+ timelineItem: {
+ flexDirection: 'row',
+ marginBottom: 8,
+ },
+ timelineLeft: {
+ alignItems: 'center',
+ marginRight: 12,
+ },
+ timelineDot: {
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ backgroundColor: '#e0e0e0',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ timelineDotActive: {
+ backgroundColor: '#4CAF50',
+ },
+ timelineLine: {
+ width: 2,
+ flex: 1,
+ backgroundColor: '#e0e0e0',
+ marginTop: 4,
+ },
+ timelineLineActive: {
+ backgroundColor: '#4CAF50',
+ },
+ timelineRight: {
+ flex: 1,
+ paddingBottom: 16,
+ },
+ timelineStatus: {
+ fontSize: 14,
+ color: '#999',
+ marginBottom: 4,
+ },
+ timelineStatusActive: {
+ color: '#333',
+ fontWeight: '700',
+ },
+ timelineDate: {
+ fontSize: 12,
+ color: '#999',
+ },
+ addressText: {
+ fontSize: 14,
+ color: '#333',
+ lineHeight: 20,
+ },
+ productItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#f0f0f0',
+ },
+ productInfo: {
+ flex: 1,
+ marginRight: 12,
+ },
+ productName: {
+ fontSize: 15,
+ marginBottom: 4,
+ color: '#333',
+ },
+ productQuantity: {
+ fontSize: 13,
+ color: '#666',
+ },
+ productPrices: {
+ alignItems: 'flex-end',
+ },
+ productPrice: {
+ fontSize: 13,
+ color: '#666',
+ marginBottom: 4,
+ },
+ productSubtotal: {
+ fontSize: 15,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ summaryRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingVertical: 10,
+ },
+ summaryLabel: {
+ fontSize: 15,
+ color: '#666',
+ },
+ summaryValue: {
+ fontSize: 15,
+ color: '#333',
+ },
+ totalRow: {
+ borderTopWidth: 1,
+ borderTopColor: '#e0e0e0',
+ marginTop: 8,
+ paddingTop: 16,
+ },
+ totalLabel: {
+ fontSize: 17,
+ fontWeight: '700',
+ color: '#333',
+ },
+ totalValue: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ footer: {
+ backgroundColor: '#fff',
+ padding: 16,
+ borderTopWidth: 1,
+ borderTopColor: '#e0e0e0',
+ },
+ cancelButton: {
+ backgroundColor: '#EF5350',
+ padding: 16,
+ borderRadius: 12,
+ alignItems: 'center',
+ },
+ buttonDisabled: {
+ opacity: 0.6,
+ },
+ cancelButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ errorContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 32,
+ },
+ errorText: {
+ fontSize: 16,
+ color: '#999',
+ marginTop: 16,
+ marginBottom: 24,
+ },
+ backButton: {
+ backgroundColor: '#ff6b6b',
+ paddingHorizontal: 32,
+ paddingVertical: 14,
+ borderRadius: 12,
+ },
+ backButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+});
\ No newline at end of file
diff --git a/components/CartItemCard.tsx b/components/CartItemCard.tsx
index 71f91b9..ed19d0f 100644
--- a/components/CartItemCard.tsx
+++ b/components/CartItemCard.tsx
@@ -85,17 +85,17 @@ export default function CartItemCard({
return (
- {item.productName}
+ {item.product.productName}
- {formatPrice(item.price)}
+ {formatPrice(item.product.price)}
diff --git a/components/ui/CartButton.tsx b/components/ui/CartButton.tsx
new file mode 100644
index 0000000..6b9dbaa
--- /dev/null
+++ b/components/ui/CartButton.tsx
@@ -0,0 +1,82 @@
+import React, { useState, useEffect } from 'react';
+import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+export default function CartButton() {
+ const router = useRouter();
+ const [cartCount, setCartCount] = useState(0);
+
+ useEffect(() => {
+ loadCartCount();
+
+ // Refresh cart count khi focus
+ const interval = setInterval(loadCartCount, 3000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const loadCartCount = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) return;
+
+ const response = await fetch('http://YOUR_API_URL:8080/api/cart', {
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
+
+ const data = await response.json();
+ if (data.success && data.data.cartItems) {
+ const totalItems = data.data.cartItems.reduce(
+ (sum: number, item: any) => sum + item.quantity,
+ 0
+ );
+ setCartCount(totalItems);
+ }
+ } catch (error) {
+ console.error('Load cart count error:', error);
+ }
+ };
+
+ return (
+ router.push('/cart')}
+ >
+
+ {cartCount > 0 && (
+
+
+ {cartCount > 99 ? '99+' : cartCount}
+
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ position: 'relative',
+ padding: 8,
+ },
+ badge: {
+ position: 'absolute',
+ top: 4,
+ right: 4,
+ backgroundColor: '#FF6B6B',
+ borderRadius: 10,
+ minWidth: 18,
+ height: 18,
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 4,
+ },
+ badgeText: {
+ color: '#fff',
+ fontSize: 10,
+ fontWeight: 'bold',
+ },
+});
\ No newline at end of file
diff --git a/hooks/useCart.ts b/hooks/useCart.ts
index defec64..084fdd6 100644
--- a/hooks/useCart.ts
+++ b/hooks/useCart.ts
@@ -127,21 +127,32 @@ export const useCart = () => {
}, []);
// Xóa tất cả sản phẩm
- const clearCart = useCallback(async () => {
- try {
- const userId = await getUserId();
- if (!userId) return false;
-
- await cartService.clearCart(userId);
- setCart(null);
- Alert.alert('Thành công', 'Đã xóa tất cả sản phẩm');
- return true;
- } catch (error) {
- console.error('Clear cart error:', error);
- Alert.alert('Lỗi', 'Không thể xóa giỏ hàng');
- return false;
- }
- }, []);
+ const clearCart = useCallback(
+ async (showAlert: boolean = true) => {
+ try {
+ const userId = await getUserId();
+ if (!userId) return false;
+
+ await cartService.clearCart(userId);
+ setCart(null);
+
+ if (showAlert) {
+ Alert.alert('Thành công', 'Đã xóa tất cả sản phẩm');
+ }
+
+ return true;
+ } catch (error) {
+ console.error('Clear cart error:', error);
+
+ if (showAlert) {
+ Alert.alert('Lỗi', 'Không thể xóa giỏ hàng');
+ }
+
+ return false;
+ }
+ },
+ []
+ );
// Load cart khi component mount
useEffect(() => {
diff --git a/services/cart.ts b/services/cart.ts
index 0343794..55b54f5 100644
--- a/services/cart.ts
+++ b/services/cart.ts
@@ -2,12 +2,15 @@ import api from './api';
export interface CartItem {
cartItemId: number;
- productId: number;
- productName: string;
- productImage: string;
quantity: number;
- price: number;
subtotal: number;
+
+ product: {
+ productId: number;
+ productName: string;
+ price: number;
+ imageUrl: string;
+ };
}
export interface CartResponse {
@@ -43,12 +46,20 @@ const cartService = {
// Lấy danh sách sản phẩm trong giỏ hàng
getCart: async (userId: number): Promise => {
- console.log('=== GET CART REQUEST ===', { userId });
const response = await api.get(
`/cart/${userId}`,
{ requireAuth: true }
);
- console.log('=== GET CART RESPONSE ===', response);
+
+ // MAP lại items để UI dùng dễ hơn
+ response.items = response.items.map(item => ({
+ ...item,
+ productName: item.product.productName,
+ productImage: item.product.imageUrl,
+ price: Number(item.product.price),
+ subtotal: Number(item.subtotal),
+ }));
+
return response;
},
diff --git a/services/orderApi.ts b/services/orderApi.ts
new file mode 100644
index 0000000..52070e8
--- /dev/null
+++ b/services/orderApi.ts
@@ -0,0 +1,136 @@
+import api from './api';
+
+export interface CreateOrderRequest {
+ shippingAddress: string;
+ items: {
+ productId: number;
+ quantity: number;
+ }[];
+}
+
+export interface OrderItem {
+ orderItemId: number;
+ productId: number;
+ productName: string;
+ quantity: number;
+ price: number;
+ subtotal: number;
+}
+
+export interface Order {
+ orderId: number;
+ userId: number;
+ userName: string;
+ totalPrice: number;
+ orderStatus: string;
+ shippingAddress: string;
+ createdAt: string;
+ updatedAt: string;
+ orderItems: OrderItem[];
+}
+
+export interface OrderProgress {
+ orderId: number;
+ orderStatus: string;
+ timeline: {
+ status: string;
+ timestamp: string | null;
+ completed: boolean;
+ }[];
+}
+
+export interface ApiResponse {
+ success: boolean;
+ message?: string;
+ data?: T;
+ total?: number;
+}
+
+class OrderApiService {
+ /**
+ * Tạo đơn hàng
+ */
+ async createOrder(userId: number,orderData: CreateOrderRequest): Promise> {
+ try {
+ return await api.post(`/orders/${userId}`, orderData, {
+ requireAuth: true
+ });
+ } catch (error) {
+ console.error('Create order error:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Lấy tất cả đơn hàng
+ */
+ async getAllOrders(): Promise> {
+ try {
+ return await api.get>('/orders', {
+ requireAuth: true
+ });
+ } catch (error) {
+ console.error('Get all orders error:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Lọc đơn hàng theo trạng thái
+ */
+ async getOrdersByStatus(status: string): Promise> {
+ try {
+ return await api.get>(
+ `/orders/filter?status=${status}`,
+ { requireAuth: true }
+ );
+ } catch (error) {
+ console.error('Get orders by status error:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Lấy chi tiết đơn hàng
+ */
+ async getOrderDetail(orderId: number): Promise> {
+ try {
+ return await api.get>(`/orders/${orderId}`, {
+ requireAuth: true
+ });
+ } catch (error) {
+ console.error('Get order detail error:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Lấy tiến độ đơn hàng
+ */
+ async getOrderProgress(orderId: number): Promise> {
+ try {
+ return api.get(`/orders/${orderId}/progress`, { requireAuth: true });
+ } catch (error) {
+ console.error('Get order progress error:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Hủy đơn hàng
+ */
+ async cancelOrder(orderId: number): Promise> {
+ try {
+ return await api.put>(
+ `/orders/${orderId}/cancel`,
+ undefined,
+ { requireAuth: true }
+ );
+ } catch (error) {
+ console.error('Cancel order error:', error);
+ throw error;
+ }
+ }
+}
+
+export default new OrderApiService();
\ No newline at end of file