first commit
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Animated,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { InsideHeader } from '../../Component/Index';
|
||||
import {
|
||||
useFetchLiveAuction,
|
||||
useFetchPurchaseAuction,
|
||||
} from '../../Services/BBPS/Hooks';
|
||||
import colors from '../../Constant/Color';
|
||||
import LiveAuction from './LiveAuction';
|
||||
import PurchaseAuction from './PurchaseAuction';
|
||||
import { ShoppingCart } from 'lucide-react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import FontsFamily from '../../Constant/FontsFamily';
|
||||
import { useSelector } from 'react-redux';
|
||||
const { width } = Dimensions.get('screen')
|
||||
const TAB_WIDTH = 140;
|
||||
const TAB_HEIGHT = 44;
|
||||
|
||||
const Auction = () => {
|
||||
const Navigation = useNavigation() as any
|
||||
const { data: liveAuctionData = {} }: any = useFetchLiveAuction();
|
||||
const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction();
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
|
||||
|
||||
const liveCount = Array.isArray(liveAuctionData?.result)
|
||||
? liveAuctionData?.pages[0]?.totalCount
|
||||
: 0;
|
||||
|
||||
const purchaseCount = Array.isArray(purchaseAuctionData?.data)
|
||||
? purchaseAuctionData.data.length
|
||||
: 0;
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live');
|
||||
|
||||
const translateX = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const onTabPress = (tab: 'live' | 'purchase', index: number) => {
|
||||
setSelectedTab(tab);
|
||||
Animated.spring(translateX, {
|
||||
toValue: index * TAB_WIDTH,
|
||||
useNativeDriver: true,
|
||||
friction: 8,
|
||||
}).start();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<InsideHeader title="Auction" showArrow />
|
||||
|
||||
{/* Switch Tabs */}
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
<View style={styles.switchWrapper}>
|
||||
<Animated.View
|
||||
pointerEvents="none"
|
||||
style={[
|
||||
styles.activeTab,
|
||||
{
|
||||
transform: [{ translateX }],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.tab}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onTabPress('live', 0)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
selectedTab === 'live' && styles.activeText,
|
||||
]}
|
||||
>
|
||||
Live ({liveCount})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.tab}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onTabPress('purchase', 1)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
selectedTab === 'purchase' && styles.activeText,
|
||||
]}
|
||||
>
|
||||
Purchased ({purchaseCount})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{/* Cart */}
|
||||
<View>
|
||||
{SelectedPurchaseList.length && <View style={{ borderWidth: 2, borderColor: colors.white, backgroundColor: colors.primaryLight, position: 'absolute', borderRadius: 50, zIndex: 20, right: 0, top: -10, paddingHorizontal:2 }}>
|
||||
<Text style={{ fontSize: 12, color: colors.black }}>{SelectedPurchaseList.length}</Text>
|
||||
</View>}
|
||||
<TouchableOpacity style={{ padding: 10, backgroundColor: colors.primaryLight, borderRadius: 20 }} onPress={() => { Navigation.navigate(NavigationString.PurchaseList) }}>
|
||||
<ShoppingCart size={22} color={colors.black} strokeWidth={2} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{/* Content */}
|
||||
{selectedTab === 'live' ? <LiveAuction /> : <PurchaseAuction />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Auction;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
|
||||
/* ---------- Switch UI ---------- */
|
||||
|
||||
switchWrapper: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 14,
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 18,
|
||||
padding: 2,
|
||||
height: TAB_HEIGHT,
|
||||
},
|
||||
|
||||
tab: {
|
||||
width: TAB_WIDTH,
|
||||
height: TAB_HEIGHT - 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 2,
|
||||
},
|
||||
|
||||
tabText: {
|
||||
fontSize: 14,
|
||||
color: '#64748b',
|
||||
lineHeight: 18,
|
||||
fontFamily: FontsFamily.poppinsSemiBold
|
||||
},
|
||||
|
||||
activeText: {
|
||||
color: colors.primary,
|
||||
},
|
||||
|
||||
activeTab: {
|
||||
position: 'absolute',
|
||||
top: 2,
|
||||
left: 2,
|
||||
width: TAB_WIDTH,
|
||||
height: TAB_HEIGHT - 4,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 6,
|
||||
elevation: 3,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native';
|
||||
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||
import LiveAuctionTile from './LiveAuctionTile';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useFetchLiveAuction } from '../../Services/BBPS/Hooks';
|
||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||
import { Skelton } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { selectPurchaseAuctionSlice, togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||
|
||||
const LiveAuction = () => {
|
||||
const navigation: any = useNavigation();
|
||||
const Dispatch = useDispatch()
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveAuction();
|
||||
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRefetching) {
|
||||
setShowRefreshBanner(true);
|
||||
} else {
|
||||
const timer = setTimeout(() => {
|
||||
setShowRefreshBanner(false);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isRefetching]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage]);
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
if (!isFetchingNextPage) return null;
|
||||
return (
|
||||
<View style={{ paddingVertical: 12 }}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}, [isFetchingNextPage]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: any) => {
|
||||
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.auctionNumber === item.auctionNumber)
|
||||
return <LiveAuctionTile
|
||||
data={item}
|
||||
index={index}
|
||||
auctionId={item.auctionId}
|
||||
auctionDispatchDate={item.auctionDispatchDate}
|
||||
isPurchased={isPurchase !== -1 ? true : false}
|
||||
purchaseEnd={item.purchaseEnd}
|
||||
auctionOfWork={item.auctionOfWork}
|
||||
auctionFees={item.auctionFees}
|
||||
auctionNumber={item.auctionNumber}
|
||||
onPurchasePress={() => { Dispatch(togglePurchaseAuction(item)) }}
|
||||
onDetailsPress={() => { }}
|
||||
/>
|
||||
},
|
||||
[SelectedPurchaseList]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Skelton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchInput onChange={() => { }} placeholder="Search...." />
|
||||
|
||||
{showRefreshBanner && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(200)}
|
||||
exiting={FadeOutUp.duration(500)}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 14,
|
||||
marginTop: 6,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#FFFFFF',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 8,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
elevation: 4,
|
||||
zIndex: 10, // Ensure it stays on top
|
||||
position: 'absolute',
|
||||
top: 60, // Adjust based on your SearchInput height
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: '#374151',
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
Refreshing auctions
|
||||
</Text>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
<FlatList
|
||||
data={data?.result ?? []}
|
||||
keyExtractor={(item) => String(item.auctionId)}
|
||||
renderItem={renderItem}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0.2}
|
||||
ListFooterComponent={renderFooter}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
tintColor="transparent" // Set to transparent if you only want your custom banner to show
|
||||
colors={[colors.primary]}
|
||||
/>
|
||||
}
|
||||
initialNumToRender={6}
|
||||
maxToRenderPerBatch={6}
|
||||
windowSize={7}
|
||||
removeClippedSubviews
|
||||
updateCellsBatchingPeriod={50}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveAuction;
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { memo } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import moment from 'moment';
|
||||
import colors from '../../Constant/Color';
|
||||
import { LiveAuctionTileProps } from './types/types';
|
||||
import AuctionDetail from './Modal/AuctionDetail';
|
||||
import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native';
|
||||
|
||||
const LiveAuctionTile: React.FC<LiveAuctionTileProps> = ({
|
||||
data,
|
||||
isPurchased = false,
|
||||
onPurchasePress,
|
||||
auctionDispatchDate,
|
||||
auctionOfWork,
|
||||
purchaseEnd,
|
||||
auctionFees,
|
||||
auctionNumber,
|
||||
}) => {
|
||||
const [showDetail, setShowDetail] = React.useState(false);
|
||||
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
|
||||
|
||||
return (
|
||||
<View style={styles.cardWrapper}>
|
||||
{/* Status Tag */}
|
||||
<View
|
||||
style={[
|
||||
styles.statusTag,
|
||||
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.container}
|
||||
activeOpacity={0.9}
|
||||
onPress={() => setShowDetail(true)}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.badge}>
|
||||
<Tag size={14} color={colors.primary} />
|
||||
<Text style={styles.badgeText}>#{auctionNumber}</Text>
|
||||
</View>
|
||||
<Text style={styles.priceText}>₹{auctionFees}</Text>
|
||||
</View>
|
||||
|
||||
{/* Work Title */}
|
||||
<Text style={styles.workTitle} numberOfLines={2}>
|
||||
{auctionOfWork ?? 'General Auction Work'}
|
||||
</Text>
|
||||
|
||||
{/* Info Grid */}
|
||||
<View style={styles.infoGrid}>
|
||||
<View style={styles.infoItem}>
|
||||
<Calendar size={16} color="#64748b" />
|
||||
<View style={styles.infoTextGroup}>
|
||||
<Text style={styles.infoLabel}>Start</Text>
|
||||
<Text style={styles.infoValue}>{auctionDispatchDate ?? '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoItem}>
|
||||
<Calendar size={16} color={purchaseEnded ? "#ef4444" : "#22c55e"} />
|
||||
<View style={styles.infoTextGroup}>
|
||||
<Text style={styles.infoLabel}>Deadline</Text>
|
||||
<Text style={[styles.infoValue, purchaseEnded && { color: '#ef4444' }]}>
|
||||
{moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Action Bar */}
|
||||
<View style={styles.actionBar}>
|
||||
<TouchableOpacity style={styles.iconBtn} onPress={() => setShowDetail(true)}>
|
||||
<Info size={18} color={colors.primary} />
|
||||
<Text style={styles.iconBtnText}>Details</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.mainButton,
|
||||
purchaseEnded
|
||||
? styles.btnDisabled
|
||||
: isPurchased
|
||||
? styles.btnPurchased
|
||||
: styles.btnActive,
|
||||
]}
|
||||
onPress={onPurchasePress}
|
||||
disabled={purchaseEnded}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.mainButtonText,
|
||||
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
|
||||
]}
|
||||
>
|
||||
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
|
||||
</Text>
|
||||
{!purchaseEnded && (
|
||||
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Auction Detail Modal */}
|
||||
<AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LiveAuctionTile);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
cardWrapper: {
|
||||
marginVertical: 10,
|
||||
marginHorizontal: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
|
||||
// Modern shadow
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 20,
|
||||
elevation: 8,
|
||||
},
|
||||
statusTag: {
|
||||
width: 6,
|
||||
height: '100%',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.primary + '25',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 12,
|
||||
gap: 6,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: colors.primary,
|
||||
},
|
||||
priceText: {
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
color: '#0f172a',
|
||||
},
|
||||
workTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#334155',
|
||||
lineHeight: 24,
|
||||
marginBottom: 16,
|
||||
},
|
||||
infoGrid: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#f0f4f8',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 16,
|
||||
marginBottom: 18,
|
||||
},
|
||||
infoItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
infoTextGroup: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 10,
|
||||
color: '#94a3b8',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '700',
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
color: '#1e293b',
|
||||
},
|
||||
actionBar: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
iconBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#e2e8f0',
|
||||
},
|
||||
iconBtnText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: colors.primary,
|
||||
},
|
||||
mainButton: {
|
||||
flex: 1,
|
||||
height: 46,
|
||||
borderRadius: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
btnActive: {
|
||||
backgroundColor: '#4ade80', // light green
|
||||
},
|
||||
btnPurchased: {
|
||||
backgroundColor: colors.error,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.error,
|
||||
},
|
||||
btnDisabled: {
|
||||
backgroundColor: '#e2e8f0',
|
||||
},
|
||||
mainButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,345 @@
|
||||
import React, { memo, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
Platform,
|
||||
StatusBar,
|
||||
} from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
X,
|
||||
Hash,
|
||||
Briefcase,
|
||||
Calendar,
|
||||
CreditCard,
|
||||
FileText,
|
||||
Download,
|
||||
Eye,
|
||||
Layers,
|
||||
ArrowLeft,
|
||||
Info,
|
||||
Clock
|
||||
} from 'lucide-react-native';
|
||||
import colors from '../../../Constant/Color';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import AuctionItemDetail from './AuctionItemDetail';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||
const inset = useSafeAreaInsets()
|
||||
const [allBoolean, setAllBoolean] = useState({ showItemDetail: false })
|
||||
const formatDate = (date?: string) => {
|
||||
if (!date) return '-';
|
||||
return moment(date, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A');
|
||||
};
|
||||
|
||||
const isLive = data?.purchaseEnd
|
||||
? moment().isBefore(moment(data.purchaseEnd, 'DD-MM-YYYY HH:mm:ss'))
|
||||
: false;
|
||||
|
||||
// Internal Components
|
||||
const Section = ({ title, icon: Icon, children }: any) => (
|
||||
<View style={styles.sectionContainer}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Icon size={20} color={colors.primary} />
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
</View>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
|
||||
const InfoBox = ({ label, value, full = false }: any) => (
|
||||
<View style={[styles.infoBox, full ? { width: '100%' } : { width: '48%' }]}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.value}>{value || '-'}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isVisible={visible}
|
||||
style={styles.fullModal}
|
||||
animationIn="slideInRight"
|
||||
animationOut="slideOutRight"
|
||||
useNativeDriver
|
||||
hideModalContentWhileAnimating
|
||||
statusBarTranslucent
|
||||
>
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<StatusBar barStyle="dark-content" />
|
||||
|
||||
{/* Full Header */}
|
||||
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||
<ArrowLeft size={24} color="#1e293b" />
|
||||
</TouchableOpacity>
|
||||
<View style={styles.headerTitleContainer}>
|
||||
<Text style={styles.headerTitleText}>Auction Overview</Text>
|
||||
<Text style={styles.headerIdText}>Ref: {data?.auctionNumber || 'N/A'}</Text>
|
||||
</View>
|
||||
<View style={styles.liveIndicator}>
|
||||
<View style={[styles.dot, { backgroundColor: isLive ? '#22c55e' : '#94a3b8' }]} />
|
||||
<Text style={styles.liveText}>{isLive ? 'LIVE' : 'CLOSED'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Scrollable Body */}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
{/* 1. Basic Info Grid */}
|
||||
<Section title="Details" icon={Layers}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Auction No." value={data?.auctionNumber} />
|
||||
<InfoBox label="Pattern" value={data?.auctionPattern || 'Forward'} />
|
||||
<InfoBox label="Mode" value={data?.auctionMode || 'Open'} />
|
||||
<InfoBox label="Category" value={data?.categoryOfAuction || 'General'} />
|
||||
<InfoBox label="EMD Type" value={data?.emd || 'Online'} />
|
||||
<InfoBox label="EMD Figure" value={data?.emdFigure || 'Item Wise'} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* 2. Work Description */}
|
||||
<Section title="Auction Details" icon={Briefcase}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Auction Name" value={'Auction of cars'} />
|
||||
<InfoBox label="Category Of Auction " value={'Live'} />
|
||||
<InfoBox label="Auto Time Increment " value={'yes'} />
|
||||
<InfoBox label="Auction Fees (In Figure) " value={'500'} />
|
||||
<InfoBox label="Auction Fees (In Words)" value={'Five Hundred'} />
|
||||
<InfoBox label="Auction Keyword" value={'Dev Furniture, Furnture'} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* 3. Timeline Section */}
|
||||
<Section title="Key Dates" icon={Calendar}>
|
||||
<View style={styles.timelineContainer}>
|
||||
<View style={styles.timelineRow}>
|
||||
<View style={styles.iconCircle}><Clock size={16} color="#64748b" /></View>
|
||||
<View>
|
||||
<Text style={styles.label}>Auction Purchase (Start)</Text>
|
||||
<Text style={styles.value}>{formatDate(data?.auctionDispatchDate)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.timelineConnector} />
|
||||
<View style={styles.timelineRow}>
|
||||
<View style={[styles.iconCircle, { backgroundColor: '#fee2e2' }]}><Clock size={16} color="#ef4444" /></View>
|
||||
<View>
|
||||
<Text style={styles.label}>Purchase Deadline (End)</Text>
|
||||
<Text style={styles.value}>{formatDate(data?.purchaseEnd)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* 4. Item Table Section */}
|
||||
<Section title="Auction Items" icon={FileText}>
|
||||
<View style={styles.tableWrapper}>
|
||||
<View style={styles.tableHead}>
|
||||
<Text style={[styles.tableLabel, { flex: 2 }]}>Item</Text>
|
||||
<Text style={styles.tableLabel}>Qty</Text>
|
||||
<Text style={[styles.tableLabel, { textAlign: 'right' }]}>View</Text>
|
||||
</View>
|
||||
{/* Dummy Item Row */}
|
||||
<View style={styles.tableRow}>
|
||||
<View style={{ flex: 2 }}>
|
||||
<Text style={styles.itemMain}>Sick Sari / Laundry</Text>
|
||||
<Text style={styles.itemSub}>Category: Sari</Text>
|
||||
</View>
|
||||
<Text style={styles.itemQty}>120,000</Text>
|
||||
<TouchableOpacity style={styles.actionIcon} onPress={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: true })) }}>
|
||||
<Eye size={18} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* 5. Downloads */}
|
||||
<Section title="Downloads" icon={Download}>
|
||||
<TouchableOpacity style={styles.fileCard}>
|
||||
<View style={styles.fileIcon}><FileText size={20} color="#ef4444" /></View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.fileName}>Auction_Notice_Main.pdf</Text>
|
||||
<Text style={styles.fileSize}>1.2 MB • PDF</Text>
|
||||
</View>
|
||||
<Download size={20} color="#94a3b8" />
|
||||
</TouchableOpacity>
|
||||
</Section>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
{/* Sticky Footer */}
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.mainBtn, !isLive && styles.btnDisabled]}
|
||||
disabled={!isLive}
|
||||
>
|
||||
<Text style={styles.mainBtnText}>
|
||||
{isLive ? 'Add to Cart / Purchase' : 'Auction Closed'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<AuctionItemDetail data={{
|
||||
productName: "sick sari",
|
||||
timeDuration: "112 YEAR",
|
||||
category: "laundry",
|
||||
subCategory: "sari",
|
||||
reservePriceFig: "120000",
|
||||
reservePriceWords: "One Lakh Twenty Thousand",
|
||||
auctionStartFig: "120000",
|
||||
auctionStartWords: "One Lakh Twenty Thousand",
|
||||
bidVariationFig: "200",
|
||||
bidVariationWords: "Two Hundred",
|
||||
emdFig: "2000",
|
||||
emdWords: "Two Thousand",
|
||||
description: "Auction of cars",
|
||||
documentName: "BBPS screens.pdf"
|
||||
}} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, showItemDetail: false })) }} visible={allBoolean.showItemDetail} />
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fullModal: {
|
||||
margin: 0,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f1f5f9',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
backBtn: {
|
||||
padding: 8,
|
||||
marginRight: 8,
|
||||
},
|
||||
headerTitleContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
headerTitleText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: '#0f172a',
|
||||
},
|
||||
headerIdText: {
|
||||
fontSize: 13,
|
||||
color: '#64748b',
|
||||
},
|
||||
liveIndicator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f8fafc',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 20,
|
||||
gap: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e2e8f0',
|
||||
},
|
||||
dot: { width: 8, height: 8, borderRadius: 4 },
|
||||
liveText: { fontSize: 11, fontWeight: '800', color: '#475569' },
|
||||
|
||||
scrollView: { flex: 1, backgroundColor: '#f8fafc' },
|
||||
scrollContent: { padding: 16, paddingBottom: 100 },
|
||||
|
||||
sectionContainer: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#eef2f6',
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
marginBottom: 20,
|
||||
paddingBottom: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f8fafc',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#1e293b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
grid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
infoBox: { marginBottom: 16 },
|
||||
label: { fontSize: 12, color: '#94a3b8', fontWeight: '600', marginBottom: 4 },
|
||||
value: { fontSize: 15, fontWeight: '700', color: '#334155' },
|
||||
spacer: { height: 12 },
|
||||
|
||||
timelineContainer: { paddingLeft: 8 },
|
||||
timelineRow: { flexDirection: 'row', alignItems: 'center', gap: 15 },
|
||||
iconCircle: { width: 34, height: 34, borderRadius: 10, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' },
|
||||
timelineConnector: { width: 2, height: 20, backgroundColor: '#f1f5f9', marginLeft: 16 },
|
||||
|
||||
tableWrapper: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 12 },
|
||||
tableHead: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 12 },
|
||||
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
||||
tableRow: { flexDirection: 'row', alignItems: 'center' },
|
||||
itemMain: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
||||
itemSub: { fontSize: 12, color: '#64748b' },
|
||||
itemQty: { flex: 1, fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||
actionIcon: { padding: 6 },
|
||||
|
||||
fileCard: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
||||
fileIcon: { width: 40, height: 40, borderRadius: 8, backgroundColor: '#fee2e2', alignItems: 'center', justifyContent: 'center' },
|
||||
fileName: { fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||
fileSize: { fontSize: 12, color: '#94a3b8' },
|
||||
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#f1f5f9',
|
||||
},
|
||||
mainBtn: {
|
||||
backgroundColor: colors.primary,
|
||||
height: 56,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: colors.primary,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 8,
|
||||
elevation: 4,
|
||||
},
|
||||
btnDisabled: { backgroundColor: '#cbd5e1' },
|
||||
mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' },
|
||||
});
|
||||
|
||||
export default memo(AuctionFullDetail);
|
||||
@@ -0,0 +1,274 @@
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
Platform,
|
||||
StatusBar,
|
||||
Dimensions
|
||||
} from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import {
|
||||
X,
|
||||
Package,
|
||||
Clock,
|
||||
Tag,
|
||||
IndianRupee,
|
||||
FileText,
|
||||
Download,
|
||||
ArrowLeft,
|
||||
Info,
|
||||
Layers,
|
||||
ChevronRight
|
||||
} from 'lucide-react-native';
|
||||
import colors from '../../../Constant/Color';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
// --- Exact Dummy Data Mapping ---
|
||||
const ITEM_DUMMY_DATA = {
|
||||
productName: "sick sari",
|
||||
timeDuration: "112 YEAR",
|
||||
category: "laundry",
|
||||
subCategory: "sari",
|
||||
reservePriceFig: "120000",
|
||||
reservePriceWords: "One Lakh Twenty Thousand",
|
||||
auctionStartFig: "120000",
|
||||
auctionStartWords: "One Lakh Twenty Thousand",
|
||||
bidVariationFig: "200",
|
||||
bidVariationWords: "Two Hundred",
|
||||
emdFig: "2000",
|
||||
emdWords: "Two Thousand",
|
||||
description: "Auction of cars",
|
||||
documentName: "BBPS screens.pdf"
|
||||
};
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
data?: typeof ITEM_DUMMY_DATA;
|
||||
}
|
||||
|
||||
const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data = ITEM_DUMMY_DATA }) => {
|
||||
const inset = useSafeAreaInsets()
|
||||
const Section = ({ title, icon: Icon, children }: any) => (
|
||||
<View style={styles.sectionCard}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<View style={styles.iconCircle}>
|
||||
<Icon size={18} color={colors.primary} strokeWidth={2.5} />
|
||||
</View>
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
</View>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
|
||||
const PriceTile = ({ label, fig, words, full = false }: any) => (
|
||||
<View style={[styles.priceTile, full ? { width: '100%' } : { width: '48%' }]}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.priceFig}>₹{Number(fig).toLocaleString('en-IN')}</Text>
|
||||
<Text style={styles.priceWords}>{words}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isVisible={visible}
|
||||
style={styles.fullModal}
|
||||
animationIn="slideInUp"
|
||||
animationOut="slideOutDown"
|
||||
backdropOpacity={1}
|
||||
useNativeDriver
|
||||
statusBarTranslucent
|
||||
>
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
|
||||
{/* Navbar */}
|
||||
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||
<ArrowLeft size={24} color="#1e293b" />
|
||||
</TouchableOpacity>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Item Information</Text>
|
||||
<Text style={styles.headerSub}>Detail View</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Product Identity Card */}
|
||||
<Section title="Product Specification" icon={Package}>
|
||||
<View style={styles.identityHeader}>
|
||||
<Text style={styles.productNameText}>{data.productName.toUpperCase()}</Text>
|
||||
<View style={styles.categoryBadge}>
|
||||
<Text style={styles.categoryText}>{data.category}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.specGrid}>
|
||||
<View style={styles.specItem}>
|
||||
<Text style={styles.label}>Sub Category</Text>
|
||||
<Text style={styles.specValue}>{data.subCategory}</Text>
|
||||
</View>
|
||||
<View style={styles.specItem}>
|
||||
<Text style={styles.label}>Duration</Text>
|
||||
<Text style={styles.specValue}>{data.timeDuration}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Pricing & Financials */}
|
||||
<Section title="Reserve & Bid Values" icon={IndianRupee}>
|
||||
<PriceTile
|
||||
label="Reserve Price"
|
||||
fig={data.reservePriceFig}
|
||||
words={data.reservePriceWords}
|
||||
full
|
||||
/>
|
||||
<View style={styles.hDivider} />
|
||||
<PriceTile
|
||||
label="Auction Start Value"
|
||||
fig={data.auctionStartFig}
|
||||
words={data.auctionStartWords}
|
||||
full
|
||||
/>
|
||||
<View style={styles.hDivider} />
|
||||
<View style={styles.row}>
|
||||
<PriceTile label="Bid Variation" fig={data.bidVariationFig} words={data.bidVariationWords} />
|
||||
<PriceTile label="EMD Amount" fig={data.emdFig} words={data.emdWords} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Description Card */}
|
||||
<Section title="Brief Description" icon={Info}>
|
||||
<Text style={styles.descContent}>{data.description}</Text>
|
||||
</Section>
|
||||
|
||||
{/* Documents Card */}
|
||||
<Section title="View Documents" icon={FileText}>
|
||||
<View style={styles.docTableHead}>
|
||||
<Text style={styles.tableLabel}>Sr No.</Text>
|
||||
<Text style={[styles.tableLabel, { flex: 2 }]}>Document Name</Text>
|
||||
<Text style={styles.tableLabel}>Action</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.docRow} activeOpacity={0.7}>
|
||||
<Text style={styles.docIndex}>1</Text>
|
||||
<View style={{ flex: 2 }}>
|
||||
<Text style={styles.docTitle} numberOfLines={1}>{data.documentName}</Text>
|
||||
</View>
|
||||
<View style={styles.downloadIconBox}>
|
||||
<Download size={18} color={colors.primary} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Section>
|
||||
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fullModal: { margin: 0, backgroundColor: '#f8fafc' },
|
||||
safeArea: { flex: 1 },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f1f5f9',
|
||||
},
|
||||
backBtn: { padding: 8, marginRight: 8 },
|
||||
headerTitle: { fontSize: 18, fontWeight: '800', color: '#0f172a' },
|
||||
headerSub: { fontSize: 12, color: '#94a3b8', fontWeight: '600', textTransform: 'uppercase' },
|
||||
|
||||
scrollView: { flex: 1 },
|
||||
scrollContent: { padding: 16, paddingBottom: 50 },
|
||||
|
||||
sectionCard: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 24,
|
||||
padding: 20,
|
||||
marginBottom: 16,
|
||||
...Platform.select({
|
||||
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.05, shadowRadius: 10 },
|
||||
android: { elevation: 3 }
|
||||
})
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
marginBottom: 20,
|
||||
},
|
||||
iconCircle: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 12,
|
||||
backgroundColor: colors.primary + '10',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#334155',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
|
||||
// Product Identity
|
||||
identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
|
||||
productNameText: { fontSize: 20, fontWeight: '900', color: '#1e293b', flex: 1 },
|
||||
categoryBadge: { backgroundColor: '#f1f5f9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
|
||||
categoryText: { fontSize: 11, fontWeight: '800', color: '#64748b', textTransform: 'uppercase' },
|
||||
specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 },
|
||||
specItem: { flex: 1 },
|
||||
specValue: { fontSize: 15, fontWeight: '700', color: '#334155', marginTop: 2 },
|
||||
|
||||
// Pricing
|
||||
priceTile: { marginBottom: 16 },
|
||||
label: { fontSize: 11, color: '#94a3b8', fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 },
|
||||
priceFig: { fontSize: 22, fontWeight: '900', color: colors.black },
|
||||
priceWords: { fontSize: 13, color: '#64748b', fontStyle: 'italic', marginTop: 2 },
|
||||
hDivider: { height: 1, backgroundColor: '#f1f5f9', marginBottom: 16 },
|
||||
row: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
|
||||
// Description
|
||||
descContent: { fontSize: 15, color: '#475569', lineHeight: 22, fontWeight: '500' },
|
||||
|
||||
// Table
|
||||
docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', marginBottom: 10 },
|
||||
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
||||
docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
||||
docIndex: { flex: 1, fontSize: 14, fontWeight: '700', color: '#64748b' },
|
||||
docTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
||||
downloadIconBox: { backgroundColor: '#fff', padding: 8, borderRadius: 10, borderWidth: 1, borderColor: '#e2e8f0' },
|
||||
|
||||
// Footer
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0, width: '100%',
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||
borderTopWidth: 1, borderTopColor: '#f1f5f9'
|
||||
},
|
||||
primaryBtn: {
|
||||
backgroundColor: colors.primary,
|
||||
height: 56, borderRadius: 18,
|
||||
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10
|
||||
},
|
||||
primaryBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' }
|
||||
});
|
||||
|
||||
export default memo(AuctionItemDetail);
|
||||
@@ -0,0 +1,259 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Pressable,
|
||||
} from 'react-native';
|
||||
import DatePicker from 'react-native-date-picker';
|
||||
import moment from 'moment';
|
||||
import colors from '../../../Constant/Color';
|
||||
import FontsFamily from '../../../Constant/FontsFamily';
|
||||
|
||||
type AuctionStatus = 'RUNNING' | 'ENDED' | 'NOT_STARTED' | null;
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onApply: (filters: {
|
||||
fromDate: Date | null;
|
||||
toDate: Date | null;
|
||||
status: AuctionStatus;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const FilterModal: React.FC<Props> = ({ visible, onClose, onApply }) => {
|
||||
const [fromDate, setFromDate] = useState<Date | null>(null);
|
||||
const [toDate, setToDate] = useState<Date | null>(null);
|
||||
const [status, setStatus] = useState<AuctionStatus>(null);
|
||||
|
||||
const [openPicker, setOpenPicker] = useState(false);
|
||||
const [pickerType, setPickerType] = useState<'FROM' | 'TO'>('FROM');
|
||||
|
||||
const openDatePicker = (type: 'FROM' | 'TO') => {
|
||||
setPickerType(type);
|
||||
setOpenPicker(true);
|
||||
};
|
||||
|
||||
const handleConfirm = (date: Date) => {
|
||||
if (pickerType === 'FROM') {
|
||||
setFromDate(date);
|
||||
} else {
|
||||
setToDate(date);
|
||||
}
|
||||
setOpenPicker(false);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onApply({ fromDate, toDate, status });
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={visible}
|
||||
animationType="slide"
|
||||
transparent
|
||||
statusBarTranslucent
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
{/* Overlay */}
|
||||
<Pressable style={styles.overlay} onPress={onClose} />
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Filter Auctions</Text>
|
||||
|
||||
{/* ===== DATE RANGE ===== */}
|
||||
<Text style={styles.sectionTitle}>Date Range</Text>
|
||||
|
||||
<View style={styles.dateRow}>
|
||||
<TouchableOpacity
|
||||
style={styles.dateBox}
|
||||
onPress={() => openDatePicker('FROM')}
|
||||
>
|
||||
<Text style={styles.dateLabel}>From</Text>
|
||||
<Text style={styles.dateValue}>
|
||||
{fromDate
|
||||
? moment(fromDate).format('DD MMM YYYY')
|
||||
: 'Select date'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.dateBox}
|
||||
onPress={() => openDatePicker('TO')}
|
||||
>
|
||||
<Text style={styles.dateLabel}>To</Text>
|
||||
<Text style={styles.dateValue}>
|
||||
{toDate
|
||||
? moment(toDate).format('DD MMM YYYY')
|
||||
: 'Select date'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* ===== STATUS ===== */}
|
||||
<Text style={styles.sectionTitle}>Auction Status</Text>
|
||||
|
||||
<View style={styles.statusRow}>
|
||||
{[
|
||||
{ label: 'Running', value: 'RUNNING' },
|
||||
{ label: 'Ended', value: 'ENDED' },
|
||||
{ label: 'Not Started', value: 'NOT_STARTED' },
|
||||
].map(item => {
|
||||
const active = status === item.value;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.value}
|
||||
onPress={() => setStatus(item.value as AuctionStatus)}
|
||||
style={[
|
||||
styles.statusPill,
|
||||
active && styles.statusActive,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.statusText,
|
||||
active && styles.statusActiveText,
|
||||
]}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* ===== FOOTER ===== */}
|
||||
<View style={styles.footer}>
|
||||
{/* <TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
|
||||
<Text style={styles.cancelText}>Cancel</Text>
|
||||
</TouchableOpacity> */}
|
||||
|
||||
<TouchableOpacity style={styles.applyBtn} onPress={handleApply}>
|
||||
<Text style={styles.applyText}>Apply</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* ===== DATE PICKER ===== */}
|
||||
<DatePicker
|
||||
modal
|
||||
open={openPicker}
|
||||
date={
|
||||
pickerType === 'FROM'
|
||||
? fromDate || new Date()
|
||||
: toDate || new Date()
|
||||
}
|
||||
mode="date"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={() => setOpenPicker(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterModal;
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
container: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
backgroundColor: '#fff',
|
||||
borderTopLeftRadius: 22,
|
||||
borderTopRightRadius: 22,
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
marginBottom: 10,
|
||||
color: '#475569',
|
||||
},
|
||||
dateRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 20,
|
||||
},
|
||||
dateBox: {
|
||||
width: '48%',
|
||||
padding: 14,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#F8FAFC',
|
||||
borderWidth: 1,
|
||||
borderColor: '#E2E8F0',
|
||||
},
|
||||
dateLabel: {
|
||||
fontSize: 11,
|
||||
color: '#64748B',
|
||||
marginBottom: 4,
|
||||
},
|
||||
dateValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#0F172A',
|
||||
},
|
||||
statusRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 10,
|
||||
},
|
||||
statusPill: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: '#CBD5E1',
|
||||
backgroundColor: '#F8FAFC',
|
||||
},
|
||||
statusActive: {
|
||||
backgroundColor: colors.primary,
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 13,
|
||||
color: '#334155',
|
||||
fontFamily:FontsFamily.poppinsSemiBold
|
||||
},
|
||||
statusActiveText: {
|
||||
color: '#fff',
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 24,
|
||||
},
|
||||
cancelBtn: {
|
||||
padding: 12,
|
||||
},
|
||||
|
||||
applyBtn: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
width: '100%'
|
||||
},
|
||||
applyText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
fontFamily: FontsFamily.poppinsSemiBold
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState, useCallback, memo } from 'react';
|
||||
import { View, Text, StyleSheet, FlatList, RefreshControl } from 'react-native';
|
||||
import LiveAuctionTile from './LiveAuctionTile';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import moment from 'moment';
|
||||
import { useFetchPurchaseAuction } from '../../Services/BBPS/Hooks';
|
||||
import PurchaseAuctionTile from './PurchaseAuctionTile';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setAuction } from '../../Redux/Slices/SelectedAuction';
|
||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||
import { Skelton } from '../../Component/Index';
|
||||
import { allBooleanProps } from './types/types';
|
||||
import FilterModal from './Modal/FilterModal';
|
||||
|
||||
|
||||
const PurchaseAuction = () => {
|
||||
const navigation = useNavigation() as any;
|
||||
const Dispatch = useDispatch()
|
||||
const { data = {}, isPending } = useFetchPurchaseAuction();
|
||||
const [allBoolean, setAllBoolean] = useState<allBooleanProps>({ isShowFilter: false })
|
||||
const format = 'DD-MM-YYYY HH:mm:ss';
|
||||
|
||||
function isAuctionActive(item: any): boolean {
|
||||
const now = moment();
|
||||
const start = moment(item?.keyDates?.auctionBidding?.startDateTime, format);
|
||||
const end = moment(item?.keyDates?.auctionBidding?.endDateTime, format);
|
||||
|
||||
if (!start.isValid() || !end.isValid()) return false;
|
||||
|
||||
return now.isBetween(start, end, undefined, '[]');
|
||||
}
|
||||
|
||||
function isAuctionEnd(item: any): boolean {
|
||||
const now = moment();
|
||||
const end = moment(item?.keyDates?.auctionBidding?.endDateTime, format);
|
||||
|
||||
if (!end.isValid()) return false;
|
||||
|
||||
return now.isAfter(end);
|
||||
}
|
||||
console.log(allBoolean);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchInput onChange={() => { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
|
||||
{isPending ? <Skelton /> : <FlatList
|
||||
data={data?.data.slice(0, 10) || []}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 200, paddingHorizontal: 10 }}
|
||||
renderItem={({ item, index }) => (
|
||||
<PurchaseAuctionTile
|
||||
item={item}
|
||||
onPressDetails={() => {
|
||||
navigation.navigate('AuctionDetail', { item, onAddToPurchase: () => { }, routeFrom: 'PurchasedAuction' });
|
||||
}}
|
||||
onStartBidding={() => {
|
||||
Dispatch(setAuction(item))
|
||||
// navigation.navigate(NavigationString, { data: item })
|
||||
}}
|
||||
buttonEnable={isAuctionActive(item)}
|
||||
isAuctionEnd={isAuctionEnd(item)}
|
||||
/>
|
||||
)}
|
||||
/>}
|
||||
{allBoolean.isShowFilter && <View style={{ flex: 1 }}>
|
||||
<FilterModal visible={allBoolean.isShowFilter} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: false })) }} onApply={() => { }} />
|
||||
</View>}
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseAuction;
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
||||
import { InsideHeader, Loader } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useOfflineAuctionPurchase } from '../../Services/BBPS/Hooks';
|
||||
|
||||
const dummyAuctionList = [
|
||||
{
|
||||
auctionId: 1,
|
||||
auctionNumber: 'AUC-2025-001',
|
||||
auctionPattern: 'Single Stage',
|
||||
auctionDispatchDate: '25 Feb 2025',
|
||||
auctionDispatcherNumber: 'DISP-001',
|
||||
auctionFees: 2500,
|
||||
gst: 18,
|
||||
gstAmount: 450,
|
||||
portalCharge: 100,
|
||||
payableAmount: 3050,
|
||||
},
|
||||
{
|
||||
auctionId: 2,
|
||||
auctionNumber: 'AUC-2025-002',
|
||||
auctionPattern: 'Two Stage',
|
||||
auctionDispatchDate: '28 Feb 2025',
|
||||
auctionDispatcherNumber: 'DISP-002',
|
||||
auctionFees: 1800,
|
||||
gst: 18,
|
||||
gstAmount: 324,
|
||||
portalCharge: 80,
|
||||
payableAmount: 2204,
|
||||
},
|
||||
{
|
||||
auctionId: 3,
|
||||
auctionNumber: 'AUC-2025-003',
|
||||
auctionPattern: 'Single Stage',
|
||||
auctionDispatchDate: '01 Mar 2025',
|
||||
auctionDispatcherNumber: 'DISP-003',
|
||||
auctionFees: 3200,
|
||||
gst: 18,
|
||||
gstAmount: 576,
|
||||
portalCharge: 120,
|
||||
payableAmount: 3896,
|
||||
},
|
||||
];
|
||||
|
||||
const PurchaseAuctionList = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [list, setList] = useState(dummyAuctionList);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
console.log(SelectedPurchaseList,'iiiiiiiiiiiiiiiiiiiiiiii');
|
||||
|
||||
const { mutate, isPending } = useOfflineAuctionPurchase()
|
||||
const calculateTotalAmount = (item: any) => {
|
||||
const gstAmount =
|
||||
(Number(item.auctionFees) + Number(item.portalCharge)) *
|
||||
(Number(item.gst) / 100);
|
||||
|
||||
const payableAmount =
|
||||
Number(item.auctionFees) + Number(item.portalCharge) + gstAmount;
|
||||
return payableAmount
|
||||
}
|
||||
|
||||
const handleOfflinePurchase = async () => {
|
||||
const body = {
|
||||
auctionDetails: SelectedPurchaseList.map((b: any) => ({
|
||||
auctionDispatchDate: b?.auctionDispatchDate,
|
||||
auctionDispatcherNumber: b?.auctionDispatcherNumber,
|
||||
auctionFees: b?.auctionFees,
|
||||
auctionId: b?.auctionId,
|
||||
auctionNumber: b?.auctionNumber,
|
||||
auctionPattern: b?.auctionPattern,
|
||||
gst: b?.gst,
|
||||
gstAmount: b?.gstAmount,
|
||||
payableAmount: calculateTotalAmount(b),
|
||||
portalCharge: b?.portalCharge
|
||||
})),
|
||||
orderNo: ''
|
||||
};
|
||||
|
||||
try {
|
||||
// console.log(body);
|
||||
mutate(body, {
|
||||
onSuccess: (res) => {
|
||||
console.log(res, '---------------------purchsase response');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error, "================purchse Error");
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log("purchase offline error", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePurchase = (type: 'online' | 'offline') => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase();
|
||||
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: typeof dummyAuctionList[0] }) => (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.title}>{item.auctionNumber}</Text>
|
||||
<Text style={styles.pattern}>{item.auctionPattern}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<Calendar size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Dispatch Date: {item.auctionDispatchDate}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<User size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Dispatcher No: {item.auctionDispatcherNumber}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Auction Fees: ₹{item.auctionFees}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>GST ({item.gst}%): ₹{item.gstAmount}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Portal Charge: ₹{item.portalCharge}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.totalRow}>
|
||||
<CreditCard size={20} color={colors.white} />
|
||||
<Text style={styles.total}>Total: ₹{item.payableAmount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<InsideHeader title="Purchase List" showArrow />
|
||||
<View style={styles.screen}>
|
||||
<FlatList
|
||||
data={SelectedPurchaseList}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.auctionId.toString()}
|
||||
contentContainerStyle={styles.container}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.bottomButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { marginRight: 8 }]}
|
||||
onPress={() => handlePurchase('online')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Purchase Online</Text>
|
||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { marginLeft: 8 }]}
|
||||
onPress={() => handlePurchase('offline')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Purchase Offline</Text>
|
||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{loading && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<Loader />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseAuctionList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
padding: 16,
|
||||
paddingBottom: 120,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
elevation: 5,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.12,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowRadius: 8,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
},
|
||||
pattern: {
|
||||
fontSize: 14,
|
||||
color: colors.primary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
cardRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
marginBottom: 4,
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: '#555',
|
||||
},
|
||||
totalRow: {
|
||||
marginTop: 10,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
total: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
},
|
||||
bottomButtonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.white,
|
||||
width: '100%',
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 10,
|
||||
elevation: 5
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
fontSize: 14,
|
||||
},
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.2)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,274 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import Animated, {
|
||||
FadeInUp,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import moment from 'moment';
|
||||
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
||||
import colors from '../../Constant/Color';
|
||||
|
||||
const PurchaseAuctionTile = ({
|
||||
item,
|
||||
onPressDetails,
|
||||
onStartBidding,
|
||||
buttonEnable,
|
||||
isAuctionEnd,
|
||||
}: any) => {
|
||||
const scale = useSharedValue(1);
|
||||
const elevation = useSharedValue(8);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: scale.value }],
|
||||
elevation: elevation.value, // Android-safe
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePressIn = () => {
|
||||
scale.value = withTiming(0.97, { duration: 120 });
|
||||
elevation.value = withTiming(3, { duration: 120 });
|
||||
};
|
||||
|
||||
const handlePressOut = () => {
|
||||
scale.value = withTiming(1, { duration: 160 });
|
||||
elevation.value = withTiming(8, { duration: 160 });
|
||||
};
|
||||
|
||||
const statusColor = isAuctionEnd
|
||||
? '#EF4444'
|
||||
: buttonEnable
|
||||
? colors.primary
|
||||
: '#94A3B8';
|
||||
|
||||
const lightStatusBg = isAuctionEnd
|
||||
? '#FEF2F2'
|
||||
: buttonEnable
|
||||
? '#EFF6FF'
|
||||
: '#F8FAFC';
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeInUp.duration(400)}
|
||||
style={[styles.shadowContainer, animatedStyle]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={onPressDetails}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
style={styles.cardInternal}
|
||||
>
|
||||
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
|
||||
|
||||
<View style={styles.mainInfo}>
|
||||
{/* Header */}
|
||||
<View style={styles.headerRow}>
|
||||
<View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: lightStatusBg }]}>
|
||||
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
||||
<Text style={[styles.statusText, { color: statusColor }]}>
|
||||
{isAuctionEnd ? 'ENDED' : buttonEnable ? 'LIVE' : 'PENDING'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.title}>{item.auctionNumber}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.amountContainer}>
|
||||
<Text style={styles.amount}>
|
||||
₹{item.auctionFees?.toLocaleString()}
|
||||
</Text>
|
||||
<Text style={styles.feeLabel}>Auction Fee</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Date */}
|
||||
<View style={styles.dateRow}>
|
||||
<Text style={styles.dateText}>
|
||||
<Text style={styles.dateLabel}>Bidding Ends: </Text>
|
||||
{moment(item?.keyDates?.auctionBidding?.endDateTime).format(
|
||||
'DD MMM, hh:mm A'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Footer */}
|
||||
<View style={styles.footer}>
|
||||
<View style={styles.footerTop}>
|
||||
<Text style={styles.footerLabel}>Remaining Time</Text>
|
||||
{buttonEnable && !isAuctionEnd ? (
|
||||
<CountdownTimer
|
||||
endDate={item.keyDates.auctionBidding.endDateTime}
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.endedText}>--:--:--</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
disabled={!buttonEnable}
|
||||
onPress={onStartBidding}
|
||||
activeOpacity={0.85}
|
||||
style={[
|
||||
styles.actionButton,
|
||||
{ backgroundColor: statusColor },
|
||||
!buttonEnable && styles.disabledButton,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.addText}>
|
||||
{isAuctionEnd
|
||||
? 'Bidding Closed'
|
||||
: !buttonEnable
|
||||
? 'Payment Required'
|
||||
: 'Proceed to Start Bidding'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseAuctionTile;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
shadowContainer: {
|
||||
marginHorizontal: 5,
|
||||
marginTop: 14,
|
||||
marginBottom: 6,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#fff',
|
||||
elevation: 8,
|
||||
},
|
||||
cardInternal: {
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
statusAccent: {
|
||||
width: 5,
|
||||
height: '100%',
|
||||
},
|
||||
mainInfo: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
},
|
||||
headerRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 12,
|
||||
},
|
||||
statusBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
marginBottom: 6,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
statusDot: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
marginRight: 5,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '800',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
title: {
|
||||
fontWeight: '800',
|
||||
fontSize: 16,
|
||||
color: '#1E293B',
|
||||
},
|
||||
amountContainer: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
amount: {
|
||||
fontWeight: '900',
|
||||
fontSize: 18,
|
||||
color: '#0F172A',
|
||||
},
|
||||
feeLabel: {
|
||||
fontSize: 10,
|
||||
color: '#64748B',
|
||||
fontWeight: '500',
|
||||
},
|
||||
dateRow: {
|
||||
backgroundColor: '#F8FAFC',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#F1F5F9',
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 12,
|
||||
color: '#334155',
|
||||
fontWeight: '600',
|
||||
},
|
||||
dateLabel: {
|
||||
fontWeight: '400',
|
||||
color: '#64748B',
|
||||
},
|
||||
footer: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#F1F5F9',
|
||||
paddingTop: 12,
|
||||
},
|
||||
footerTop: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
footerLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
color: '#94A3B8',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
endedText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
color: '#CBD5E1',
|
||||
},
|
||||
actionButton: {
|
||||
width: '100%',
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
}),
|
||||
},
|
||||
disabledButton: {
|
||||
backgroundColor: '#E2E8F0',
|
||||
},
|
||||
addText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||
import { ChevronDown, Trash2, Info } from 'lucide-react-native';
|
||||
import { InsideHeader } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||
|
||||
const PurchaseList = () => {
|
||||
const Navigation: any = useNavigation()
|
||||
const Dispatch = useDispatch()
|
||||
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
|
||||
const toggleExpand = (index: number) => {
|
||||
setExpandedIndex(prev => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.screen}>
|
||||
<InsideHeader title="Purchase" showArrow />
|
||||
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.heading}>Purchase List</Text>
|
||||
<Info size={20} color="#0F172A" />
|
||||
</View>
|
||||
|
||||
{/* Cards */}
|
||||
{SelectedPurchaseList.map((item: any, index: any) => {
|
||||
const expanded = expandedIndex === index;
|
||||
return (
|
||||
<Animated.View
|
||||
key={item.auctionId}
|
||||
layout={Layout.springify()}
|
||||
style={styles.card}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => toggleExpand(index)}
|
||||
>
|
||||
<CardHeader
|
||||
title={item.auctionOfWork}
|
||||
expanded={expanded}
|
||||
/>
|
||||
|
||||
<Text style={styles.subTitle}>
|
||||
Auction No: {item.auctionNumber}
|
||||
</Text>
|
||||
|
||||
<Text style={styles.subTitle}>
|
||||
Status:{' '}
|
||||
<Text
|
||||
style={{
|
||||
color: item.status ? '#16A34A' : '#DC2626',
|
||||
fontWeight: '700',
|
||||
}}
|
||||
>
|
||||
{item.status ? 'Open' : 'Closed'}
|
||||
</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(220)}
|
||||
exiting={FadeOut.duration(180)}
|
||||
layout={Layout.springify()}
|
||||
style={styles.details}
|
||||
>
|
||||
{renderRow('Auction Mode', item.auctionMode)}
|
||||
{renderRow('Pattern', item.auctionPattern)}
|
||||
{renderRow('Fees', `₹ ${item.auctionFees}`)}
|
||||
{renderRow('Department', item.deptName)}
|
||||
{renderRow('Purchase End', item.purchaseEnd)}
|
||||
|
||||
<TouchableOpacity style={styles.removeButton} onPress={() => { Dispatch(togglePurchaseAuction(item)) }}>
|
||||
<Trash2 size={18} color="#fff" />
|
||||
<Text style={styles.removeText}>Remove</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* Footer */}
|
||||
<TouchableOpacity disabled={Boolean(!SelectedPurchaseList.length)} style={[styles.proceedButton, { backgroundColor: Boolean(!SelectedPurchaseList.length) ? colors.lightText : colors.primary }]} onPress={() => { Navigation.navigate(NavigationString.PurchaseAuctionList) }}>
|
||||
<Text style={styles.proceedText}>Proceed to Purchase</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseList;
|
||||
|
||||
const CardHeader = ({ title, expanded }: {
|
||||
title: string;
|
||||
expanded: boolean;
|
||||
}) => {
|
||||
const rotation = useSharedValue(expanded ? 1 : 0);
|
||||
rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 });
|
||||
const arrowStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${rotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Animated.View style={arrowStyle}>
|
||||
<ChevronDown size={22} color={colors.black} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRow = (label: string, value: string) => (
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.value}>{value}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F8FAFC',
|
||||
},
|
||||
container: {
|
||||
padding: 16,
|
||||
paddingBottom: 110,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 14,
|
||||
},
|
||||
heading: {
|
||||
fontSize: 22,
|
||||
fontWeight: '800',
|
||||
color: colors.black,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 18,
|
||||
padding: 16,
|
||||
marginBottom: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E5E7EB',
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
flex: 1,
|
||||
marginRight: 12,
|
||||
},
|
||||
subTitle: {
|
||||
fontSize: 14,
|
||||
color: '#64748B',
|
||||
marginTop: 6,
|
||||
},
|
||||
details: {
|
||||
marginTop: 16,
|
||||
paddingTop: 14,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#E5E7EB',
|
||||
},
|
||||
row: {
|
||||
marginVertical: 6,
|
||||
},
|
||||
label: {
|
||||
fontSize: 13,
|
||||
color: '#64748B',
|
||||
},
|
||||
value: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: '#0F172A',
|
||||
},
|
||||
removeButton: {
|
||||
marginTop: 18,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 14,
|
||||
paddingVertical: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
removeText: {
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
},
|
||||
proceedButton: {
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 18,
|
||||
alignItems: 'center',
|
||||
},
|
||||
proceedText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface LiveAuctionTileProps {
|
||||
data: any
|
||||
onPurchasePress?: () => void;
|
||||
onDetailsPress?: () => void;
|
||||
index: number;
|
||||
auctionDispatchDate?: string;
|
||||
auctionOfWork?: string;
|
||||
purchaseEnd?: string;
|
||||
auctionFees?: string | number;
|
||||
isPurchased?: boolean;
|
||||
auctionId?: any;
|
||||
auctionNumber?: any;
|
||||
}
|
||||
|
||||
export interface allBooleanProps {
|
||||
showDetail?: boolean
|
||||
isShowFilter?: boolean
|
||||
}
|
||||
Reference in New Issue
Block a user