import React, { useState } from 'react'; import { View, FlatList, StyleSheet, TouchableOpacity, ActivityIndicator, } from 'react-native'; import Animated, { FadeInDown, FadeIn } from 'react-native-reanimated'; import { Calendar, Hash, Wifi, WifiOff, Receipt, CheckCircle2, Gavel, } from 'lucide-react-native'; import { InsideHeader } from '../../components'; import { useDispatch, useSelector } from 'react-redux'; import { useOfflineAuctionPurchase, usePurchaseAuctionList } from '../../Services/ApiCall/Hooks'; import { showSuccessAlert } from '../../Constant/ShowDailog'; import { clearPurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction'; import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native'; import NavigationString from '../../Constant/NavigationString'; import Skeleton from '../../components/Skeleton'; import { useTheme } from '../../Hooks/useTheme'; import AppText from '../../components/AppText'; const PurchaseAuctionList = () => { const Navigation: any = useNavigation(); const Dispatch = useDispatch(); const { colors, spacing, shadows } = useTheme(); const [loading, setLoading] = useState(false); const [list, setList] = useState([]); const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction); const LoginUser = useSelector((state: any) => state.token.token); const { mutate, isPending } = useOfflineAuctionPurchase(); const { mutate: purchaseListMutation, isPending: purchaseListPending } = usePurchaseAuctionList(); const calculateTotalAmount = (item: any) => { const gstAmount = (Number(item.auctionFees) + Number(item.portalCharge)) * (Number(item.gst) / 100); return Number(item.auctionFees) + Number(item.portalCharge) + gstAmount; }; const grandTotal = list.reduce( (sum: number, item: any) => sum + calculateTotalAmount(item), 0, ); const handleOfflinePurchase = async () => { const body = { auctionDetails: list.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 { mutate(body, { onSuccess: res => { if (res.statusCode === 200) { showSuccessAlert(res.message); Dispatch(clearPurchaseAuction()); Navigation.dispatch( CommonActions.reset({ index: 0, routes: [{ name: NavigationString.Home }] }), ); } }, onError: error => console.log(error, 'purchase Error'), }); } catch (error) { console.log('purchase offline error', error); } }; const handlePurchase = (type: 'online' | 'offline') => { setLoading(true); setTimeout(() => { setLoading(false); type === 'online' ? undefined : handleOfflinePurchase(); }, 1200); }; const getPurchaseList = () => { const auctionNumbers: string[] = SelectedPurchaseList.map( (item: { auctionNumber: string }) => item.auctionNumber, ); purchaseListMutation( { auctionNumbers, contractorId: LoginUser.contractorId }, { onSuccess: res => { if (res.statusCode === 200) setList(res.data); }, onError: error => console.log(error, 'purchase list error'), }, ); }; useFocusEffect( React.useCallback(() => { getPurchaseList(); // Intentionally runs only on SelectedPurchaseList changes; other deps are stable or recreated each render. // eslint-disable-next-line react-hooks/exhaustive-deps }, [SelectedPurchaseList]), ); const styles = makeStyles(colors, spacing, shadows); /* ── Render Item ── */ const renderItem = ({ item, index }: { item: any; index: number }) => { const total = calculateTotalAmount(item); const gstAmount = (Number(item.auctionFees) + Number(item.portalCharge)) * (Number(item.gst) / 100); return ( {/* Top accent stripe */} {/* Header */} {String(index + 1).padStart(2, '0')} {item.auctionNumber} {item.auctionPattern ? ( {item.auctionPattern} ) : null} {/* Info Rows */} } label="Dispatcher No" value={item.auctionDispatcherNumber ?? '—'} colors={colors} /> } label="Dispatch Date" value={item.auctionDispatchDate ?? '—'} colors={colors} /> {/* Fee Breakdown */} FEE BREAKDOWN {/* Total Banner */} Total Payable ₹ {total.toFixed(2)} ); }; return ( {/* Grand Total Strip */} {list.length} auction{list.length !== 1 ? 's' : ''} ₹ {grandTotal.toFixed(2)} Ready to pay {/* List */} {purchaseListPending ? ( ) : ( item.auctionId?.toString()} contentContainerStyle={styles.container} showsVerticalScrollIndicator={false} /> )} {/* Bottom Buttons */} handlePurchase('online')} > Online handlePurchase('offline')} > {isPending ? ( ) : ( <> Offline )} {/* Loading Overlay */} {(loading || isPending) && ( Processing purchase… )} ); }; export default PurchaseAuctionList; /* ─── Sub-components ─── */ const InfoRow = ({ icon, label, value, colors, }: { icon: React.ReactNode; label: string; value: string; colors: any; }) => ( {icon} {label} {value} ); const infoRowStyles = StyleSheet.create({ row: { flexDirection: 'row', alignItems: 'center', paddingVertical: 5, gap: 6 }, iconWrap: { width: 22, alignItems: 'center' }, label: { flex: 1, fontWeight: '500' } as any, value: { fontWeight: '600' } as any, }); const FeeRow = ({ label, value, colors, }: { label: string; value: string; colors: any; }) => ( {label} {value} ); const feeRowStyles = StyleSheet.create({ row: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 }, }); /* ─── Styles factory ─── */ const makeStyles = (colors: any, spacing: any, shadows: any) => StyleSheet.create({ screen: { flex: 1, backgroundColor: colors.background, }, /* Grand total strip */ grandTotalStrip: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, marginHorizontal: spacing.m, marginTop: spacing.m, marginBottom: spacing.s, paddingHorizontal: spacing.m, paddingVertical: spacing.m, borderRadius: 18, ...shadows.medium, }, grandTotalRight: { flexDirection: 'row', alignItems: 'center', gap: 5, paddingHorizontal: spacing.s + 2, paddingVertical: spacing.xs + 2, borderRadius: 20, }, container: { paddingHorizontal: spacing.m, paddingTop: spacing.s, paddingBottom: 120, }, /* Card */ card: { backgroundColor: colors.surface, borderRadius: 20, marginBottom: spacing.m, overflow: 'hidden', ...shadows.medium, }, cardAccent: { height: 4, backgroundColor: colors.primary, }, cardBody: { padding: spacing.m, }, cardHeader: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: spacing.s, }, indexBadge: { width: 32, height: 32, borderRadius: 10, alignItems: 'center', justifyContent: 'center', marginTop: 2, }, patternBadge: { flexDirection: 'row', alignItems: 'center', gap: 4, alignSelf: 'flex-start', paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 8, marginTop: 4, }, divider: { height: StyleSheet.hairlineWidth, backgroundColor: colors.border, marginVertical: spacing.s, }, feeSection: { borderRadius: 12, padding: spacing.s + 2, marginBottom: spacing.s, }, feeHeader: { letterSpacing: 1.2, marginBottom: spacing.xs + 2, fontWeight: '700', } as any, totalBanner: { borderRadius: 14, paddingVertical: 12, paddingHorizontal: spacing.m, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, totalLeft: { flexDirection: 'row', alignItems: 'center', gap: spacing.s, }, /* Footer */ footer: { position: 'absolute', bottom: 0, left: 0, right: 0, flexDirection: 'row', paddingHorizontal: spacing.m, paddingTop: spacing.s + 4, paddingBottom: spacing.m + 4, backgroundColor: colors.surface, gap: spacing.s, ...shadows.strong, }, button: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 14, borderRadius: 16, gap: spacing.s, }, onlineButton: { backgroundColor: colors.surface, borderWidth: 1.5, }, offlineButton: { ...shadows.medium, }, btnText: { letterSpacing: 1.2, } as any, /* Loading overlay */ loadingOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.3)', justifyContent: 'center', alignItems: 'center', zIndex: 10, }, loadingCard: { borderRadius: 20, padding: spacing.l, alignItems: 'center', ...shadows.strong, }, });