freshyo/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx
2026-08-04 23:06:27 +05:30

776 lines
26 KiB
TypeScript
Executable file

import React, { useState, useCallback, useMemo, memo } from "react";
import { View, Dimensions, Image, RefreshControl, ScrollView } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import { useRouter } from "expo-router";
import {
theme,
tw,
useManualRefresh,
useMarkDataFetchers,
LoadingDialog,
MyTouchableOpacity,
MyText, SearchBar
} from "common-ui";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import ProductCard from "@/components/ProductCard";
import AddToCartDialog from "@/src/components/AddToCartDialog";
import MyFlatList from "common-ui/src/components/flat-list";
import { trpc } from "@/src/trpc-client";
import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks";
import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier";
import { useCentralSlotStore } from "@/src/store/centralSlotStore";
import { useCentralProductStore } from "@/src/store/centralProductStore";
import FloatingCartBar from "@/components/floating-cart-bar";
import BannerCarousel from "@/components/BannerCarousel";
import { useUserDetails } from "@/src/contexts/AuthContext";
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
import { useNavigationStore } from "@/src/store/navigationStore";
import NextOrderGlimpse from "@/components/NextOrderGlimpse";
dayjs.extend(relativeTime);
const { width: screenWidth } = Dimensions.get("window");
const itemWidth = screenWidth * 0.45;
const heroItemWidth = (screenWidth - 72) / 3;
const gridItemWidth = (screenWidth - 48) / 2;
const formatTimeRange = (deliveryTime: string) => {
const time = dayjs(deliveryTime);
const endTime = time.add(1, 'hour');
const startPeriod = time.format('A');
const endPeriod = endTime.format('A');
if (startPeriod === endPeriod) {
return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`;
} else {
return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`;
}
};
const staticStyles = {
flatListContent: { gap: 16 },
columnWrapper: { gap: 16, paddingHorizontal: 16 },
popularListContent: { paddingBottom: 16 },
slotsListContent: { paddingBottom: 24 },
};
const TAG_COLORS = [
{ pageBg: '#FFF8F9', bg: '#FFF1F2', border: '#FECDD3', text: '#9F1239', dot: '#E11D48' }, // rose
{ pageBg: '#FFFCF1', bg: '#FFFBEB', border: '#FDE68A', text: '#92400E', dot: '#D97706' }, // amber
{ pageBg: '#F6FEF9', bg: '#F0FDF4', border: '#BBF7D0', text: '#166534', dot: '#16A34A' }, // green
{ pageBg: '#F5FAFF', bg: '#EFF6FF', border: '#BFDBFE', text: '#1E3A8A', dot: '#2563EB' }, // blue
{ pageBg: '#FAF8FF', bg: '#F5F3FF', border: '#DDD6FE', text: '#5B21B6', dot: '#7C3AED' }, // violet
{ pageBg: '#FFF9F2', bg: '#FFF7ED', border: '#FFD6B0', text: '#9A3412', dot: '#EA580C' }, // orange
{ pageBg: '#F3FEFF', bg: '#ECFEFF', border: '#A5F3FC', text: '#155E75', dot: '#0891B2' }, // cyan
{ pageBg: '#FFF7FB', bg: '#FDF2F8', border: '#FBCFE8', text: '#9D174D', dot: '#DB2777' }, // pink
];
const getTagColor = (id: number) => TAG_COLORS[id % TAG_COLORS.length];
interface RenderStoreProps {
item: any;
}
const RenderStore = memo(({ item }: RenderStoreProps) => {
const router = useRouter();
const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore();
const handlePress = useCallback(() => {
setNavigatedFromHome(true);
setSelectedStoreId(item.id);
router.push('/(drawer)/(tabs)/stores');
}, [item.id, router, setNavigatedFromHome, setSelectedStoreId]);
return (
<MyTouchableOpacity
style={tw`items-center mb-4`}
onPress={handlePress}
activeOpacity={0.7}
>
<View
style={tw`w-16 h-16 rounded-2xl bg-white/20 border-2 border-white/30 items-center justify-center mb-2 shadow-lg overflow-hidden`}
>
{item.signedImageUrl ? (
<Image source={{ uri: item.signedImageUrl }} style={tw`w-16 h-16 rounded-2xl`} resizeMode="cover" />
) : (
<MaterialIcons name="storefront" size={28} color="#FFF" />
)}
</View>
<MyText style={tw`font-bold text-xs text-center tracking-wide drop-shadow-sm text-neutral-800`} numberOfLines={1}>
{item.name.replace(/^The\s+/i, "")}
</MyText>
</MyTouchableOpacity>
);
});
RenderStore.displayName = 'RenderStore';
interface SlotCardProps {
slot: any;
}
const SlotCard = memo(({ slot }: SlotCardProps) => {
const router = useRouter();
const now = dayjs();
const freezeTime = dayjs(slot.freezeTime);
const isClosingSoon = freezeTime.diff(now, "hour") < 4 && freezeTime.isAfter(now);
const handlePress = useCallback(() => {
router.push(`/(drawer)/(tabs)/home/slot-view?slotId=${slot.id}`);
}, [router, slot.id]);
return (
<MyTouchableOpacity
testID={`slot-card-${slot.id}`}
style={[
tw`bg-white rounded-[24px] p-5 mr-4 shadow-xl shadow-slate-200 border border-slate-100 min-w-[280px]`,
isClosingSoon ? tw`border-l-4 border-l-amber-400` : tw`border-l-4 border-l-brand500`,
]}
onPress={handlePress}
activeOpacity={0.9}
>
<View style={tw`flex-row justify-end items-start mb-4`}>
{isClosingSoon && (
<View style={tw`bg-amber-50 px-2 py-0.5 rounded-md border border-amber-100 flex-row items-center`}>
<View style={tw`w-1 h-1 rounded-full bg-amber-500 mr-1`} />
<MyText style={tw`text-[10px] font-bold text-amber-700`}>CLOSING SOON</MyText>
</View>
)}
</View>
<View style={tw`flex-row justify-between mb-5`}>
<View style={tw`flex-1 mr-4`}>
<View style={tw`flex-row items-center mb-1.5`}>
<View style={tw`bg-brand50 p-1 rounded-md mr-1.5`}>
<MaterialIcons name="local-shipping" size={12} color={theme.colors.brand600} />
</View>
<MyText style={tw`text-[10px] font-bold text-brand700 uppercase`}>Delivery At</MyText>
</View>
<MyText style={tw`text-sm font-extrabold text-slate-900`}>{formatTimeRange(slot.deliveryTime)}</MyText>
<MyText style={tw`text-[11px] font-bold text-slate-500`}>{dayjs(slot.deliveryTime).format("ddd, MMM DD")}</MyText>
</View>
<View style={tw`flex-1`}>
<View style={tw`flex-row items-center mb-1.5`}>
<View style={tw`bg-amber-50 p-1 rounded-md mr-1.5`}>
<MaterialIcons name="timer" size={12} color="#D97706" />
</View>
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>Order By</MyText>
</View>
<MyText style={tw`text-sm font-extrabold text-slate-900`}>{dayjs(slot.freezeTime).format("h:mm A")}</MyText>
<MyText style={tw`text-[11px] font-bold text-slate-500`}>{dayjs(slot.freezeTime).format("ddd, MMM DD")}</MyText>
</View>
</View>
<View style={tw`flex-row items-center`}>
<View style={tw`flex-row mr-3`}>
{slot.products.slice(0, 3).map((p: any, i: number) => (
<View
key={p.id}
style={[tw`w-8 h-8 rounded-full border-2 border-white bg-slate-100 overflow-hidden`, i > 0 && tw`-ml-3`]}
>
{p.images?.[0] ? (
<Image source={{ uri: p.images?.[0] }} style={tw`w-full h-full`} />
) : (
<MaterialIcons name="image" size={14} color="#94A3B8" />
)}
</View>
))}
</View>
<MyText style={tw`text-[11px] font-bold text-brand600`}>View all {slot.products.length} items</MyText>
<MaterialIcons name="chevron-right" size={16} color={theme.colors.brand600} />
</View>
</MyTouchableOpacity>
);
});
interface PopularProductItemProps {
item: any;
onPress: (id: number) => void;
}
const PopularProductItem = memo(({ item, onPress }: PopularProductItemProps) => {
const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
return (
<View style={tw`mr-4`}>
<ProductCard
item={item}
itemWidth={itemWidth}
onPress={handlePress}
showDeliveryInfo={false}
useAddToCartDialog={true}
miniView={true}
/>
</View>
);
});
interface ExploreTabProps {
tag: any;
isSelected: boolean;
onPress: () => void;
}
const ExploreTab = memo(({ tag, isSelected, onPress }: ExploreTabProps) => {
return (
<MyTouchableOpacity
onPress={onPress}
activeOpacity={0.75}
style={tw`px-2 pt-2 pb-1`}
>
<View style={tw`items-center`}>
<MyText
style={[
tw`text-base tracking-tight`,
{
color: isSelected ? '#111827' : '#64748B',
fontWeight: isSelected ? '700' : '500',
},
]}
>
{tag.tagName}
</MyText>
<View
style={{
width: '100%',
height: 4,
borderRadius: 999,
marginTop: 8,
backgroundColor: isSelected ? '#111827' : 'transparent',
}}
/>
</View>
</MyTouchableOpacity>
);
});
interface ExploreProductItemProps {
item: any;
onPress: (id: number) => void;
}
const ExploreProductItem = memo(({ item, onPress }: ExploreProductItemProps) => {
const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
return (
<View style={tw`mb-3`}>
<ProductCard
item={item}
itemWidth={heroItemWidth}
onPress={handlePress}
showDeliveryInfo={false}
useAddToCartDialog={true}
miniView={true}
variant="hero"
/>
</View>
);
});
interface SlotItemProps {
item: any;
}
const SlotItem = memo(({ item }: SlotItemProps) => <SlotCard slot={item} />);
interface ProductItemProps {
item: any;
onPress: (id: number) => void;
}
const ProductItem = memo(({ item, onPress }: ProductItemProps) => {
const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
return (
<ProductCard
item={item}
itemWidth={gridItemWidth}
onPress={handlePress}
showDeliveryInfo={true}
miniView={false}
useAddToCartDialog={true}
/>
);
});
interface ListHeaderProps {
gradientHeight: number;
onGradientLayout: (height: number) => void;
storesData: any;
popularProducts: any[];
sortedSlots: any[];
onProductPress: (id: number) => void;
dashboardTags: any[];
activeTagId: number | null;
activeTagProducts: any[];
onSelectTag: (id: number) => void;
}
const ListHeader = memo(({
gradientHeight,
onGradientLayout,
storesData,
popularProducts,
sortedSlots,
onProductPress,
dashboardTags,
activeTagId,
activeTagProducts,
onSelectTag,
}: ListHeaderProps) => {
const [showAllActiveProducts, setShowAllActiveProducts] = useState(false);
const handleLayout = useCallback((event: any) => {
const { y, height } = event.nativeEvent.layout;
onGradientLayout(y + height);
}, [onGradientLayout]);
React.useEffect(() => {
setShowAllActiveProducts(false);
}, [activeTagId]);
const renderPopularItem = useCallback(({ item }: { item: any }) => (
<PopularProductItem item={item} onPress={onProductPress} />
), [onProductPress]);
const renderSlotItem = useCallback(({ item }: { item: any }) => (
<SlotItem item={item} />
), []);
const gradientStyle = useMemo(() => [
tw`absolute left-0 right-0 shadow-lg`,
{ height: gradientHeight + 32, zIndex: -1 }
], [gradientHeight]);
const activeColor = activeTagId == null ? null : getTagColor(activeTagId);
const pageTint = activeColor?.pageBg ?? '#FFFFFF';
const visibleActiveTagProducts = showAllActiveProducts ? activeTagProducts : activeTagProducts.slice(0, 6);
const hasMoreActiveTagProducts = activeTagProducts.length > visibleActiveTagProducts.length;
return (
<>
<View onLayout={handleLayout} style={{ backgroundColor: pageTint }}>
<LinearGradient
colors={[pageTint, pageTint]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0.5 }}
style={gradientStyle}
/>
{dashboardTags.length > 0 && (
<View
style={[
tw`mx-4 mb-2 rounded-[28px] px-3 pt-3 pb-2`,
{ backgroundColor: pageTint },
]}
>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={tw`gap-3 py-1 px-1`}
>
{dashboardTags.map((tag) => (
<ExploreTab
key={tag.id}
tag={tag}
isSelected={activeTagId === tag.id}
onPress={() => onSelectTag(tag.id)}
/>
))}
</ScrollView>
{activeTagProducts.length > 0 ? (
<View style={[tw`mt-4 relative`, { backgroundColor: pageTint }]}>
<View style={tw`flex-row flex-wrap justify-between`}>
{visibleActiveTagProducts.map((product: any) => (
<ExploreProductItem
key={product.id}
item={product}
onPress={onProductPress}
/>
))}
</View>
{hasMoreActiveTagProducts && (
<MyTouchableOpacity
style={tw`self-center mt-1 mb-2 px-5 py-2.5 rounded-full bg-gray-900`}
activeOpacity={0.85}
onPress={() => setShowAllActiveProducts(true)}
>
<MyText style={tw`text-white text-sm font-semibold`}>Show More</MyText>
</MyTouchableOpacity>
)}
</View>
) : (
<View style={tw`py-6 items-center`}>
<MyText style={tw`text-sm text-gray-500 font-medium`}>
No products in this category yet
</MyText>
</View>
)}
</View>
)}
</View>
<View style={tw`py-4`}>
<BannerCarousel />
</View>
<View
style={[
tw`rounded-t-3xl px-4`,
{ backgroundColor: pageTint },
]}
>
{storesData?.stores && storesData.stores.length > 0 && (
<View style={tw`mt-4 mb-5 rounded-[28px] bg-white/70 border border-white px-3 pt-4 pb-2`}>
<View style={tw`flex-row items-center justify-between mb-4 px-1`}>
<View>
<MyText style={tw`text-xl font-extrabold text-gray-900 tracking-tight`}>
Our Stores
</MyText>
<MyText style={tw`text-xs font-medium mt-0.5 text-gray-500`}>Fresh from our locations</MyText>
</View>
</View>
<View style={tw`pb-2`}>
<View style={tw`flex-row flex-wrap`}>
{storesData.stores.map((store: any) => (
<View key={store.id} style={tw`w-1/4 p-1`}>
<RenderStore item={store} />
</View>
))}
</View>
</View>
</View>
)}
<View style={tw`py-2`}>
<NextOrderGlimpse />
</View>
<View style={tw`mb-4 pt-2 px-1`}>
<MyText style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}>Popular Items</MyText>
<MyText style={tw`text-sm text-gray-500 font-medium`}>Trending fresh picks just for you</MyText>
</View>
<View style={tw`relative`}>
<MyFlatList
data={popularProducts}
keyExtractor={(item) => item.id.toString()}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={staticStyles.popularListContent}
renderItem={renderPopularItem}
removeClippedSubviews={true}
/>
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.08)"]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
style={tw`absolute right-0 top-0 bottom-4 w-12 rounded-l-xl`}
pointerEvents="none"
/>
</View>
{sortedSlots.length > 0 && (
<View style={tw`mt-2 mb-4`}>
<View style={tw`flex-row items-center justify-between px-1 mb-6`}>
<View>
<MyText style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}>Upcoming Delivery Slots</MyText>
<MyText style={tw`text-sm text-gray-500 font-medium mt-1`}>Plan your fresh deliveries ahead</MyText>
</View>
</View>
<MyFlatList
data={sortedSlots.slice(0, 5)}
keyExtractor={(item) => item.id.toString()}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={staticStyles.slotsListContent}
decelerationRate="fast"
snapToInterval={280 + 16}
renderItem={renderSlotItem}
removeClippedSubviews={true}
/>
</View>
)}
<View style={tw`mt-2 mb-4`}>
<View style={tw`flex-row items-center justify-between px-1 mb-4`}>
<View>
<MyText style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}>All Available Products</MyText>
<MyText style={tw`text-sm text-gray-500 font-medium mt-1`}>Browse our complete selection</MyText>
</View>
</View>
</View>
</View>
</>
);
});
export default function Dashboard() {
const router = useRouter();
const userDetails = useUserDetails();
const [inputQuery, setInputQuery] = useState("");
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
const [gradientHeight, setGradientHeight] = useState(0);
const [displayedProducts, setDisplayedProducts] = useState<any[]>([]);
const [hasMore, setHasMore] = useState(true);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const { getQuickestSlot } = useProductSlotIdentifier();
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
const refetchProducts = useCentralProductStore((state) => state.refetchProducts);
const refetchSlotsFromStore = useCentralSlotStore((state) => state.refetchSlots);
const [isRefreshing, setIsRefreshing] = useState(false);
const {
data: productsData,
isLoading,
error,
} = useAllProducts();
const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
const { data: storesData, refetch: refetchStores } = useStores();
const { data: slotsData } = useSlots();
const products = productsData?.products || [];
const dashboardTags = productsData?.tags || [];
const [selectedTagId, setSelectedTagId] = useState<number | null>(null);
const activeTagId = selectedTagId ?? dashboardTags[0]?.id ?? null;
React.useEffect(() => {
if (products.length === 0) {
setDisplayedProducts([])
setHasMore(false)
return
}
const initialBatch = products
.filter(p => typeof p.id === "number")
.sort((a, b) => {
const slotA = getQuickestSlot(a.id)
const slotB = getQuickestSlot(b.id)
const aOutOfStock = Boolean(productSlotsMap[a.id]?.isOutOfStock) || !slotA
const bOutOfStock = Boolean(productSlotsMap[b.id]?.isOutOfStock) || !slotB
if (aOutOfStock && !bOutOfStock) return 1
if (!aOutOfStock && bOutOfStock) return -1
return 0
})
setDisplayedProducts(initialBatch)
setHasMore(products.length > 10)
}, [productsData, productSlotsMap]);
const popularItemIds = useMemo(() => {
const popularItems = essentialConsts?.popularItems;
if (!popularItems) return [];
if (Array.isArray(popularItems)) {
return popularItems.map((id: any) => parseInt(id)).filter((id: number) => !isNaN(id));
} else if (typeof popularItems === 'string') {
return popularItems
.split(',')
.map((id: string) => parseInt(id.trim()))
.filter((id: number) => !isNaN(id));
}
return [];
}, [essentialConsts?.popularItems]);
const sortedSlots = useMemo(() => {
if (!slotsData?.slots) return [];
const now = dayjs();
return [...slotsData.slots]
.filter(slot => dayjs(slot.deliveryTime).isAfter(now))
.sort((a, b) => {
const deliveryDiff = dayjs(a.deliveryTime).diff(dayjs(b.deliveryTime));
if (deliveryDiff !== 0) return deliveryDiff;
return dayjs(a.freezeTime).diff(dayjs(b.freezeTime));
});
}, [slotsData]);
const popularProducts = useMemo(() => {
return popularItemIds
.map(id => products.find(product => product.id === id))
.filter((product): product is NonNullable<typeof product> => product != null);
}, [popularItemIds, products]);
const activeTagProducts = useMemo(() => {
if (activeTagId == null) return [];
const activeTag = dashboardTags.find((tag: any) => tag.id === activeTagId);
if (!activeTag) return [];
return products
.filter((product: any) => activeTag.productIds?.includes(product.id) ?? false)
.sort((a: any, b: any) => {
const slotA = getQuickestSlot(a.id)
const slotB = getQuickestSlot(b.id)
const aOutOfStock = Boolean(productSlotsMap[a.id]?.isOutOfStock) || !slotA
const bOutOfStock = Boolean(productSlotsMap[b.id]?.isOutOfStock) || !slotB
if (aOutOfStock && !bOutOfStock) return 1
if (!aOutOfStock && bOutOfStock) return -1
return 0
});
}, [activeTagId, dashboardTags, products, getQuickestSlot, productSlotsMap]);
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
try {
const promises = [];
if (refetchProducts) {
promises.push(refetchProducts());
}
if (refetchSlotsFromStore) {
promises.push(refetchSlotsFromStore());
}
promises.push(refetchStores());
promises.push(refetchConsts());
await Promise.all(promises);
} finally {
setIsRefreshing(false);
}
}, [refetchProducts, refetchSlotsFromStore, refetchStores, refetchConsts]);
useManualRefresh(() => {
handleRefresh();
});
useMarkDataFetchers(() => {
handleRefresh();
});
const handleProductPress = useCallback((id: number) => {
router.push(`/(drawer)/(tabs)/home/product-detail/${id}`);
}, [router]);
const handleGradientLayout = useCallback((height: number) => {
setGradientHeight(height);
}, []);
const handleSearchPress = useCallback(() => {
router.push("/(drawer)/(tabs)/home/search-results");
}, [router]);
const renderProductItem = useCallback(({ item }: { item: any }) => (
<ProductItem item={item} onPress={handleProductPress} />
// <Image style={{ width: 150, height: 235 }} source={{ uri: item.images[0]}} />
), [handleProductPress]);
const listHeader = useMemo(() => (
<ListHeader
gradientHeight={gradientHeight}
onGradientLayout={handleGradientLayout}
storesData={storesData}
popularProducts={popularProducts}
sortedSlots={sortedSlots}
onProductPress={handleProductPress}
dashboardTags={dashboardTags}
activeTagId={activeTagId}
activeTagProducts={activeTagProducts}
onSelectTag={setSelectedTagId}
/>
), [gradientHeight, handleGradientLayout, storesData, popularProducts, sortedSlots, handleProductPress, dashboardTags, activeTagId, activeTagProducts]);
const pageTint = activeTagId == null ? '#FFFFFF' : getTagColor(activeTagId).pageBg;
const pageTintStyle = useMemo(() => [
tw`flex-1`,
{ backgroundColor: pageTint }
], [pageTint]);
const searchBarContainerStyle = useMemo(() => [
tw`w-full px-4 pt-4 pb-0`,
{ backgroundColor: pageTint }
], [pageTint]);
const listContentContainerStyle = useMemo(() => [
tw`pb-24`,
staticStyles.flatListContent
], []);
if (isLoading || isLoadingConsts) {
return (
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
<MyText style={tw`text-gray-500 font-medium`}>
{isLoading ? 'Loading products...' : 'Loading app settings...'}
</MyText>
</View>
);
}
if (error || constsError) {
return (
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Oops!</MyText>
<MyText style={tw`text-gray-500 mt-2`}>
{error ? 'Failed to load products' : 'Failed to load app settings'}
</MyText>
</View>
);
}
let str = ''
displayedProducts.forEach(product => str += `${product.id}-`)
// console.log(str)
return (
<TabLayoutWrapper style={{ backgroundColor: pageTint }}>
<View style={pageTintStyle}>
<View style={searchBarContainerStyle}>
<SearchBar
value={""}
onChangeText={() => { }}
onPress={handleSearchPress}
editable={false}
containerStyle={tw`bg-white`}
onSubmitEditing={() => {
if (inputQuery.trim()) {
router.push(`/(drawer)/(tabs)/home/search-results?q=${encodeURIComponent(inputQuery.trim())}`);
}
}}
returnKeyType="search"
/>
</View>
<MyFlatList
data={displayedProducts}
keyExtractor={(item) => item.id.toString()}
numColumns={2}
style={{ backgroundColor: pageTint }}
contentContainerStyle={listContentContainerStyle}
columnWrapperStyle={staticStyles.columnWrapper}
renderItem={renderProductItem}
ListHeaderComponent={listHeader}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
tintColor="#3b82f6"
colors={["#3b82f6"]}
/>
}
ListEmptyComponent={
<View style={tw`items-center py-8`}>
<MyText style={tw`text-gray-500`}>No products available</MyText>
</View>
}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
updateCellsBatchingPeriod={50}
/>
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
<AddToCartDialog />
<View style={tw`absolute bottom-2 left-4 right-4`}>
<FloatingCartBar />
</View>
</View>
</TabLayoutWrapper>
);
}