225 lines
8.3 KiB
TypeScript
225 lines
8.3 KiB
TypeScript
import React, { useState, useCallback, useRef } from 'react';
|
|
import type { OrderCardActionProps } from '@packages/shared'
|
|
import { View, TouchableOpacity, ActivityIndicator, Linking, ScrollView, Dimensions, NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
|
|
import { useRouter } from 'expo-router';
|
|
import { tw, MyText } from 'common-ui';
|
|
import { useNavigationTarget } from 'common-ui/hooks/useNavigationTarget';
|
|
import { MaterialIcons, Ionicons } from '@expo/vector-icons';
|
|
import dayjs from 'dayjs';
|
|
import { trpc } from '@/src/trpc-client';
|
|
import { Image } from 'expo-image';
|
|
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
|
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
|
|
import { useUserDetails } from '@/src/contexts/AuthContext';
|
|
import type { Order, OrderItem } from '@/src/types/orders';
|
|
|
|
const { width: screenWidth } = Dimensions.get('window');
|
|
const CARD_WIDTH = (screenWidth - 48) * 0.95; // card width (24px padding each side), reduced 5%
|
|
const SNAP_INTERVAL = CARD_WIDTH + 24; // card + right margin = page width
|
|
|
|
interface OrderCardProps extends OrderCardActionProps<Order> {
|
|
supportMobile?: string | null;
|
|
}
|
|
|
|
function OrderCard({ order, onPress, supportMobile }: OrderCardProps) {
|
|
return (
|
|
<TouchableOpacity
|
|
style={{ width: CARD_WIDTH, marginRight: 24 }}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={tw`bg-gradient-to-r from-amber-50 to-orange-50 rounded-2xl p-4 border-2 border-amber-300`}>
|
|
<View style={tw`flex-row items-center justify-between mb-3`}>
|
|
<View style={tw`flex-row items-center`}>
|
|
<View style={tw`w-2 h-2 rounded-full bg-amber-500 mr-2`} />
|
|
<MyText style={tw`text-[10px] font-bold text-amber-600 uppercase tracking-wider`}>Upcoming Order</MyText>
|
|
</View>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`px-2 py-0.5 bg-amber-200 rounded-full flex-row items-center`}>
|
|
<MaterialIcons name="bolt" size={10} color="#D97706" style={tw`mr-0.5`} />
|
|
<MyText style={tw`text-[8px] font-black text-amber-700 uppercase`}>FLASH</MyText>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
<View style={tw`flex-row justify-between items-start mb-3`}>
|
|
<View>
|
|
<MyText style={tw`text-lg font-extrabold text-gray-900`}>#{order.orderId}</MyText>
|
|
<MyText style={tw`text-xs text-gray-500 mt-0.5`}>
|
|
{order.items.length} {order.items.length === 1 ? 'item' : 'items'} • ₹{order.totalAmount}
|
|
</MyText>
|
|
</View>
|
|
<View style={tw`bg-white px-2 py-1 rounded-lg border border-amber-200`}>
|
|
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{orderStatusManipulator(order.deliveryStatus)}</MyText>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={tw`flex-row items-center`}>
|
|
<MaterialIcons
|
|
name={order.isFlashDelivery ? "bolt" : "schedule"}
|
|
size={14}
|
|
color="#D97706"
|
|
style={tw`mr-1.5`}
|
|
/>
|
|
<MyText style={tw`text-xs font-medium text-amber-800`}>
|
|
{order.isFlashDelivery
|
|
? "30-Min Delivery"
|
|
: (() => {
|
|
const startTime = dayjs(order.deliveryDate);
|
|
const endTime = startTime.add(1, 'hour');
|
|
return `${startTime.format("DD MMM, hh:mm A")} - ${endTime.format("hh:mm A")}`;
|
|
})()
|
|
}
|
|
</MyText>
|
|
</View>
|
|
|
|
{order.items.length > 0 && (
|
|
<View style={tw`flex-row items-center mt-3 pt-3 border-t border-amber-200`}>
|
|
<View style={tw`flex-row`}>
|
|
{order.items.slice(0, 3).map((item, idx) => (
|
|
<View
|
|
key={idx}
|
|
style={[
|
|
tw`w-8 h-8 rounded-lg bg-white border border-amber-200 items-center justify-center`,
|
|
{ marginLeft: idx > 0 ? -8 : 0 }
|
|
]}
|
|
>
|
|
<Image
|
|
source={{ uri: item.image || undefined }}
|
|
style={tw`w-full h-full rounded-lg`}
|
|
/>
|
|
</View>
|
|
))}
|
|
{order.items.length > 3 && (
|
|
<View style={tw`w-8 h-8 rounded-lg bg-amber-100 border border-amber-200 items-center justify-center ml-2`}>
|
|
<MyText style={tw`text-[9px] font-bold text-amber-700`}>+{order.items.length - 3}</MyText>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<MyText style={tw`text-xs text-amber-700 font-medium ml-2`}>Track Order</MyText>
|
|
<Ionicons name="chevron-forward" size={14} color="#D97706" style={tw`ml-auto`} />
|
|
</View>
|
|
)}
|
|
|
|
{/* Support Mobile Number */}
|
|
{supportMobile && (
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center mt-3 pt-3 border-t border-amber-200`}
|
|
onPress={() => Linking.openURL(`tel:${supportMobile}`)}
|
|
>
|
|
<MaterialIcons name="phone-in-talk" size={14} color="#D97706" style={tw`mr-1.5`} />
|
|
<MyText style={tw`text-xs text-amber-700 font-medium`}>
|
|
Need help? Call {supportMobile}
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
export default function NextOrderGlimpse() {
|
|
const router = useRouter();
|
|
const { setNavigationTarget } = useNavigationTarget();
|
|
const userDetails = useUserDetails();
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
|
|
const { data: ordersData, isLoading: ordersLoading } = trpc.user.order.getOrders.useQuery({
|
|
page: 1,
|
|
pageSize: 50,
|
|
}, {
|
|
enabled: !!userDetails,
|
|
});
|
|
|
|
const { data: essentialConsts } = useGetEssentialConsts();
|
|
|
|
const allOrders: Order[] = ordersData?.data || [];
|
|
|
|
const upcomingOrders = allOrders.filter(order => {
|
|
if (order.orderStatus.toLowerCase() === 'cancelled') return false;
|
|
if (order.deliveryStatus.toLowerCase() === 'success') return false;
|
|
|
|
return true;
|
|
});
|
|
|
|
upcomingOrders.sort((a, b) => {
|
|
if (a.isFlashDelivery && !b.isFlashDelivery) return -1;
|
|
if (!a.isFlashDelivery && b.isFlashDelivery) return 1;
|
|
|
|
if (a.isFlashDelivery && b.isFlashDelivery) {
|
|
return dayjs(b.createdAt).diff(dayjs(a.createdAt));
|
|
}
|
|
|
|
if (a.deliveryDate && b.deliveryDate) {
|
|
return dayjs(a.deliveryDate).diff(dayjs(b.deliveryDate));
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
const index = Math.round(event.nativeEvent.contentOffset.x / SNAP_INTERVAL);
|
|
setCurrentIndex(Math.min(Math.max(index, 0), upcomingOrders.length - 1));
|
|
}, [upcomingOrders.length]);
|
|
|
|
const handleOrderPress = useCallback((order: Order) => {
|
|
setNavigationTarget(`/(drawer)/(tabs)/me/my-orders/${order.id}`);
|
|
router.replace('/(drawer)/(tabs)/me');
|
|
}, [router, setNavigationTarget]);
|
|
|
|
if (!userDetails) {
|
|
return null;
|
|
}
|
|
|
|
if (ordersLoading) {
|
|
return (
|
|
<View style={tw`px-6 mb-4`}>
|
|
<View style={tw`bg-white rounded-2xl p-4 border border-gray-100`}>
|
|
<ActivityIndicator size="small" color="#3B82F6" />
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (upcomingOrders.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<View style={tw`mb-4`}>
|
|
<ScrollView
|
|
ref={scrollViewRef}
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
decelerationRate="fast"
|
|
snapToInterval={SNAP_INTERVAL}
|
|
contentContainerStyle={tw`px-6`}
|
|
onScroll={handleScroll}
|
|
onMomentumScrollEnd={handleScroll}
|
|
scrollEventThrottle={16}
|
|
>
|
|
{upcomingOrders.map((order) => (
|
|
<OrderCard
|
|
key={order.id}
|
|
order={order}
|
|
onPress={() => handleOrderPress(order)}
|
|
supportMobile={essentialConsts?.supportMobile}
|
|
/>
|
|
))}
|
|
</ScrollView>
|
|
|
|
{/* Pagination Dots */}
|
|
{upcomingOrders.length > 1 && (
|
|
<View style={tw`flex-row justify-center mt-2`}>
|
|
{upcomingOrders.map((_, index) => (
|
|
<View
|
|
key={index}
|
|
style={tw`mx-1 w-2 h-2 rounded-full ${index === currentIndex ? 'bg-amber-500' : 'bg-amber-200'}`}
|
|
/>
|
|
))}
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|