freshyo/apps/admin-ui/app/(drawer)/rebalance-orders/index.tsx
2026-01-24 00:13:15 +05:30

239 lines
No EOL
8.8 KiB
TypeScript

import React, { useState } from 'react';
import { View, TouchableOpacity, Alert, FlatList } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { MyText, tw, MyTouchableOpacity, MyFlatList, BottomDialog } from 'common-ui';
import { trpc } from '../../../src/trpc-client';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
interface SlotItemProps {
item: any;
selectedSlots: number[];
toggleSlotSelection: (slotId: number) => void;
setDialogProducts: React.Dispatch<React.SetStateAction<any[]>>;
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const SlotItemComponent: React.FC<SlotItemProps> = ({
item: slot,
selectedSlots,
toggleSlotSelection,
setDialogProducts,
setDialogOpen,
}) => {
const isSelected = selectedSlots.includes(slot.id);
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
const displayProducts = slotProducts.slice(0, 2).join(', ');
const isActive = slot.isActive;
const statusColor = isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
const statusText = isActive ? 'Active' : 'Inactive';
return (
<TouchableOpacity
onPress={() => toggleSlotSelection(slot.id)}
activeOpacity={0.7}
style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}
>
{/* Header: Checkbox, ID and Status */}
<View style={tw`flex-row justify-between items-center mb-4`}>
<View style={tw`flex-row items-center`}>
<TouchableOpacity onPress={() => toggleSlotSelection(slot.id)} style={tw`mr-3`}>
<MaterialCommunityIcons
name={isSelected ? "checkbox-marked" : "checkbox-blank-outline"}
size={24}
color={isSelected ? "#F83758" : "#6b7280"}
/>
</TouchableOpacity>
<View style={tw`w-10 h-10 bg-pink2 rounded-full items-center justify-center mr-3`}>
<MaterialCommunityIcons name="calendar-clock" size={20} color="#F83758" />
</View>
<View>
<MyText style={tw`text-lg font-bold text-gray-900`}>Slot #{slot.id}</MyText>
<MyText style={tw`text-xs text-gray-500`}>ID: {slot.id}</MyText>
</View>
</View>
<View style={tw`flex-row items-center`}>
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
</View>
</View>
</View>
{/* Divider */}
<View style={tw`h-[1px] bg-gray-100 mb-4`} />
{/* Details Grid */}
<View style={tw`flex-row flex-wrap`}>
{/* Delivery Time */}
<View style={tw`w-1/2 mb-4 pr-2`}>
<View style={tw`flex-row items-center mb-1`}>
<MaterialCommunityIcons name="truck-delivery-outline" size={14} color="#6b7280" style={tw`mr-1`} />
<MyText style={tw`text-xs text-gray-500 uppercase font-semibold tracking-wider`}>Delivery</MyText>
</View>
<MyText style={tw`text-sm font-medium text-gray-800`}>
{dayjs(slot.deliveryTime).format('DD MMM, h:mm A')}
</MyText>
</View>
{/* Freeze Time */}
<View style={tw`w-1/2 mb-4 pl-2`}>
<View style={tw`flex-row items-center mb-1`}>
<MaterialCommunityIcons name="snowflake" size={14} color="#6b7280" style={tw`mr-1`} />
<MyText style={tw`text-xs text-gray-500 uppercase font-semibold tracking-wider`}>Freeze</MyText>
</View>
<MyText style={tw`text-sm font-medium text-gray-800`}>
{dayjs(slot.freezeTime).format('DD MMM, h:mm A')}
</MyText>
</View>
</View>
{/* Products */}
{slotProducts.length > 0 ? (
<View style={tw`bg-gray-50 p-3 rounded-xl mt-1`}>
<View style={tw`flex-row items-start`}>
<MaterialCommunityIcons name="basket-outline" size={16} color="#4b5563" style={tw`mr-2 mt-0.5`} />
<View style={tw`flex-1`}>
<MyText style={tw`text-xs text-gray-500 mb-0.5`}>Products</MyText>
<View style={tw`flex-row items-center flex-wrap`}>
<MyText style={tw`text-sm text-gray-800 leading-5`}>{displayProducts}</MyText>
{slotProducts.length > 2 && (
<TouchableOpacity
onPress={() => {
setDialogProducts(slotProducts);
setDialogOpen(true);
}}
>
<MyText style={tw`text-sm text-pink1 font-semibold ml-1`}>
+{slotProducts.length - 2} more
</MyText>
</TouchableOpacity>
)}
</View>
</View>
</View>
</View>
) : null}
</TouchableOpacity>
);
};
export default function RebalanceOrders() {
const [selectedSlots, setSelectedSlots] = useState<number[]>([]);
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogProducts, setDialogProducts] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const { data: slotsData, isLoading, refetch: refetchSlots } = trpc.admin.slots.getAll.useQuery();
const upcomingSlots = slotsData?.slots?.filter(slot => dayjs(slot.deliveryTime).isAfter(dayjs())) || [];
const handleRefresh = async () => {
setRefreshing(true);
await refetchSlots();
setRefreshing(false);
};
const { mutate: rebalanceSlots } = trpc.admin.order.rebalanceSlots.useMutation({
onSuccess: () => {
refetchSlots();
},
onSettled: () => {
Alert.alert("Rebalance Complete", "Slots have been rebalanced.");
}
});
const toggleSlotSelection = (slotId: number) => {
setSelectedSlots(prev =>
prev.includes(slotId)
? prev.filter(id => id !== slotId)
: [...prev, slotId]
);
};
const handleRebalance = () => {
Alert.alert("Rebalancing...", "Please wait while we rebalance the selected slots.", [{ text: "OK" }]);
rebalanceSlots({ slotIds: selectedSlots });
};
if (isLoading) {
return (
<View style={tw`flex-1 justify-center items-center bg-white`}>
<MyText>Loading slots...</MyText>
</View>
);
}
return (
<View style={tw`flex-1 bg-white relative`}>
<View style={tw`p-4 flex-1`}>
<MyText style={tw`text-xl font-bold text-gray-900 mb-4`}>Rebalance Upcoming Slots</MyText>
{upcomingSlots.length === 0 ? (
<View style={tw`flex-1 justify-center items-center`}>
<MyText style={tw`text-lg text-gray-600`}>No upcoming slots available for rebalancing.</MyText>
</View>
) : (
<MyFlatList
data={upcomingSlots}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<SlotItemComponent
item={item}
selectedSlots={selectedSlots}
toggleSlotSelection={toggleSlotSelection}
setDialogProducts={setDialogProducts}
setDialogOpen={setDialogOpen}
/>
)}
contentContainerStyle={tw`pb-24 flex-1`} // Space for floating button
showsVerticalScrollIndicator={false}
onRefresh={handleRefresh}
refreshing={refreshing}
/>
)}
</View>
{/* Floating Rebalance Button */}
<MyTouchableOpacity
onPress={handleRebalance}
disabled={selectedSlots.length === 0}
activeOpacity={0.95}
style={{
position: 'absolute',
bottom: 32,
right: 24,
zIndex: 100,
opacity: selectedSlots.length === 0 ? 0.5 : 1
}}
>
<LinearGradient
colors={selectedSlots.length === 0 ? ['#9CA3AF', '#6B7280'] : ['#F83758', '#E91E63']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={tw`w-16 h-16 rounded-[24px] items-center justify-center shadow-lg shadow-pink300`}
>
<MaterialCommunityIcons name="refresh" size={32} color="white" />
</LinearGradient>
</MyTouchableOpacity>
{/* Products Dialog */}
<BottomDialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
<View style={tw`p-4`}>
<MyText style={tw`text-lg font-bold mb-4`}>All Products</MyText>
<FlatList
data={dialogProducts}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<View style={tw`py-2 border-b border-gray-200`}>
<MyText style={tw`text-base`}>{item}</MyText>
</View>
)}
showsVerticalScrollIndicator={false}
style={tw`max-h-80`}
/>
</View>
</BottomDialog>
</View>
);
}