This commit is contained in:
shafi54 2026-01-26 17:09:32 +05:30
parent b953774fbc
commit 6b8680570b
5 changed files with 265 additions and 197 deletions

View file

@ -263,6 +263,7 @@ export const vendorSnippetsRouter = router({
productId: item.productId, productId: item.productId,
productName: item.product.name, productName: item.product.name,
quantity: parseFloat(item.quantity), quantity: parseFloat(item.quantity),
productSize: item.product.productQuantity,
price: parseFloat(item.price.toString()), price: parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || 'unit', unit: item.product.unit?.shortNotation || 'unit',
subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity), subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity),
@ -419,6 +420,7 @@ export const vendorSnippetsRouter = router({
price: parseFloat(item.price.toString()), price: parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || 'unit', unit: item.product.unit?.shortNotation || 'unit',
subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity), subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity),
productSize: item.product.productQuantity,
is_packaged: item.is_packaged, is_packaged: item.is_packaged,
is_package_verified: item.is_package_verified, is_package_verified: item.is_package_verified,
})); }));

View file

@ -6,6 +6,7 @@ import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url, generateUploa
import { ApiError } from '../../lib/api-error'; import { ApiError } from '../../lib/api-error';
import { eq, and, gt, sql, inArray, desc } from 'drizzle-orm'; import { eq, and, gt, sql, inArray, desc } from 'drizzle-orm';
import { getProductById as getProductByIdFromCache, getAllProducts as getAllProductsFromCache } from '../../stores/product-store'; import { getProductById as getProductByIdFromCache, getAllProducts as getAllProductsFromCache } from '../../stores/product-store';
import dayjs from 'dayjs';
// Uniform Product Type // Uniform Product Type
interface Product { interface Product {
@ -46,7 +47,16 @@ export const productRouter = router({
const cachedProduct = await getProductByIdFromCache(productId); const cachedProduct = await getProductByIdFromCache(productId);
if (cachedProduct) { if (cachedProduct) {
return cachedProduct; // Filter delivery slots to only include those with future freeze times
const currentTime = new Date();
const filteredSlots = cachedProduct.deliverySlots.filter(slot =>
dayjs(slot.freezeTime).isAfter(currentTime)
);
return {
...cachedProduct,
deliverySlots: filteredSlots
};
} }
// If not in cache, fetch from database (fallback) // If not in cache, fetch from database (fallback)
@ -97,7 +107,8 @@ export const productRouter = router({
and( and(
eq(productSlots.productId, productId), eq(productSlots.productId, productId),
eq(deliverySlotInfo.isActive, true), eq(deliverySlotInfo.isActive, true),
gt(deliverySlotInfo.deliveryTime, sql`NOW()`) gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
) )
) )
.orderBy(deliverySlotInfo.deliveryTime); .orderBy(deliverySlotInfo.deliveryTime);

View file

@ -1,114 +1,133 @@
import { useMemo, useState, useEffect } from 'react' import { useMemo, useState, useEffect } from "react";
import { useSearch } from '@tanstack/react-router' import { useSearch } from "@tanstack/react-router";
import { trpc } from '../trpc/client' import { trpc } from "../trpc/client";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { Button } from '@/components/ui/button' import { Button } from "@/components/ui/button";
import dayjs from 'dayjs' import dayjs from "dayjs";
import items from "razorpay/dist/types/items";
interface VendorOrder { interface VendorOrder {
orderId: string orderId: string;
customerName: string customerName: string;
orderDate: string orderDate: string;
totalAmount: string totalAmount: string;
products: Array<{ products: Array<{
orderItemId: number orderItemId: number;
productId: number productId: number;
productName: string productName: string;
quantity: number quantity: number;
unit: string unit: string;
is_packaged: boolean is_packaged: boolean;
is_package_verified: boolean is_package_verified: boolean;
}> }>;
} }
interface DeliverySlot { interface DeliverySlot {
id: number id: number;
deliveryTime: string deliveryTime: string;
freezeTime: string freezeTime: string;
deliverySequence: any deliverySequence: any;
} }
export function VendorOrderListRoute() { export function VendorOrderListRoute() {
const { id } = useSearch({ from: '/vendor-order-list' }) const { id } = useSearch({ from: "/vendor-order-list" });
// Fetch snippet info // Fetch snippet info
const { data: snippetInfo, isLoading: isLoadingSnippet } = id const { data: snippetInfo, isLoading: isLoadingSnippet } = id
? trpc.admin.vendorSnippets.getOrdersBySnippet.useQuery({ snippetCode: id }) ? trpc.admin.vendorSnippets.getOrdersBySnippet.useQuery({ snippetCode: id })
: { data: null, isLoading: false } : { data: null, isLoading: false };
const snippet = snippetInfo?.snippet const snippet = snippetInfo?.snippet;
const isPermanent = snippet?.isPermanent const isPermanent = snippet?.isPermanent;
const { data: upcomingSlots } = trpc.admin.vendorSnippets.getUpcomingSlots.useQuery(undefined, { enabled: !!id }) const { data: upcomingSlots } =
trpc.admin.vendorSnippets.getUpcomingSlots.useQuery(undefined, {
enabled: !!id,
});
// State for selected slot // State for selected slot
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null) const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null);
// Auto-select first slot when snippets are loaded and isPermanent is true // Auto-select first slot when snippets are loaded and isPermanent is true
useEffect(() => { useEffect(() => {
if (isPermanent && upcomingSlots?.data && upcomingSlots.data.length > 0 && !selectedSlotId) { if (
setSelectedSlotId(upcomingSlots.data[0].id) isPermanent &&
upcomingSlots?.data &&
upcomingSlots.data.length > 0 &&
!selectedSlotId
) {
setSelectedSlotId(upcomingSlots.data[0].id);
} }
}, [isPermanent, upcomingSlots, selectedSlotId]) }, [isPermanent, upcomingSlots, selectedSlotId]);
// Fetch orders based on mode // Fetch orders based on mode
const { data: slotOrdersData, error, isLoading: isLoadingOrders, isFetching, refetch } = trpc.admin.vendorSnippets.getOrdersBySnippetAndSlot.useQuery( const {
data: slotOrdersData,
error,
isLoading: isLoadingOrders,
isFetching,
refetch,
} = trpc.admin.vendorSnippets.getOrdersBySnippetAndSlot.useQuery(
{ snippetCode: id!, slotId: selectedSlotId! }, { snippetCode: id!, slotId: selectedSlotId! },
{ enabled: !!id && !!selectedSlotId && isPermanent } { enabled: !!id && !!selectedSlotId && isPermanent },
) );
const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.useQuery( const { data: regularOrders } =
trpc.admin.vendorSnippets.getOrdersBySnippet.useQuery(
{ snippetCode: id! }, { snippetCode: id! },
{ enabled: !!id && !isPermanent } { enabled: !!id && !isPermanent },
) );
const orders = slotOrdersData?.data || regularOrders?.data || [] const orders = slotOrdersData?.data || regularOrders?.data || [];
const isLoadingCurrent = isPermanent ? isLoadingOrders : isLoadingSnippet const isLoadingCurrent = isPermanent ? isLoadingOrders : isLoadingSnippet;
const updatePackagingMutation = trpc.admin.vendorSnippets.updateOrderItemPackaging.useMutation() const updatePackagingMutation =
trpc.admin.vendorSnippets.updateOrderItemPackaging.useMutation();
const [updatingItems, setUpdatingItems] = useState<Set<number>>(new Set()) const [updatingItems, setUpdatingItems] = useState<Set<number>>(new Set());
const handlePackagingToggle = async (orderItemId: number, currentValue: boolean) => { const handlePackagingToggle = async (
setUpdatingItems(prev => new Set(prev).add(orderItemId)) orderItemId: number,
currentValue: boolean,
) => {
setUpdatingItems((prev) => new Set(prev).add(orderItemId));
try { try {
await updatePackagingMutation.mutateAsync({ await updatePackagingMutation.mutateAsync({
orderItemId, orderItemId,
is_packaged: !currentValue is_packaged: !currentValue,
}) });
// Refetch data to update the UI // Refetch data to update the UI
refetch() refetch();
} catch (error) { } catch (error) {
console.error('Failed to update packaging status:', error) console.error("Failed to update packaging status:", error);
} finally { } finally {
setUpdatingItems(prev => { setUpdatingItems((prev) => {
const newSet = new Set(prev) const newSet = new Set(prev);
newSet.delete(orderItemId) newSet.delete(orderItemId);
return newSet return newSet;
}) });
} }
} };
const productSummary = useMemo(() => { const productSummary = useMemo(() => {
const summary: Record<string, { quantity: number; unit: string }> = {}; const summary: Record<string, { quantity: number; unit: string }> = {};
orders.forEach(order => { orders.forEach((order) => {
order.products.forEach(product => { order.products.forEach((product) => {
const key = product.productName; const key = product.productName;
if (!summary[key]) { if (!summary[key]) {
summary[key] = { quantity: 0, unit: product.unit }; summary[key] = { quantity: 0, unit: product.unit };
} }
summary[key].quantity += product.quantity; summary[key].quantity += product.quantity * product.productSize;
}); });
}); });
return Object.entries(summary).map(([name, data]) => ({ return Object.entries(summary).map(([name, data]) => ({
name, name,
quantity: data.quantity, quantity: data.quantity,
unit: data.unit unit: data.unit,
})); }));
}, [orders]) }, [orders]);
return ( return (
<section className="space-y-6"> <section className="space-y-6">
@ -118,7 +137,9 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
{productSummary.map((item, index) => ( {productSummary.map((item, index) => (
<div key={index} className="flex justify-between text-sm"> <div key={index} className="flex justify-between text-sm">
<span className="text-slate-600">{item.name}:</span> <span className="text-slate-600">{item.name}:</span>
<span className="font-medium text-slate-900">{item.quantity} {item.unit}</span> <span className="font-medium text-slate-900">
{item.quantity} {item.unit}
</span>
</div> </div>
))} ))}
</div> </div>
@ -127,7 +148,9 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
<div className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm"> <div className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div> <div>
<h2 className="text-xl font-semibold text-slate-900">Vendor Orders</h2> <h2 className="text-xl font-semibold text-slate-900">
Vendor Orders
</h2>
<p className="text-sm text-slate-500"> <p className="text-sm text-slate-500">
Track incoming orders and fulfilment progress for vendor partners. Track incoming orders and fulfilment progress for vendor partners.
{id && <span className="block mt-1 text-xs">Snippet: {id}</span>} {id && <span className="block mt-1 text-xs">Snippet: {id}</span>}
@ -135,18 +158,21 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
</div> </div>
{isPermanent && upcomingSlots?.data && ( {isPermanent && upcomingSlots?.data && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label htmlFor="slot-select" className="text-sm font-medium text-slate-700"> <label
htmlFor="slot-select"
className="text-sm font-medium text-slate-700"
>
Select Slot: Select Slot:
</label> </label>
<select <select
id="slot-select" id="slot-select"
value={selectedSlotId || ''} value={selectedSlotId || ""}
onChange={(e) => setSelectedSlotId(Number(e.target.value))} onChange={(e) => setSelectedSlotId(Number(e.target.value))}
className="base-select px-3 py-2 text-sm border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" className="base-select px-3 py-2 text-sm border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
> >
{upcomingSlots.data.map((slot) => ( {upcomingSlots.data.map((slot) => (
<option key={slot.id} value={slot.id}> <option key={slot.id} value={slot.id}>
{dayjs(slot.deliveryTime).format('ddd, MMM DD hh:mm A')} {dayjs(slot.deliveryTime).format("ddd, MMM DD hh:mm A")}
</option> </option>
))} ))}
</select> </select>
@ -155,11 +181,11 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
<Button <Button
variant="outline" variant="outline"
onClick={() => { onClick={() => {
void refetch() void refetch();
}} }}
disabled={isFetching} disabled={isFetching}
> >
{isFetching ? 'Refreshing…' : 'Refresh'} {isFetching ? "Refreshing…" : "Refresh"}
</Button> </Button>
</div> </div>
@ -170,7 +196,7 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
</div> </div>
) : error ? ( ) : error ? (
<div className="rounded-xl border border-red-200 bg-red-50 p-6 text-sm text-red-600"> <div className="rounded-xl border border-red-200 bg-red-50 p-6 text-sm text-red-600">
{error.message ?? 'Unable to load vendor orders right now'} {error.message ?? "Unable to load vendor orders right now"}
</div> </div>
) : !id ? ( ) : !id ? (
<div className="rounded-xl border border-red-200 bg-red-50 p-6 text-sm text-red-600"> <div className="rounded-xl border border-red-200 bg-red-50 p-6 text-sm text-red-600">
@ -184,9 +210,10 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{orders.map((order) => { {orders.map((order) => {
const parsedDate = order.orderDate const parsedDate = order.orderDate
? dayjs(order.orderDate).format('ddd, MMM DD hh:mm A') ? dayjs(order.orderDate).format("ddd, MMM DD hh:mm A")
: 'N/A' : "N/A";
const badgeClass = 'border-slate-200 bg-slate-100 text-slate-600 inline-flex items-center rounded-full border px-3 py-0.5 text-xs font-semibold uppercase' const badgeClass =
"border-slate-200 bg-slate-100 text-slate-600 inline-flex items-center rounded-full border px-3 py-0.5 text-xs font-semibold uppercase";
return ( return (
<article <article
@ -197,9 +224,7 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
<h3 className="text-base font-semibold text-slate-900"> <h3 className="text-base font-semibold text-slate-900">
{order.orderId} {order.orderId}
</h3> </h3>
<span className={badgeClass}> <span className={badgeClass}>Pending</span>
Pending
</span>
</header> </header>
<dl className="grid gap-3 text-sm text-slate-600"> <dl className="grid gap-3 text-sm text-slate-600">
<div className="space-y-2"> <div className="space-y-2">
@ -208,28 +233,43 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
</dt> </dt>
<dd className="space-y-2"> <dd className="space-y-2">
{order.products.map((product) => ( {order.products.map((product) => (
<div key={product.orderItemId} className="flex items-center gap-3"> <div
key={product.orderItemId}
className="flex items-center gap-3"
>
<span className="text-sm font-medium text-slate-900 flex-1"> <span className="text-sm font-medium text-slate-900 flex-1">
{product.productName}: {product.quantity} {product.unit} {product.productName}: {product.productSize * product.quantity}{" "}
{product.unit}
</span> </span>
<label <label
htmlFor={`package-${product.orderItemId}`} htmlFor={`package-${product.orderItemId}`}
className={cn( className={cn(
"text-sm font-medium", "text-sm font-medium",
product.is_packaged ? "text-green-700" : "text-slate-600" product.is_packaged
? "text-green-700"
: "text-slate-600",
)} )}
> >
Packaged Packaged
{updatingItems.has(product.orderItemId) && ( {updatingItems.has(product.orderItemId) && (
<span className="ml-2 text-xs text-blue-500">(updating...)</span> <span className="ml-2 text-xs text-blue-500">
(updating...)
</span>
)} )}
</label> </label>
<input <input
type="checkbox" type="checkbox"
id={`package-${product.orderItemId}`} id={`package-${product.orderItemId}`}
checked={product.is_packaged} checked={product.is_packaged}
disabled={updatingItems.has(product.orderItemId)} disabled={updatingItems.has(
onChange={() => handlePackagingToggle(product.orderItemId, product.is_packaged)} product.orderItemId,
)}
onChange={() =>
handlePackagingToggle(
product.orderItemId,
product.is_packaged,
)
}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/> />
</div> </div>
@ -240,7 +280,9 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
<dt className="text-xs uppercase tracking-wide text-slate-400"> <dt className="text-xs uppercase tracking-wide text-slate-400">
Date Date
</dt> </dt>
<dd className="font-medium text-slate-900">{parsedDate}</dd> <dd className="font-medium text-slate-900">
{parsedDate}
</dd>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<dt className="text-xs uppercase tracking-wide text-slate-400"> <dt className="text-xs uppercase tracking-wide text-slate-400">
@ -252,7 +294,7 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
</div> </div>
</dl> </dl>
</article> </article>
) );
})} })}
</div> </div>
)} )}
@ -263,13 +305,5 @@ const { data: regularOrders } = trpc.admin.vendorSnippets.getOrdersBySnippet.use
) : null} ) : null}
</div> </div>
</section> </section>
) );
}
const statusBadgeStyles: Record<string, string> = {
pending: 'border-amber-200 bg-amber-100 text-amber-700',
completed: 'border-emerald-200 bg-emerald-100 text-emerald-700',
cancelled: 'border-rose-200 bg-rose-100 text-rose-700',
inprogress: 'border-sky-200 bg-sky-100 text-sky-700',
dispatched: 'border-indigo-200 bg-indigo-100 text-indigo-700'
} }

View file

@ -21,6 +21,7 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import FontAwesome5 from "@expo/vector-icons/FontAwesome5"; import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import ProductCard from "@/components/ProductCard"; import ProductCard from "@/components/ProductCard";
import MyFlatList from "common-ui/src/components/flat-list";
import { trpc } from "@/src/trpc-client"; import { trpc } from "@/src/trpc-client";
import { useGetCart } from "@/hooks/cart-query-hooks"; import { useGetCart } from "@/hooks/cart-query-hooks";
@ -38,6 +39,7 @@ dayjs.extend(relativeTime);
const { width: screenWidth } = Dimensions.get("window"); const { width: screenWidth } = Dimensions.get("window");
const itemWidth = screenWidth * 0.45; // 45% of screen width const itemWidth = screenWidth * 0.45; // 45% of screen width
const gridItemWidth = (screenWidth * 0.9) / 2; // Half of screen width minus padding
const RenderStore = ({ const RenderStore = ({
item, item,
@ -115,6 +117,7 @@ export default function Dashboard() {
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
const [isLoadingMore, setIsLoadingMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false);
const { backgroundColor } = useStatusBarStore(); const { backgroundColor } = useStatusBarStore();
const { getQuickestSlot } = useProductSlotIdentifier();
const { const {
data: productsData, data: productsData,
@ -142,37 +145,41 @@ export default function Dashboard() {
setIsLoadingMore(true); setIsLoadingMore(true);
// Simulate loading more products by taking the next batch
// In a real app, you would make an API call with pagination params
// setTimeout(() => {
const batchSize = 10; const batchSize = 10;
const startIndex = page * batchSize; const startIndex = page * batchSize;
const endIndex = startIndex + batchSize; const endIndex = startIndex + batchSize;
// Get the next batch of products // Get the next batch of products
const nextBatch = products.slice(startIndex, endIndex); const nextBatchRaw = products.slice(startIndex, endIndex);
const oldBatchIds = displayedProducts.map(p => p.id)
const nextBatchIds = nextBatch.map(p => p.id)
const productIds = products.map(p => p.id);
// Filter products to only include those with available slots
const nextBatch = nextBatchRaw.filter(product => {
const slot = getQuickestSlot(product.id);
return slot !== null && slot !== undefined;
});
if (nextBatch.length > 0) { if (nextBatch.length > 0) {
setDisplayedProducts(prev => [...prev, ...nextBatch]); setDisplayedProducts(prev => [...prev, ...nextBatch]);
setPage(prev => prev + 1); setPage(prev => page + 1);
setHasMore(endIndex < products.length); setHasMore(endIndex < products.length);
} else { } else {
setHasMore(false); setHasMore(false);
} }
setIsLoadingMore(false); setIsLoadingMore(false);
// }, 500); // Simulate network delay
}; };
// Initialize with the first batch of products // Initialize with the first batch of products (only those with available slots)
React.useEffect(() => { React.useEffect(() => {
if (products.length > 0 && displayedProducts.length === 0) { if (products.length > 0 && displayedProducts.length === 0) {
const initialBatch = products.slice(0, 10); // First 10 products const initialBatchRaw = products.slice(0, 10);
// Filter to include only products with available slots
const initialBatch = initialBatchRaw.filter(product => {
const slot = getQuickestSlot(product.id);
return slot !== null && slot !== undefined;
});
setDisplayedProducts(initialBatch); setDisplayedProducts(initialBatch);
setHasMore(products.length > 10); setHasMore(products.length > 10);
setPage(1); setPage(1);
@ -359,7 +366,6 @@ export default function Dashboard() {
<BannerCarousel /> <BannerCarousel />
</View> </View>
<TestingPhaseNote />
{/* White Section */} {/* White Section */}
<View <View
@ -587,13 +593,18 @@ export default function Dashboard() {
</View> </View>
</View> </View>
{/* Product Grid */} {/* Product List */}
<View style={tw`flex-row flex-wrap`}> <MyFlatList
{displayedProducts.map((item, index: number) => ( data={displayedProducts}
keyExtractor={(item) => item.id.toString()}
numColumns={2}
contentContainerStyle={tw`pb-8`}
columnWrapperStyle={tw`py-2`}
renderItem={({ item, index }) => (
<View style={tw`ml-1`}>
<ProductCard <ProductCard
item={item} item={item}
itemWidth={(screenWidth * 0.9) / 2} // Half of screen width minus padding itemWidth={gridItemWidth}
onPress={() => onPress={() =>
router.push( router.push(
`/(drawer)/(tabs)/home/product-detail/${item.id}` `/(drawer)/(tabs)/home/product-detail/${item.id}`
@ -601,18 +612,30 @@ export default function Dashboard() {
} }
showDeliveryInfo={true} showDeliveryInfo={true}
miniView={false} miniView={false}
nullIfNotAvailable={true} // nullIfNotAvailable={true}
containerComp={({children}) => <View key={item.id} style={tw`w-1/2 pr-2 pb-4`}>{children}</View>}
key={item.id} key={item.id}
/> />
))}
</View> </View>
)}
{isLoadingMore && ( onEndReached={() => {
if (!isLoadingMore && hasMore) {
loadMoreProducts();
}
}}
onEndReachedThreshold={0.5}
ListFooterComponent={
isLoadingMore ? (
<View style={tw`items-center py-4`}> <View style={tw`items-center py-4`}>
<MyText>Loading more...</MyText> <MyText>Loading more...</MyText>
</View> </View>
)} ) : null
}
ListEmptyComponent={
<View style={tw`items-center py-8`}>
<MyText style={tw`text-gray-500`}>No products available</MyText>
</View>
}
/>
</View> </View>
</View> </View>

View file

@ -825,8 +825,6 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
</View> </View>
)} )}
<TestingPhaseNote />
{/* Bottom Checkout Bar - Now Static */} {/* Bottom Checkout Bar - Now Static */}
{hasAvailableItems && ( {hasAvailableItems && (
<View style={tw`bg-white mt-4 rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6`}> <View style={tw`bg-white mt-4 rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6`}>