836 lines
No EOL
34 KiB
TypeScript
836 lines
No EOL
34 KiB
TypeScript
import React, { useState , useEffect } from 'react';
|
|
import { View, TouchableOpacity, Alert, TextInput, ActivityIndicator } from 'react-native';
|
|
import { AppContainer, MyText, tw, MyFlatList, BottomDialog, BottomDropdown, Checkbox, theme, MyTextInput } from 'common-ui';
|
|
import { trpc } from '../../../src/trpc-client';
|
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
|
import dayjs from 'dayjs';
|
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
import { Entypo } from '@expo/vector-icons';
|
|
import CancelOrderDialog from '@/components/CancelOrderDialog';
|
|
|
|
const AdminNotesForm = ({ orderId, existingNotes, onClose, refetch }: { orderId: string; existingNotes?: string | null; onClose: () => void; refetch: () => void }) => {
|
|
const [notesText, setNotesText] = useState(existingNotes || '');
|
|
const updateNotesMutation = trpc.admin.order.updateNotes.useMutation();
|
|
|
|
return (
|
|
<View style={tw`p-4`}>
|
|
<MyText style={tw`text-lg font-bold mb-4`}>Admin Notes</MyText>
|
|
<TextInput
|
|
style={tw`border border-gray-300 rounded p-3 h-24 text-base`}
|
|
multiline
|
|
value={notesText}
|
|
onChangeText={setNotesText}
|
|
placeholder="Enter admin notes..."
|
|
/>
|
|
<View style={tw`flex-row mt-4`}>
|
|
<TouchableOpacity
|
|
style={tw`flex-1 bg-blue-500 p-3 rounded ml-2`}
|
|
onPress={() => {
|
|
updateNotesMutation.mutate(
|
|
{ orderId: parseInt(orderId), adminNotes: notesText },
|
|
{
|
|
onSuccess: () => {
|
|
onClose();
|
|
Alert.alert('Success', 'Notes updated successfully');
|
|
refetch();
|
|
},
|
|
onError: (error: any) => {
|
|
Alert.alert('Error', error.message || 'Failed to update notes');
|
|
},
|
|
}
|
|
);
|
|
}}
|
|
>
|
|
<MyText style={tw`text-center text-white font-semibold`}>Save</MyText>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
|
|
interface OrderType {
|
|
id: number;
|
|
orderId: string;
|
|
readableId: number;
|
|
customerName: string | null;
|
|
address: string;
|
|
totalAmount: number;
|
|
deliveryCharge: number;
|
|
items: {
|
|
id?: number;
|
|
name: string;
|
|
quantity: number;
|
|
price: number;
|
|
amount: number;
|
|
unit: string;
|
|
isPackaged?: boolean;
|
|
isPackageVerified?: boolean;
|
|
}[];
|
|
createdAt: string;
|
|
deliveryTime: string | null;
|
|
status: 'pending' | 'delivered' | 'cancelled';
|
|
isPackaged: boolean;
|
|
isDelivered: boolean;
|
|
isCod: boolean;
|
|
isFlashDelivery: boolean;
|
|
couponCode?: string;
|
|
couponDescription?: string;
|
|
discountAmount?: number;
|
|
adminNotes?: string | null;
|
|
userNotes?: string | null;
|
|
}
|
|
|
|
const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }) => {
|
|
const id = order.orderId;
|
|
const router = useRouter();
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const [itemsDialogOpen, setItemsDialogOpen] = useState(false);
|
|
const [notesDialogOpen, setNotesDialogOpen] = useState(false);
|
|
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
|
const [userNotesDialogOpen, setUserNotesDialogOpen] = useState(false);
|
|
const [adminNotesDialogOpen, setAdminNotesDialogOpen] = useState(false);
|
|
const [updatingItems, setUpdatingItems] = useState<Set<number>>(new Set());
|
|
|
|
const updatePackagedMutation = trpc.admin.order.updatePackaged.useMutation();
|
|
const updateDeliveredMutation = trpc.admin.order.updateDelivered.useMutation();
|
|
const updateItemPackagingMutation = trpc.admin.order.updateOrderItemPackaging.useMutation();
|
|
|
|
const handleOrderPress = () => {
|
|
router.push(`/order-details/${order.orderId}` as any);
|
|
};
|
|
|
|
const handleMenuOption = () => {
|
|
setMenuOpen(false);
|
|
router.push(`/order-details/${order.orderId}` as any);
|
|
};
|
|
|
|
const handleMarkPackaged = (isPackaged: boolean) => {
|
|
updatePackagedMutation.mutate(
|
|
{ orderId: order.orderId.toString(), isPackaged },
|
|
{
|
|
onSuccess: () => {
|
|
setMenuOpen(false);
|
|
refetch();
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleMarkDelivered = (isDelivered: boolean) => {
|
|
updateDeliveredMutation.mutate(
|
|
{ orderId: order.orderId.toString(), isDelivered },
|
|
{
|
|
onSuccess: () => {
|
|
setMenuOpen(false);
|
|
refetch();
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleItemPackagingToggle = (itemId: number, field: 'isPackaged' | 'isPackageVerified', value: boolean) => {
|
|
setUpdatingItems(prev => new Set(prev).add(itemId));
|
|
|
|
updateItemPackagingMutation.mutate(
|
|
{ orderItemId: itemId, [field]: value },
|
|
{
|
|
onSuccess: () => {
|
|
setUpdatingItems(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(itemId);
|
|
return newSet;
|
|
});
|
|
refetch();
|
|
},
|
|
onError: (error: any) => {
|
|
setUpdatingItems(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(itemId);
|
|
return newSet;
|
|
});
|
|
Alert.alert("Error", error.message || "Failed to update packaging status");
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'delivered': return 'bg-green-100 text-green-800';
|
|
case 'cancelled': return 'bg-red-100 text-red-800';
|
|
default: return 'bg-yellow-100 text-yellow-800';
|
|
}
|
|
};
|
|
|
|
if(order.id === 162)
|
|
console.log({order})
|
|
|
|
return (
|
|
<>
|
|
<TouchableOpacity
|
|
style={tw`bg-white mx-4 mb-4 rounded-xl shadow-sm border border-gray-100 overflow-hidden`}
|
|
onPress={handleOrderPress}
|
|
activeOpacity={0.9}
|
|
>
|
|
{/* Header Section */}
|
|
<View style={tw`p-4 border-b border-gray-100 bg-gray-50/50`}>
|
|
<View style={tw`flex-row justify-between items-start`}>
|
|
<View style={tw`flex-1`}>
|
|
<View style={tw`flex-row items-center mb-1`}>
|
|
<MyText style={tw`font-bold text-lg text-gray-900 mr-2`}>
|
|
{order.customerName || 'Unknown Customer'}
|
|
</MyText>
|
|
<View style={tw`bg-gray-200 px-2 py-0.5 rounded mr-2`}>
|
|
<MyText style={tw`text-xs font-medium text-gray-600`}>#{order.readableId}</MyText>
|
|
</View>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`bg-amber-100 px-2 py-0.5 rounded-full border border-amber-200 flex-row items-center`}>
|
|
<MaterialIcons name="bolt" size={12} color="#D97706" />
|
|
<MyText style={tw`text-xs font-bold text-amber-700 ml-1`}>FLASH</MyText>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<View style={tw`flex-row items-center`}>
|
|
<MaterialIcons name="access-time" size={12} color="#6B7280" />
|
|
<MyText style={tw`text-xs text-gray-500 ml-1`}>
|
|
{dayjs(order.createdAt).format('MMM D, h:mm A')}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
onPress={() => setMenuOpen(true)}
|
|
style={tw`p-2 -mr-2 -mt-2 rounded-full`}
|
|
>
|
|
<Entypo name="dots-three-vertical" size={16} color="#9CA3AF" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Main Content */}
|
|
<View style={tw`p-4`}>
|
|
{/* Status Badges */}
|
|
<View style={tw`flex-row flex-wrap gap-4 mb-4`}>
|
|
{/* <View style={tw`px-2.5 py-1 rounded-full ${getStatusColor(order.status)}`}>
|
|
<MyText style={tw`text-xs font-semibold capitalize`}>{order.status}</MyText>
|
|
</View> */}
|
|
{/* {order.isCod && (
|
|
<View style={tw`px-2.5 py-1 rounded-full bg-blue-50 border border-blue-100`}>
|
|
<MyText style={tw`text-xs font-semibold text-blue-700`}>COD</MyText>
|
|
</View>
|
|
)} */}
|
|
<View style={tw`flex-row items-center gap-1`}>
|
|
<MyText style={tw`text-sm font-semibold text-gray-600`}>Packaged</MyText>
|
|
<Checkbox
|
|
checked={order.isPackaged}
|
|
// onPress={() => handleMarkPackaged(!order.isPackaged)}
|
|
onPress={() => {}}
|
|
size={18}
|
|
fillColor={theme.colors.gray500}
|
|
checkColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
<View style={tw`flex-row items-center gap-1`}>
|
|
<MyText style={tw`text-xs font-semibold text-gray-600`}>Delivered</MyText>
|
|
<Checkbox
|
|
checked={order.isDelivered}
|
|
onPress={() => handleMarkDelivered(!order.isDelivered)}
|
|
size={18}
|
|
fillColor="#10B981"
|
|
checkColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Delivery Info */}
|
|
<View style={tw`flex-row items-start mb-4 bg-blue-50/50 p-3 rounded-lg`}>
|
|
<MaterialIcons name="location-pin" size={18} color="#3B82F6" style={tw`mt-0.5`} />
|
|
<View style={tw`ml-2 flex-1`}>
|
|
<MyText style={tw`text-xs font-bold text-blue-800 mb-0.5 uppercase tracking-wide`}>Delivery Address</MyText>
|
|
<MyText style={tw`text-sm text-gray-700 leading-5`} numberOfLines={2}>
|
|
{order.address}
|
|
</MyText>
|
|
<View style={tw`flex-row items-center mt-2`}>
|
|
<MaterialIcons name="event" size={14} color="#6B7280" />
|
|
<MyText style={tw`text-xs text-gray-600 ml-1`}>
|
|
{order.isFlashDelivery ? "Flash Delivery:" : "Slot:"} {order.isFlashDelivery ? dayjs(order.createdAt).add(30, 'minutes').format('MMM D, h:mm A') : order.deliveryTime ? dayjs(order.deliveryTime).format("ddd, MMM D • h:mm A") : 'Not scheduled'}
|
|
</MyText>
|
|
</View>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`flex-row items-center mt-1 bg-amber-50 px-2 py-1 rounded`}>
|
|
<MaterialIcons name="bolt" size={12} color="#D97706" />
|
|
<MyText style={tw`text-xs text-amber-700 ml-1 font-medium`}>
|
|
30-Minute Delivery • High Priority
|
|
</MyText>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Items Summary & Total */}
|
|
<View style={tw`mb-4`}>
|
|
<View style={tw`mb-2`}>
|
|
<View style={tw`flex-row justify-between items-center`}>
|
|
<TouchableOpacity
|
|
onPress={() => setItemsDialogOpen(true)}
|
|
style={tw`flex-row items-center py-2 px-3 bg-blue-50 rounded-lg flex-1 mr-3`}
|
|
>
|
|
<MaterialIcons name="shopping-cart" size={16} color="#3B82F6" />
|
|
<MyText style={tw`text-sm font-medium text-blue-700 ml-2`}>
|
|
{order.items.length} {order.items.length === 1 ? 'item' : 'items'}
|
|
</MyText>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`ml-2 bg-amber-100 px-1.5 py-0.5 rounded-full`}>
|
|
<MyText style={tw`text-xs font-bold text-amber-700`}>⚡</MyText>
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
<View style={tw`flex-row items-center`}>
|
|
<MyText style={tw`text-base font-semibold text-gray-900 mr-2`}>Total:</MyText>
|
|
<MyText style={tw`text-lg font-bold ${order.isFlashDelivery ? 'text-amber-700' : 'text-gray-900'}`}>₹{order.totalAmount}</MyText>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Coupons */}
|
|
{order.couponCode && (
|
|
<View style={tw`mb-4`}>
|
|
<MyText style={tw`text-xs font-bold text-gray-500 mb-2 uppercase tracking-wide`}>Applied Coupons</MyText>
|
|
<View style={tw`bg-pink-50 border border-pink-200 rounded-lg p-3`}>
|
|
<MyText style={tw`text-sm text-pink-800 font-medium mb-1`}>
|
|
{order.couponCode}
|
|
</MyText>
|
|
{order.couponDescription && (
|
|
<MyText style={tw`text-xs text-pink-600 mb-2`}>
|
|
{order.couponDescription}
|
|
</MyText>
|
|
)}
|
|
{order.discountAmount && (
|
|
<MyText style={tw`text-sm font-bold text-pink-800`}>
|
|
Discount: ₹{order.discountAmount}
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Notes Section */}
|
|
<View style={tw`flex-row gap-2`}>
|
|
{order.userNotes && (
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-3 bg-amber-50 rounded-lg flex-1`}
|
|
onPress={() => setUserNotesDialogOpen(true)}
|
|
>
|
|
<MaterialIcons name="note" size={18} color="#D97706" />
|
|
<MyText style={tw`text-amber-800 font-medium ml-2`}>
|
|
User Notes
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
)}
|
|
{order.adminNotes && (
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-3 bg-blue-50 rounded-lg flex-1`}
|
|
onPress={() => setNotesDialogOpen(true)}
|
|
>
|
|
<MaterialIcons name="admin-panel-settings" size={18} color="#2563EB" />
|
|
<MyText style={tw`text-blue-800 font-medium ml-2`}>
|
|
Admin Notes
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
{/* Footer / Delivery Charge */}
|
|
{order.deliveryCharge > 0 && (
|
|
<View style={tw`pt-3 border-t border-gray-100`}>
|
|
<View style={tw`flex-row justify-between items-center`}>
|
|
<MyText style={tw`text-sm text-gray-500`}>Delivery Charge</MyText>
|
|
<MyText style={tw`text-sm text-gray-900`}>₹{order.deliveryCharge}</MyText>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
|
|
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
|
<View style={tw`p-6`}>
|
|
<MyText style={tw`text-lg font-bold text-gray-800 mb-4`}>
|
|
Order Options
|
|
</MyText>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`bg-amber-50 border border-amber-200 rounded-lg p-3 mb-4 flex-row items-center`}>
|
|
<MaterialIcons name="bolt" size={20} color="#D97706" />
|
|
<View style={tw`ml-3 flex-1`}>
|
|
<MyText style={tw`text-sm font-bold text-amber-900`}>Flash Delivery Order</MyText>
|
|
<MyText style={tw`text-xs text-amber-700`}>
|
|
Deliver within 30 minutes • High Priority
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
)}
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
|
onPress={() => handleMarkPackaged(!order.isPackaged)}
|
|
>
|
|
<Entypo name="box" size={20} color="#6B7280" />
|
|
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
|
{order.isPackaged ? 'Mark Not Packaged' : 'Mark Packaged'}
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
{order.isPackaged && (
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
|
onPress={() => handleMarkDelivered(!order.isDelivered)}
|
|
>
|
|
<Entypo name="location" size={20} color="#6B7280" />
|
|
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
|
{order.isDelivered ? 'Mark Not Delivered' : 'Mark Delivered'}
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
)}
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
|
onPress={handleMenuOption}
|
|
>
|
|
<Entypo name="info-with-circle" size={20} color="#6B7280" />
|
|
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
|
Order Details
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-4 bg-gray-50 rounded-lg mb-3`}
|
|
onPress={() => {
|
|
setMenuOpen(false);
|
|
setNotesDialogOpen(true);
|
|
}}
|
|
>
|
|
<Entypo name="edit" size={20} color="#6B7280" />
|
|
<MyText style={tw`text-gray-800 font-medium ml-3`}>
|
|
Admin Notes
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
{order.status !== 'cancelled' && (
|
|
<TouchableOpacity
|
|
style={tw`flex-row items-center p-4 bg-red-50 rounded-lg`}
|
|
onPress={() => {
|
|
setMenuOpen(false);
|
|
setCancelDialogOpen(true);
|
|
}}
|
|
>
|
|
<MaterialIcons name="cancel" size={20} color="#DC2626" />
|
|
<MyText style={tw`text-red-700 font-medium ml-3`}>
|
|
Cancel Order
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</BottomDialog>
|
|
|
|
<BottomDialog open={itemsDialogOpen} onClose={() => setItemsDialogOpen(false)}>
|
|
<View style={tw`py-6`}>
|
|
<View style={tw`flex-row items-center justify-between mb-4`}>
|
|
<MyText style={tw`text-lg font-bold text-gray-800`}>
|
|
Order Items
|
|
</MyText>
|
|
{order.isFlashDelivery && (
|
|
<View style={tw`bg-amber-100 px-2 py-1 rounded-full border border-amber-200 flex-row items-center`}>
|
|
<MaterialIcons name="bolt" size={14} color="#D97706" />
|
|
<MyText style={tw`text-xs font-bold text-amber-700 ml-1`}>FLASH</MyText>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<MyText style={tw`text-sm text-gray-600 mb-6`}>
|
|
Total: ₹{order.totalAmount}
|
|
</MyText>
|
|
{order.items.map((item, idx) => (
|
|
<View key={idx} style={tw`py-2 border-b border-gray-50 last:border-0`}>
|
|
<View style={tw`flex-row items-center`}>
|
|
<View style={tw`bg-gray-100 px-2 py-1 rounded items-center justify-center mr-2`}>
|
|
<MyText style={tw`text-xs font-bold text-gray-600`}>{item.quantity} {item.unit}</MyText>
|
|
</View>
|
|
<MyText style={tw`text-sm text-gray-800 flex-1`} numberOfLines={1} ellipsizeMode="tail">
|
|
{item.name.length > 30 ? `${item.name.substring(0, 30)}...` : item.name}
|
|
</MyText>
|
|
{item.isPackaged !== undefined && item.isPackageVerified !== undefined && (
|
|
<>
|
|
<View style={tw`flex-row items-center gap-1 mr-3`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-600`}>pkg</MyText>
|
|
<Checkbox
|
|
checked={item.isPackaged}
|
|
onPress={() => handleItemPackagingToggle(item.id!, 'isPackaged', !item.isPackaged)}
|
|
size={18}
|
|
fillColor={updatingItems.has(item.id!) ? "#F59E0B" : "#10B981"}
|
|
checkColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
<View style={tw`flex-row items-center gap-1`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-600`}>verf</MyText>
|
|
<Checkbox
|
|
checked={item.isPackageVerified}
|
|
onPress={() => handleItemPackagingToggle(item.id!, 'isPackageVerified', !item.isPackageVerified)}
|
|
size={18}
|
|
fillColor={updatingItems.has(item.id!) ? "#F59E0B" : "#10B981"}
|
|
checkColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
{updatingItems.has(item.id!) && (
|
|
<ActivityIndicator size="small" color="#F59E0B" style={tw`ml-1`} />
|
|
)}
|
|
</>
|
|
)}
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</BottomDialog>
|
|
|
|
<BottomDialog open={notesDialogOpen} onClose={() => setNotesDialogOpen(false)}>
|
|
<AdminNotesForm orderId={order.orderId} existingNotes={order.adminNotes} onClose={() => setNotesDialogOpen(false)} refetch={refetch} />
|
|
</BottomDialog>
|
|
|
|
<CancelOrderDialog
|
|
orderId={order.id}
|
|
open={cancelDialogOpen}
|
|
onClose={() => setCancelDialogOpen(false)}
|
|
onSuccess={refetch}
|
|
/>
|
|
|
|
<BottomDialog open={userNotesDialogOpen} onClose={() => setUserNotesDialogOpen(false)}>
|
|
<View style={tw`p-6`}>
|
|
<MyText style={tw`text-lg font-bold text-gray-800 mb-4`}>
|
|
User Notes
|
|
</MyText>
|
|
<View style={tw`bg-amber-50 p-4 rounded-lg border border-amber-200`}>
|
|
<MyText style={tw`text-sm text-amber-900 leading-5`}>
|
|
{order.userNotes}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
</BottomDialog>
|
|
|
|
<BottomDialog open={adminNotesDialogOpen} onClose={() => setAdminNotesDialogOpen(false)}>
|
|
<View style={tw`p-6`}>
|
|
<MyText style={tw`text-lg font-bold text-gray-800 mb-4`}>
|
|
Admin Notes
|
|
</MyText>
|
|
<View style={tw`bg-blue-50 p-4 rounded-lg border border-blue-200`}>
|
|
<MyText style={tw`text-sm text-blue-900 leading-5`}>
|
|
{order.adminNotes}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
</BottomDialog>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default function Orders() {
|
|
const router = useRouter();
|
|
const { filter } = useLocalSearchParams<{ filter?: string }>();
|
|
const [selectedSlot, setSelectedSlot] = useState<number | null>(null);
|
|
const [selectedSlotType, setSelectedSlotType] = useState<'slot' | 'flash' | null>(null);
|
|
const [packagedFilter, setPackagedFilter] = useState<'all' | 'packaged' | 'not_packaged'>('all');
|
|
const [packagedChecked, setPackagedChecked] = useState(false);
|
|
const [notPackagedChecked, setNotPackagedChecked] = useState(false);
|
|
const [deliveredFilter, setDeliveredFilter] = useState<'all' | 'delivered' | 'not_delivered'>('all');
|
|
const [deliveredChecked, setDeliveredChecked] = useState(false);
|
|
const [notDeliveredChecked, setNotDeliveredChecked] = useState(false);
|
|
const [cancellationFilter, setCancellationFilter] = useState<'all' | 'cancelled' | 'not_cancelled'>('all');
|
|
const [cancelledChecked, setCancelledChecked] = useState(false);
|
|
const [notCancelledChecked, setNotCancelledChecked] = useState(false);
|
|
const [flashDeliveryFilter, setFlashDeliveryFilter] = useState<'all' | 'flash' | 'regular'>('all');
|
|
const [flashChecked, setFlashChecked] = useState(false);
|
|
const [regularChecked, setRegularChecked] = useState(false);
|
|
const [filterDialogOpen, setFilterDialogOpen] = useState(false);
|
|
|
|
// Handle initial filter from URL params
|
|
useEffect(() => {
|
|
if (filter === 'flash') {
|
|
setSelectedSlotType('flash');
|
|
setFlashDeliveryFilter('flash');
|
|
setFlashChecked(true);
|
|
setRegularChecked(false);
|
|
}
|
|
}, [filter]);
|
|
const { data: slotsData } = trpc.admin.slots.getAll.useQuery();
|
|
const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage, refetch } = trpc.admin.order.getAll.useInfiniteQuery(
|
|
{
|
|
limit: 20,
|
|
slotId: selectedSlotType === 'slot' ? selectedSlot : null,
|
|
packagedFilter,
|
|
deliveredFilter,
|
|
cancellationFilter,
|
|
flashDeliveryFilter: selectedSlotType === 'flash' ? 'flash' : flashDeliveryFilter
|
|
},
|
|
{
|
|
getNextPageParam: (lastPage) => lastPage?.nextCursor,
|
|
}
|
|
);
|
|
|
|
const orders = data?.pages.flatMap(page => page?.orders) || [];
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={tw`flex-1 justify-center items-center bg-white`}>
|
|
<ActivityIndicator size="large" color="#3B82F6" />
|
|
<MyText style={tw`text-gray-600 mt-4`}>Loading orders...</MyText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const slotOptions = [
|
|
{ label: '⚡ Flash Deliveries', value: 'flash' },
|
|
...(slotsData?.slots?.map(slot => ({
|
|
label: dayjs(slot.deliveryTime).format('ddd DD MMM, h:mm a'),
|
|
value: slot.id.toString(),
|
|
})) || [])
|
|
];
|
|
|
|
|
|
return (
|
|
<>
|
|
<MyFlatList
|
|
data={orders}
|
|
keyExtractor={(item) => item!.orderId}
|
|
renderItem={({ item }) => item ? <OrderItem order={item} refetch={refetch} /> : null}
|
|
onEndReached={() => {
|
|
if (hasNextPage && !isFetchingNextPage) {
|
|
fetchNextPage();
|
|
}
|
|
}}
|
|
onEndReachedThreshold={0.5}
|
|
onRefresh={() => refetch()}
|
|
ListHeaderComponent={
|
|
<>
|
|
<View style={tw`flex-row justify-between items-center p-4 bg-white`}>
|
|
<View style={tw`flex-1 mr-4`}>
|
|
<BottomDropdown
|
|
label="Select Slot"
|
|
options={slotOptions}
|
|
value={selectedSlotType === 'flash' ? 'flash' : (selectedSlot?.toString() || '')}
|
|
onValueChange={(val) => {
|
|
if (val === 'flash') {
|
|
setSelectedSlotType('flash');
|
|
setSelectedSlot(null);
|
|
setFlashDeliveryFilter('flash');
|
|
// Reset other filters when switching to flash
|
|
setPackagedFilter('all');
|
|
setPackagedChecked(false);
|
|
setNotPackagedChecked(false);
|
|
setDeliveredFilter('all');
|
|
setDeliveredChecked(false);
|
|
setNotDeliveredChecked(false);
|
|
setCancellationFilter('all');
|
|
setCancelledChecked(false);
|
|
setNotCancelledChecked(false);
|
|
} else {
|
|
setSelectedSlotType('slot');
|
|
setSelectedSlot(val ? Number(val) : null);
|
|
setFlashDeliveryFilter('all');
|
|
}
|
|
}}
|
|
placeholder="All slots"
|
|
/>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={() => setFilterDialogOpen(true)}
|
|
style={tw`p-2`}
|
|
>
|
|
<MaterialIcons name="filter-list" size={24} color="#6b7280" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
{!isLoading && selectedSlotType && (
|
|
<View style={tw`bg-gray-50 p-3 border-b border-gray-200`}>
|
|
<MyText style={tw`text-center text-gray-600`}>
|
|
{selectedSlotType === 'flash'
|
|
? `${orders.length} Flash delivery orders`
|
|
: `${orders.length} Orders in slot`
|
|
}
|
|
</MyText>
|
|
</View>
|
|
)}
|
|
</>
|
|
}
|
|
ListFooterComponent={
|
|
isFetchingNextPage ? (
|
|
<View style={tw`py-4 items-center flex-row justify-center`}>
|
|
<ActivityIndicator size="small" color="#3B82F6" />
|
|
<MyText style={tw`text-gray-600 ml-2`}>Loading more...</MyText>
|
|
</View>
|
|
) : null
|
|
}
|
|
/>
|
|
|
|
<BottomDialog open={filterDialogOpen} onClose={() => setFilterDialogOpen(false)}>
|
|
<AppContainer>
|
|
<View style={tw`mt-4`}>
|
|
<MyText style={tw`text-lg font-semibold mb-2`}>Packaged Status</MyText>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<Checkbox
|
|
checked={packagedChecked}
|
|
onPress={() => {
|
|
const newValue = !packagedChecked;
|
|
setPackagedChecked(newValue);
|
|
if (newValue && notPackagedChecked) {
|
|
setPackagedFilter('all');
|
|
} else if (newValue) {
|
|
setPackagedFilter('packaged');
|
|
} else if (notPackagedChecked) {
|
|
setPackagedFilter('not_packaged');
|
|
} else {
|
|
setPackagedFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Packaged</MyText>
|
|
</View>
|
|
<View style={tw`flex-row items-center`}>
|
|
<Checkbox
|
|
checked={notPackagedChecked}
|
|
onPress={() => {
|
|
const newValue = !notPackagedChecked;
|
|
setNotPackagedChecked(newValue);
|
|
if (packagedChecked && newValue) {
|
|
setPackagedFilter('all');
|
|
} else if (newValue) {
|
|
setPackagedFilter('not_packaged');
|
|
} else if (packagedChecked) {
|
|
setPackagedFilter('packaged');
|
|
} else {
|
|
setPackagedFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Not Packaged</MyText>
|
|
</View>
|
|
</View>
|
|
<View style={tw`mt-6`}>
|
|
<MyText style={tw`text-lg font-semibold mb-2`}>Delivered Status</MyText>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<Checkbox
|
|
checked={deliveredChecked}
|
|
onPress={() => {
|
|
const newValue = !deliveredChecked;
|
|
setDeliveredChecked(newValue);
|
|
if (newValue && notDeliveredChecked) {
|
|
setDeliveredFilter('all');
|
|
} else if (newValue) {
|
|
setDeliveredFilter('delivered');
|
|
} else if (notDeliveredChecked) {
|
|
setDeliveredFilter('not_delivered');
|
|
} else {
|
|
setDeliveredFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Delivered</MyText>
|
|
</View>
|
|
<View style={tw`flex-row items-center`}>
|
|
<Checkbox
|
|
checked={notDeliveredChecked}
|
|
onPress={() => {
|
|
const newValue = !notDeliveredChecked;
|
|
setNotDeliveredChecked(newValue);
|
|
if (deliveredChecked && newValue) {
|
|
setDeliveredFilter('all');
|
|
} else if (newValue) {
|
|
setDeliveredFilter('not_delivered');
|
|
} else if (deliveredChecked) {
|
|
setDeliveredFilter('delivered');
|
|
} else {
|
|
setDeliveredFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Not Delivered</MyText>
|
|
</View>
|
|
</View>
|
|
<View style={tw`mt-6`}>
|
|
<MyText style={tw`text-lg font-semibold mb-2`}>Cancellation Status</MyText>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<Checkbox
|
|
checked={cancelledChecked}
|
|
onPress={() => {
|
|
const newValue = !cancelledChecked;
|
|
setCancelledChecked(newValue);
|
|
if (newValue && notCancelledChecked) {
|
|
setCancellationFilter('all');
|
|
} else if (newValue) {
|
|
setCancellationFilter('cancelled');
|
|
} else if (notCancelledChecked) {
|
|
setCancellationFilter('not_cancelled');
|
|
} else {
|
|
setCancellationFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Cancelled</MyText>
|
|
</View>
|
|
<View style={tw`flex-row items-center`}>
|
|
<Checkbox
|
|
checked={notCancelledChecked}
|
|
onPress={() => {
|
|
const newValue = !notCancelledChecked;
|
|
setNotCancelledChecked(newValue);
|
|
if (cancelledChecked && newValue) {
|
|
setCancellationFilter('all');
|
|
} else if (newValue) {
|
|
setCancellationFilter('not_cancelled');
|
|
} else if (cancelledChecked) {
|
|
setCancellationFilter('cancelled');
|
|
} else {
|
|
setCancellationFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Not Cancelled</MyText>
|
|
</View>
|
|
</View>
|
|
<View style={tw`mt-6`}>
|
|
<MyText style={tw`text-lg font-semibold mb-2`}>Delivery Type</MyText>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<Checkbox
|
|
checked={flashChecked}
|
|
onPress={() => {
|
|
const newValue = !flashChecked;
|
|
setFlashChecked(newValue);
|
|
if (newValue && regularChecked) {
|
|
setFlashDeliveryFilter('all');
|
|
} else if (newValue) {
|
|
setFlashDeliveryFilter('flash');
|
|
} else if (regularChecked) {
|
|
setFlashDeliveryFilter('regular');
|
|
} else {
|
|
setFlashDeliveryFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>⚡ Flash Delivery</MyText>
|
|
</View>
|
|
<View style={tw`flex-row items-center`}>
|
|
<Checkbox
|
|
checked={regularChecked}
|
|
onPress={() => {
|
|
const newValue = !regularChecked;
|
|
setRegularChecked(newValue);
|
|
if (flashChecked && newValue) {
|
|
setFlashDeliveryFilter('all');
|
|
} else if (newValue) {
|
|
setFlashDeliveryFilter('regular');
|
|
} else if (flashChecked) {
|
|
setFlashDeliveryFilter('flash');
|
|
} else {
|
|
setFlashDeliveryFilter('all');
|
|
}
|
|
}}
|
|
/>
|
|
<MyText style={tw`ml-2`}>Regular Delivery</MyText>
|
|
</View>
|
|
</View>
|
|
</AppContainer>
|
|
</BottomDialog>
|
|
</>
|
|
);
|
|
} |