enh
This commit is contained in:
parent
1f801f2146
commit
ccde89b5c8
2 changed files with 198 additions and 86 deletions
|
|
@ -96,6 +96,10 @@ export default function Dashboard() {
|
||||||
const [gradientHeight, setGradientHeight] = useState(0);
|
const [gradientHeight, setGradientHeight] = useState(0);
|
||||||
const [stickyBarLayout, setStickyBarLayout] = useState({ y: 0, height: 0 });
|
const [stickyBarLayout, setStickyBarLayout] = useState({ y: 0, height: 0 });
|
||||||
const [whiteSectionLayout, setWhiteSectionLayout] = useState({ y: 0 });
|
const [whiteSectionLayout, setWhiteSectionLayout] = useState({ y: 0 });
|
||||||
|
const [displayedProducts, setDisplayedProducts] = useState<any[]>([]);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
const { backgroundColor } = useStatusBarStore();
|
const { backgroundColor } = useStatusBarStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -121,6 +125,47 @@ export default function Dashboard() {
|
||||||
const defaultAddress = defaultAddressResponse?.data;
|
const defaultAddress = defaultAddressResponse?.data;
|
||||||
const { getQuickestSlot } = useProductSlotIdentifier();
|
const { getQuickestSlot } = useProductSlotIdentifier();
|
||||||
|
|
||||||
|
// Function to load more products
|
||||||
|
const loadMoreProducts = () => {
|
||||||
|
if (!hasMore || isLoadingMore) return;
|
||||||
|
|
||||||
|
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 startIndex = page * batchSize;
|
||||||
|
const endIndex = startIndex + batchSize;
|
||||||
|
|
||||||
|
// Get the next batch of products
|
||||||
|
const nextBatch = products.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
if (nextBatch.length > 0) {
|
||||||
|
setDisplayedProducts(prev => [...prev, ...nextBatch]);
|
||||||
|
setPage(prev => prev + 1);
|
||||||
|
setHasMore(endIndex < products.length);
|
||||||
|
} else {
|
||||||
|
setHasMore(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingMore(false);
|
||||||
|
}, 500); // Simulate network delay
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize with the first batch of products
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (products.length > 0) {
|
||||||
|
const initialBatch = products.slice(0, 10); // First 10 products
|
||||||
|
setDisplayedProducts(initialBatch);
|
||||||
|
setHasMore(products.length > 10);
|
||||||
|
setPage(1);
|
||||||
|
} else {
|
||||||
|
setDisplayedProducts([]);
|
||||||
|
setHasMore(false);
|
||||||
|
}
|
||||||
|
}, [products]);
|
||||||
|
|
||||||
// Extract popular items IDs as an array to preserve order
|
// Extract popular items IDs as an array to preserve order
|
||||||
const popularItemIds = (() => {
|
const popularItemIds = (() => {
|
||||||
const popularItems = essentialConsts?.popularItems;
|
const popularItems = essentialConsts?.popularItems;
|
||||||
|
|
@ -202,7 +247,20 @@ export default function Dashboard() {
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={[tw`flex-1 bg-white`, { position: 'relative' }]}
|
style={[tw`flex-1 bg-white`, { position: 'relative' }]}
|
||||||
stickyHeaderIndices={[2]}
|
stickyHeaderIndices={[2]}
|
||||||
onScroll={handleScroll}
|
onScroll={(e) => {
|
||||||
|
handleScroll(e);
|
||||||
|
|
||||||
|
// Check if we're near the end of the scroll for vertical loading
|
||||||
|
const { layoutMeasurement, contentOffset, contentSize } = e.nativeEvent;
|
||||||
|
const paddingToBottom = 40;
|
||||||
|
|
||||||
|
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom) {
|
||||||
|
// Load more products when reaching near the end
|
||||||
|
if (!isLoadingMore && hasMore) {
|
||||||
|
loadMoreProducts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
scrollEventThrottle={16}
|
scrollEventThrottle={16}
|
||||||
>
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
|
|
@ -363,7 +421,7 @@ export default function Dashboard() {
|
||||||
<MyText
|
<MyText
|
||||||
style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}
|
style={tw`text-2xl font-extrabold text-gray-900 tracking-tight`}
|
||||||
>
|
>
|
||||||
Delivery Slots
|
Upcoming Delivery Slots
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={tw`text-sm text-gray-500 font-medium mt-1`}>
|
<MyText style={tw`text-sm text-gray-500 font-medium mt-1`}>
|
||||||
Plan your fresh deliveries ahead
|
Plan your fresh deliveries ahead
|
||||||
|
|
@ -516,6 +574,49 @@ export default function Dashboard() {
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* All Products Section - Vertical Infinite Scroll */}
|
||||||
|
<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`}
|
||||||
|
>
|
||||||
|
All Available Products
|
||||||
|
</MyText>
|
||||||
|
<MyText style={tw`text-sm text-gray-500 font-medium mt-1`}>
|
||||||
|
Browse our complete selection
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Product Grid */}
|
||||||
|
<View style={tw`flex-row flex-wrap`}>
|
||||||
|
{displayedProducts.map((item, index: number) => (
|
||||||
|
|
||||||
|
<ProductCard
|
||||||
|
item={item}
|
||||||
|
itemWidth={(screenWidth * 0.9) / 2} // Half of screen width minus padding
|
||||||
|
onPress={() =>
|
||||||
|
router.push(
|
||||||
|
`/(drawer)/(tabs)/home/product-detail/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
showDeliveryInfo={true}
|
||||||
|
miniView={false}
|
||||||
|
nullIfNotAvailable={true}
|
||||||
|
containerComp={({children}) => <View key={item.id} style={tw`w-1/2 pr-2 pb-4`}>{children}</View>}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isLoadingMore && (
|
||||||
|
<View style={tw`items-center py-4`}>
|
||||||
|
<MyText>Loading more...</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ interface ProductCardProps {
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
showDeliveryInfo?: boolean;
|
showDeliveryInfo?: boolean;
|
||||||
miniView?: boolean;
|
miniView?: boolean;
|
||||||
|
nullIfNotAvailable?: boolean;
|
||||||
|
containerComp?: React.ComponentType<any> | React.JSXElementConstructor<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
|
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
|
||||||
|
|
@ -36,7 +38,9 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
onPress,
|
onPress,
|
||||||
showDeliveryInfo = true,
|
showDeliveryInfo = true,
|
||||||
miniView = false,
|
miniView = false,
|
||||||
}) => {
|
nullIfNotAvailable = false,
|
||||||
|
containerComp: ContainerComp = React.Fragment,
|
||||||
|
}) => {
|
||||||
const { data: cartData } = useGetCart();
|
const { data: cartData } = useGetCart();
|
||||||
const { getQuickestSlot } = useProductSlotIdentifier();
|
const { getQuickestSlot } = useProductSlotIdentifier();
|
||||||
const updateCartItem = useUpdateCartItem({
|
const updateCartItem = useUpdateCartItem({
|
||||||
|
|
@ -63,6 +67,11 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
const slotId = getQuickestSlot(item.id);
|
const slotId = getQuickestSlot(item.id);
|
||||||
const displayIsOutOfStock = item.isOutOfStock || !slotId;
|
const displayIsOutOfStock = item.isOutOfStock || !slotId;
|
||||||
|
|
||||||
|
// Return null if nullIfNotAvailable is true and the product is out of stock
|
||||||
|
if (nullIfNotAvailable && displayIsOutOfStock) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const handleQuantityChange = (newQuantity: number) => {
|
const handleQuantityChange = (newQuantity: number) => {
|
||||||
if (newQuantity === 0 && cartItem) {
|
if (newQuantity === 0 && cartItem) {
|
||||||
removeFromCart.mutate({ itemId: cartItem.id });
|
removeFromCart.mutate({ itemId: cartItem.id });
|
||||||
|
|
@ -79,95 +88,97 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyTouchableOpacity
|
<ContainerComp>
|
||||||
style={[
|
<MyTouchableOpacity
|
||||||
tw`bg-white rounded-2xl overflow-hidden border border-gray-200`,
|
style={[
|
||||||
{ width: itemWidth },
|
tw`bg-white rounded-2xl overflow-hidden border border-gray-200 pb-2`,
|
||||||
]}
|
{ width: itemWidth },
|
||||||
onPress={onPress || (() => {/* TODO: Navigate to product detail */})}
|
]}
|
||||||
activeOpacity={0.9}
|
onPress={onPress || (() => {/* TODO: Navigate to product detail */})}
|
||||||
>
|
activeOpacity={0.9}
|
||||||
<View style={tw`relative`}>
|
>
|
||||||
<Image
|
<View style={tw`relative`}>
|
||||||
source={{ uri: item.images?.[0] }}
|
<Image
|
||||||
style={{ width: "100%", height: itemWidth, resizeMode: "cover" }}
|
source={{ uri: item.images?.[0] }}
|
||||||
/>
|
style={{ width: "100%", height: itemWidth, resizeMode: "cover" }}
|
||||||
{displayIsOutOfStock && (
|
/>
|
||||||
<View style={tw`absolute inset-0 bg-black/40 items-center justify-center`}>
|
{displayIsOutOfStock && (
|
||||||
<View style={tw`bg-red-500 px-3 py-1 rounded-full`}>
|
<View style={tw`absolute inset-0 bg-black/40 items-center justify-center`}>
|
||||||
<MyText style={tw`text-white text-xs font-bold`}>Out of Stock</MyText>
|
<View style={tw`bg-red-500 px-3 py-1 rounded-full`}>
|
||||||
|
<MyText style={tw`text-white text-xs font-bold`}>Out of Stock</MyText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{miniView && (
|
||||||
|
<View style={tw`absolute bottom-2 right-2`}>
|
||||||
|
{quantity > 0 ? (
|
||||||
|
<MiniQuantifier value={quantity} onChange={handleQuantityChange} step={item.incrementStep} />
|
||||||
|
) : (
|
||||||
|
<MyTouchableOpacity
|
||||||
|
style={tw`w-8 h-8 rounded-full bg-white items-center justify-center shadow-md`}
|
||||||
|
onPress={() => handleQuantityChange(1)}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<CartIcon focused={false} size={16} color="#2E90FA" />
|
||||||
|
</MyTouchableOpacity>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{miniView && (
|
|
||||||
<View style={tw`absolute bottom-2 right-2`}>
|
|
||||||
{quantity > 0 ? (
|
|
||||||
<MiniQuantifier value={quantity} onChange={handleQuantityChange} step={item.incrementStep} />
|
|
||||||
) : (
|
|
||||||
<MyTouchableOpacity
|
|
||||||
style={tw`w-8 h-8 rounded-full bg-white items-center justify-center shadow-md`}
|
|
||||||
onPress={() => handleQuantityChange(1)}
|
|
||||||
activeOpacity={0.8}
|
|
||||||
>
|
|
||||||
<CartIcon focused={false} size={16} color="#2E90FA" />
|
|
||||||
</MyTouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={tw`px-3 pt-3`}>
|
|
||||||
<MyText style={tw`text-gray-900 font-bold text-sm mb-1`} numberOfLines={2}>
|
|
||||||
{item.name}
|
|
||||||
</MyText>
|
|
||||||
|
|
||||||
<View style={tw`flex-row items-baseline mb-2`}>
|
|
||||||
<MyText style={tw`text-brand500 font-bold text-base`}>₹{item.price}</MyText>
|
|
||||||
{item.marketPrice && Number(item.marketPrice) > Number(item.price) && (
|
|
||||||
<MyText style={tw`text-gray-400 text-xs ml-2 line-through`}>₹{item.marketPrice}</MyText>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={tw`flex-row items-center mb-2`}>
|
|
||||||
<MyText style={tw`text-gray-500 text-xs font-medium`}>Quantity: <MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(item.productQuantity || 1, item.unitNotation).display}</MyText></MyText>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{showDeliveryInfo && item.nextDeliveryDate && (
|
<View style={tw`px-3 pt-3`}>
|
||||||
<View style={tw`flex-row items-center bg-brand50 px-2 py-1.5 rounded-lg self-start mb-2 border border-brand100`}>
|
<MyText style={tw`text-gray-900 font-bold text-sm mb-1`} numberOfLines={2}>
|
||||||
<MaterialIcons name="local-shipping" size={12} color="#2E90FA" />
|
{item.name}
|
||||||
<MyText style={tw`text-[10px] text-brand700 ml-1.5 font-bold`}>
|
</MyText>
|
||||||
{dayjs(item.nextDeliveryDate).format("ddd, DD MMM • h:mm A")}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!miniView && (
|
<View style={tw`flex-row items-baseline mb-2`}>
|
||||||
<>
|
<MyText style={tw`text-brand500 font-bold text-base`}>₹{item.price}</MyText>
|
||||||
{displayIsOutOfStock ? (
|
{item.marketPrice && Number(item.marketPrice) > Number(item.price) && (
|
||||||
<View style={tw`bg-gray-100 rounded-lg items-center mt-1`}>
|
<MyText style={tw`text-gray-400 text-xs ml-2 line-through`}>₹{item.marketPrice}</MyText>
|
||||||
<MyText style={tw`text-gray-400 text-xs font-bold uppercase tracking-wide`}>Unavailable</MyText>
|
|
||||||
</View>
|
|
||||||
) : quantity > 0 ? (
|
|
||||||
<Quantifier
|
|
||||||
value={quantity}
|
|
||||||
setValue={handleQuantityChange}
|
|
||||||
step={item.incrementStep}
|
|
||||||
unit={item.unitNotation}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MyTouchableOpacity
|
|
||||||
style={tw`bg-brand500 py-2 rounded-lg items-center mt-1`}
|
|
||||||
onPress={() => handleQuantityChange(1)}
|
|
||||||
>
|
|
||||||
<View style={tw`flex-row items-center`}>
|
|
||||||
<CartIcon focused={false} size={16} color="white" />
|
|
||||||
<MyText style={tw`text-white text-xs font-bold uppercase tracking-wide ml-1`}>Add to Cart</MyText>
|
|
||||||
</View>
|
|
||||||
</MyTouchableOpacity>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</View>
|
||||||
)}
|
<View style={tw`flex-row items-center mb-2`}>
|
||||||
</View>
|
<MyText style={tw`text-gray-500 text-xs font-medium`}>Quantity: <MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(item.productQuantity || 1, item.unitNotation).display}</MyText></MyText>
|
||||||
</MyTouchableOpacity>
|
</View>
|
||||||
|
|
||||||
|
{showDeliveryInfo && item.nextDeliveryDate && (
|
||||||
|
<View style={tw`flex-row items-center bg-brand50 px-2 py-1.5 rounded-lg self-start mb-2 border border-brand100`}>
|
||||||
|
<MaterialIcons name="local-shipping" size={12} color="#2E90FA" />
|
||||||
|
<MyText style={tw`text-[10px] text-brand700 ml-1.5 font-bold`}>
|
||||||
|
{dayjs(item.nextDeliveryDate).format("ddd, DD MMM • h:mm A")}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!miniView && (
|
||||||
|
<>
|
||||||
|
{displayIsOutOfStock ? (
|
||||||
|
<View style={tw`bg-gray-100 rounded-lg items-center mt-1`}>
|
||||||
|
<MyText style={tw`text-gray-400 text-xs font-bold uppercase tracking-wide`}>Unavailable</MyText>
|
||||||
|
</View>
|
||||||
|
) : quantity > 0 ? (
|
||||||
|
<Quantifier
|
||||||
|
value={quantity}
|
||||||
|
setValue={handleQuantityChange}
|
||||||
|
step={item.incrementStep}
|
||||||
|
unit={item.unitNotation}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MyTouchableOpacity
|
||||||
|
style={tw`bg-brand500 py-2 rounded-lg items-center mt-1`}
|
||||||
|
onPress={() => handleQuantityChange(1)}
|
||||||
|
>
|
||||||
|
<View style={tw`flex-row items-center`}>
|
||||||
|
<CartIcon focused={false} size={16} color="white" />
|
||||||
|
<MyText style={tw`text-white text-xs font-bold uppercase tracking-wide ml-1`}>Add to Cart</MyText>
|
||||||
|
</View>
|
||||||
|
</MyTouchableOpacity>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</MyTouchableOpacity>
|
||||||
|
</ContainerComp>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue