second commit

This commit is contained in:
rohit-kumar-fullstack
2026-01-07 10:19:34 +05:30
parent 59fb7e0df4
commit 743d571519
29 changed files with 16782 additions and 619 deletions
+13962
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -19,7 +19,7 @@ export const apiCall = async <R, D = {}>(
data,
params,
// withCredentials: true,
baseURL: Variable.Main_Base,
// baseURL: Variable.Main_Base,
headers: {
Authentication: `Bearer ${token?.token}`,
'Content-Type': contentType || 'application/json',
@@ -30,7 +30,6 @@ export const apiCall = async <R, D = {}>(
} catch (error: any) {
const errorMessage = error?.response?.data?.error;
{ error?.response?.data?.error && showErrorAlert(error?.response?.data?.error || '') }
console.log(errorMessage, '---------------url error');
if (error?.response?.status === 401) {
store.dispatch(setToken({}));
+18 -6
View File
@@ -2,12 +2,15 @@ import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import colors from '../../Constant/Color';
import moment from 'moment';
import LottieView from 'lottie-react-native';
interface CountdownTimerProps {
endDate: string; // Expected format: "DD-MM-YYYY HH:mm:ss"
endDate: string;
myStyle?: any
size?: number // Expected format: "DD-MM-YYYY HH:mm:ss"
}
const CountdownTimer: React.FC<CountdownTimerProps> = React.memo(({ endDate }) => {
const CountdownTimer: React.FC<CountdownTimerProps> = React.memo(({ endDate, myStyle = {}, size = 20 }) => {
const calculateTimeLeft = useCallback(() => {
if (!endDate) return null;
@@ -43,8 +46,14 @@ const CountdownTimer: React.FC<CountdownTimerProps> = React.memo(({ endDate }) =
return (
<View style={styles.container}>
<Text style={styles.timerText}>
{timeLeft.days}d {timeLeft.hours}h {timeLeft.minutes}m {timeLeft.seconds}s
<LottieView
source={require('../../lottie/clock.json')}
autoPlay
loop={true}
style={{ width: size, height: size, }}
/>
<Text style={[styles.timerText, myStyle]}>
{timeLeft.days}D {timeLeft.hours}H {timeLeft.minutes}M {timeLeft.seconds}S
</Text>
</View>
);
@@ -58,15 +67,18 @@ const styles = StyleSheet.create({
paddingVertical: 5,
paddingHorizontal: 10,
borderWidth: 1,
borderColor: colors.primary,
borderColor: colors.black,
borderRadius: 25,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
timerText: {
fontSize: 14,
fontWeight: 'bold',
color: colors.primary,
color: colors.black,
paddingLeft: 5
},
expiredText: {
fontSize: 12,
+1 -1
View File
@@ -50,7 +50,7 @@ const InsideHeader: React.FC<HeaderProps> = ({ title, showArrow = false }) => {
<Text style={styles.title}>{title}</Text>
</View>
<TouchableOpacity onPress={() => { navigation.navigate(NavigationString.Emd) }}>
<TouchableOpacity onPress={() => { navigation.navigate(NavigationString.Home) }}>
<Image
source={ImagePath.Icon.Logo2}
style={styles.logo}
+122
View File
@@ -0,0 +1,122 @@
import React from 'react';
import {
Modal,
View,
Text,
TouchableOpacity,
StyleSheet,
Pressable,
} from 'react-native';
type ConfirmDialogProps = {
visible: boolean;
title?: string;
message: string;
confirmText?: string;
cancelText?: string;
onConfirm: () => void;
onClose: () => void;
};
const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
visible,
title = 'Confirm',
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
onConfirm,
onClose,
}) => {
return (
<Modal
visible={visible}
transparent
animationType="fade"
statusBarTranslucent
onRequestClose={onClose}
>
{/* Overlay (click to close) */}
<Pressable style={styles.overlay} onPress={onClose}>
{/* Stop propagation */}
<Pressable style={styles.container}>
{/* Title */}
<Text style={styles.title}>{title}</Text>
{/* Message */}
<Text style={styles.message}>{message}</Text>
{/* Actions */}
<View style={styles.actions}>
<TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
<Text style={styles.cancelText}>{cancelText}</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.confirmBtn}
onPress={() => {
onConfirm();
onClose();
}}
>
<Text style={styles.confirmText}>{confirmText}</Text>
</TouchableOpacity>
</View>
</Pressable>
</Pressable>
</Modal>
);
};
export default ConfirmDialog;
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
},
container: {
width: '85%',
backgroundColor: '#fff',
borderRadius: 12,
padding: 20,
elevation: 5,
},
title: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
textAlign: 'center',
},
message: {
fontSize: 15,
color: '#444',
textAlign: 'center',
marginBottom: 20,
},
actions: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
cancelBtn: {
paddingVertical: 10,
paddingHorizontal: 16,
marginRight: 10,
},
cancelText: {
fontSize: 14,
color: '#666',
},
confirmBtn: {
backgroundColor: '#1f4b6dff',
paddingVertical: 10,
paddingHorizontal: 18,
borderRadius: 6,
},
confirmText: {
color: '#fff',
fontSize: 14,
fontWeight: '500',
},
});
+2 -1
View File
@@ -8,6 +8,7 @@ const NavigationString = {
PurchaseList: 'PurchaseList',
Emd: 'Emd',
StartBidding: 'StartBidding',
PurchaseAuctionList:'PurchaseAuctionList'
PurchaseAuctionList:'PurchaseAuctionList',
StartReverseBidding:'StartReverseBidding'
}
export default NavigationString
+1
View File
@@ -36,6 +36,7 @@ export default function MainStack() {
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} />
</Stack.Navigator>
);
+3 -15
View File
@@ -1,17 +1,7 @@
import React, { useState, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
Dimensions,
} from 'react-native';
import { View, Text, StyleSheet, TouchableOpacity, Animated, Dimensions } from 'react-native';
import { InsideHeader } from '../../Component/Index';
import {
useFetchLiveAuction,
useFetchPurchaseAuction,
} from '../../Services/BBPS/Hooks';
import { useFetchLiveAuction, useFetchPurchaseAuction } from '../../Services/BBPS/Hooks';
import colors from '../../Constant/Color';
import LiveAuction from './LiveAuction';
import PurchaseAuction from './PurchaseAuction';
@@ -20,7 +10,6 @@ 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;
@@ -30,7 +19,6 @@ const Auction = () => {
const { data: purchaseAuctionData = {} } = useFetchPurchaseAuction();
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
const liveCount = Array.isArray(liveAuctionData?.result)
? liveAuctionData?.pages[0]?.totalCount
: 0;
@@ -101,7 +89,7 @@ const Auction = () => {
</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 }}>
{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) }}>
+39 -2
View File
@@ -9,6 +9,9 @@ import { Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useDispatch, useSelector } from 'react-redux';
import { selectPurchaseAuctionSlice, togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
import { apiCall } from '../../Axios/Axios';
import { GET_MY_AUCTION, LIVE_AUCTION } from '../../Services/BBPS/ApiUrls';
import moment from 'moment';
const LiveAuction = () => {
const navigation: any = useNavigation();
@@ -16,6 +19,7 @@ const LiveAuction = () => {
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseAuction);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveAuction();
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
const [allBoolean, setAllBoolean] = useState({ liveAuctionData: [] })
useEffect(() => {
if (isRefetching) {
@@ -64,6 +68,39 @@ const LiveAuction = () => {
[SelectedPurchaseList]
);
const getMyAuction = async () => {
try {
const res = await apiCall<any>('get', `${GET_MY_AUCTION}`,);
if (res?.statusCode === 200) {
const purchasedIds = new Set(
res.data.map((item: any) => item.auctionId),
);
const filteredLive = data?.result.map((item: any) => ({
...item,
active: !purchasedIds.has(item.auctionId), // false if match, true otherwise
}));
setAllBoolean(prev => ({
...prev,
liveAuctionData: filteredLive
}));
}
} catch (error) {
console.error('getMyAuction error', error);
} finally {
setAllBoolean(prev => ({ ...prev, isLoading: false }));
}
};
useEffect(() => {
if (data?.result?.length > 0) {
getMyAuction()
}
}, [data])
if (isLoading) {
return <Skelton />;
}
@@ -110,8 +147,8 @@ const LiveAuction = () => {
)}
<FlatList
data={data?.result ?? []}
keyExtractor={(item) => String(item.auctionId)}
data={allBoolean.liveAuctionData ?? []}
keyExtractor={(item: any) => String(item.auctionNumber)}
renderItem={renderItem}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
+82 -78
View File
@@ -22,89 +22,93 @@ const LiveAuctionTile: React.FC<LiveAuctionTileProps> = ({
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
{
!showDetail ?
<>
<View
style={[
styles.mainButtonText,
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
styles.statusTag,
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
]}
/>
<View
style={styles.container}
>
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
</Text>
{!purchaseEnded && (
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
)}
</TouchableOpacity>
</View>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity style={styles.badge} onPress={() => setShowDetail(true)}>
<Tag size={14} color={colors.primary} />
<Text style={styles.badgeText}>#{auctionNumber}</Text>
</TouchableOpacity>
<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
: !data.active ? { backgroundColor: 'lightgray' } : isPurchased
? styles.btnPurchased
: styles.btnActive,
]}
onPress={onPurchasePress}
disabled={purchaseEnded ? true : data.active ? false : true}
>
<Text
style={[
styles.mainButtonText,
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
]}
>
{purchaseEnded ? 'Expired' : !data.active ? 'Purchased' : isPurchased ? 'Remove' : 'Add to Cart'}
</Text>
{!purchaseEnded && (
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
)}
</TouchableOpacity>
</View>
{/* Auction Detail Modal */}
</View>
</>
: <AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
}
{/* Auction Detail Modal */}
<AuctionDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
</TouchableOpacity>
</View>
);
};
+28 -46
View File
@@ -1,30 +1,8 @@
import React, { memo, useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
SafeAreaView,
Platform,
StatusBar,
} from 'react-native';
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 { Briefcase, Calendar, FileText, Download, Eye, Layers, ArrowLeft, Clock } from 'lucide-react-native';
import colors from '../../../Constant/Color';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import AuctionItemDetail from './AuctionItemDetail';
@@ -69,13 +47,15 @@ const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
<Modal
isVisible={visible}
style={styles.fullModal}
animationIn="slideInRight"
animationOut="slideOutRight"
useNativeDriver
hideModalContentWhileAnimating
animationIn="fadeIn"
animationOut="fadeOut"
animationInTiming={360}
animationOutTiming={280}
statusBarTranslucent
backdropColor="transparent"
backdropOpacity={0}
>
<SafeAreaView style={styles.safeArea}>
<View style={styles.safeArea}>
<StatusBar barStyle="dark-content" />
{/* Full Header */}
@@ -191,23 +171,25 @@ const AuctionFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
</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>
<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} />
</View>
</Modal>
);
};
@@ -79,13 +79,16 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data = ITEM_DUMM
<Modal
isVisible={visible}
style={styles.fullModal}
animationIn="slideInUp"
animationOut="slideOutDown"
backdropOpacity={1}
useNativeDriver
animationIn="fadeIn"
animationOut="fadeOut"
animationInTiming={360}
animationOutTiming={280}
statusBarTranslucent
backdropColor="transparent"
backdropOpacity={0}
useNativeDriver
>
<SafeAreaView style={styles.safeArea}>
<View style={styles.safeArea}>
{/* Navbar */}
<View style={[styles.header, { paddingTop: inset.top }]}>
@@ -171,7 +174,7 @@ const AuctionItemDetail: React.FC<Props> = ({ visible, onClose, data = ITEM_DUMM
</Section>
</ScrollView>
</SafeAreaView>
</View>
</Modal>
);
};
+2 -3
View File
@@ -11,6 +11,7 @@ import { SearchInput } from '../../Component/Serach/SeacrhInput';
import { Skelton } from '../../Component/Index';
import { allBooleanProps } from './types/types';
import FilterModal from './Modal/FilterModal';
import NavigationString from '../../Constant/NavigationString';
const PurchaseAuction = () => {
@@ -38,7 +39,6 @@ const PurchaseAuction = () => {
return now.isAfter(end);
}
console.log(allBoolean);
return (
<View style={{ flex: 1 }}>
@@ -52,11 +52,10 @@ const PurchaseAuction = () => {
<PurchaseAuctionTile
item={item}
onPressDetails={() => {
navigation.navigate('AuctionDetail', { item, onAddToPurchase: () => { }, routeFrom: 'PurchasedAuction' });
}}
onStartBidding={() => {
Dispatch(setAuction(item))
// navigation.navigate(NavigationString, { data: item })
navigation.navigate(NavigationString.Emd)
}}
buttonEnable={isAuctionActive(item)}
isAuctionEnd={isAuctionEnd(item)}
+24 -12
View File
@@ -3,8 +3,12 @@ import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react
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 { useDispatch, useSelector } from 'react-redux';
import { useOfflineAuctionPurchase } from '../../Services/BBPS/Hooks';
import { showSuccessAlert } from '../../Constant/ShowDailog';
import { clearPurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
import { CommonActions, useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
const dummyAuctionList = [
{
@@ -46,11 +50,11 @@ const dummyAuctionList = [
];
const PurchaseAuctionList = () => {
const Navigation = useNavigation()
const Dispatch = useDispatch()
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 =
@@ -80,10 +84,18 @@ const PurchaseAuctionList = () => {
};
try {
// console.log(body);
mutate(body, {
onSuccess: (res) => {
console.log(res, '---------------------purchsase response');
if (res.statusCode == 200) {
showSuccessAlert(res.message)
Dispatch(clearPurchaseAuction())
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Home }],
})
);
}
},
onError: (error) => {
console.log(error, "================purchse Error");
@@ -167,16 +179,16 @@ const PurchaseAuctionList = () => {
style={[styles.button, { marginLeft: 8 }]}
onPress={() => handlePurchase('offline')}
>
<Text style={styles.buttonText}>Purchase Offline</Text>
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
{
isPending ? <Loader size='small' color={colors.white} /> : <>
<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>
);
};
+220 -230
View File
@@ -1,28 +1,13 @@
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Platform,
} from 'react-native';
import Animated, {
FadeInUp,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
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';
import AuctionDetail from './Modal/AuctionDetail';
const PurchaseAuctionTile = ({
item,
onPressDetails,
onStartBidding,
buttonEnable,
isAuctionEnd,
}: any) => {
const PurchaseAuctionTile = ({ item, onPressDetails, onStartBidding, buttonEnable, isAuctionEnd }: any) => {
const [showDetail, setShowDetail] = React.useState(false);
const scale = useSharedValue(1);
const elevation = useSharedValue(8);
@@ -43,232 +28,237 @@ const PurchaseAuctionTile = ({
elevation.value = withTiming(8, { duration: 160 });
};
const statusColor = isAuctionEnd
? '#EF4444'
: buttonEnable
? colors.primary
: '#94A3B8';
const statusColor = isAuctionEnd ? '#EF4444' : buttonEnable ? colors.primary : '#94A3B8';
const lightStatusBg = isAuctionEnd
? '#FEF2F2'
: buttonEnable
? '#EFF6FF'
: '#F8FAFC';
const lightStatusBg = isAuctionEnd ? '#FEF2F2' : buttonEnable ? '#EFF6FF' : '#F8FAFC';
console.log(item?.keyDates?.auctionBidding?.endDateTime, 'kk');
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={{ flex: 1 }}>
{!showDetail ? <Animated.View
entering={FadeInUp.duration(400)}
style={[styles.shadowContainer, animatedStyle]}
>
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
<TouchableOpacity
activeOpacity={1}
onPress={() => setShowDetail(true)}
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'}
<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>
<Text style={styles.title}>{item.auctionNumber}</Text>
</View>
<View style={styles.amountContainer}>
<Text style={styles.amount}>
₹{item.auctionFees?.toLocaleString()}
{/* Date */}
<View style={styles.dateRow}>
<Text style={styles.dateText}>
<Text style={styles.dateLabel}>Bidding Start: </Text>
{moment(
item?.keyDates?.auctionBidding?.startDateTime?.replace(' kk', ''),
'DD-MM-YYYY HH:mm:ss'
).format('DD MMM YYYY, hh:mm A')}
</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 style={styles.dateText}>
<Text style={styles.dateLabel}>Bidding Ends: </Text>
{moment(
item?.keyDates?.auctionBidding?.endDateTime?.replace(' kk', ''),
'DD-MM-YYYY HH:mm:ss'
).format('DD MMM YYYY, hh:mm A')}
</Text>
</TouchableOpacity>
</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
? 'Pending Bid'
: 'Proceed to Start Bidding'}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</TouchableOpacity>
</Animated.View>
</TouchableOpacity>
</Animated.View> :
<AuctionDetail visible={showDetail} data={item} onClose={() => setShowDetail(false)} />
}
</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,
},
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,218 @@
import React, { memo, useCallback, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
import colors from '../../../Constant/Color';
import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
import MainStyle from '../../../Styles/MainStyle';
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction }: any) => {
const [showDetail, setShowDetail] = React.useState(false);
const isMin = bidValue <= item.bidAmount;
// 🔒 Persist expand state safely
const expandedRef = useRef(false);
const animation = useSharedValue(0);
const toggleExpand = useCallback(() => {
expandedRef.current = !expandedRef.current;
animation.value = withSpring(expandedRef.current ? 1 : 0, {
damping: 15,
stiffness: 140,
});
}, []);
const bodyStyle = useAnimatedStyle(() => ({
height: interpolate(animation.value, [0, 1], [0, 190]),
opacity: interpolate(animation.value, [0, 0.5, 1], [0, 0, 1]),
marginTop: interpolate(animation.value, [0, 1], [0, 15]),
}));
const arrowStyle = useAnimatedStyle(() => ({
transform: [
{
rotate: `${interpolate(animation.value, [0, 1], [0, 180])}deg`,
},
],
}));
return (
<View>
{
!showDetail ? <Animated.View
layout={Layout.springify()}
entering={FadeInUp.delay(100)}
style={[styles.card, styles.leadingCard, { borderColor: item.me ? '#22C55E' : 'red', backgroundColor: item.me ? "#F0FDF4" : '#fdf0f0ff' }]}
>
{/* HEADER */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => { setShowDetail(true) }}
style={styles.cardHeader}
>
<View style={{ flex: 1 }}>
<Text style={styles.productName}>{item.productName}</Text>
{/* auctionStartValueFigure */}
<Text style={styles.itemIdText}>
Initial Amt : {item?.auctionStartValueFigure}
</Text>
</View>
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
<Text style={[styles.badgeText, item.me ? styles.meText : styles.liveText]}>
{item.me ? 'LEADING' : 'LIVE'}
</Text>
</View>
<Animated.Text style={[styles.arrow, arrowStyle]}>
▼
</Animated.Text>
</View>
<Text style={styles.itemIdText}>
{SelectedAuction?.auctionNumber}
</Text>
</TouchableOpacity>
</TouchableOpacity>
{/* PRICE INFO */}
<View style={styles.priceRow}>
<View>
<Text style={styles.label}>Current Bid</Text>
<Text style={styles.mainPrice}>₹{item?.bidAmount}</Text>
</View>
<View style={{ alignItems: 'flex-end' }}>
<Text style={styles.label}>Increment</Text>
<Text style={styles.incrementText}>
+₹{item.bidVariationValue}
</Text>
</View>
</View>
{/* COLLAPSIBLE BODY */}
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
<View style={styles.divider} />
<Text style={styles.bidLabel}>Set Your Bid</Text>
<View style={styles.stepperContainer}>
<TouchableOpacity
onPress={onDecrease}
disabled={isMin}
style={[styles.stepBtn, isMin && styles.disabledBtn]}
>
<Text style={styles.stepText}>−</Text>
</TouchableOpacity>
<View style={styles.inputBox}>
<Text style={styles.currency}>₹</Text>
<Text style={styles.bidValueText}>{bidValue}</Text>
</View>
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
<Text style={styles.stepText}>+</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.submitBtn}
onPress={onPlaceBid}
// activeOpacity={0.85}
>
<Text style={styles.submitBtnText}>Confirm Bid</Text>
</TouchableOpacity>
</Animated.View>
</Animated.View>
:
<AuctionItemDetail data={item} onClose={() => { setShowDetail(false) }} visible={showDetail} />
}
</View>
)
};
export default memo(BiddingCard);
const styles = StyleSheet.create({
card: {
backgroundColor: '#FFF',
borderRadius: 24,
padding: 20,
marginBottom: 16,
borderWidth: 1,
borderColor: '#E2E8F0',
overflow: 'hidden',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOpacity: 0.05,
shadowRadius: 10,
},
android: { elevation: 2 },
}),
},
leadingCard: {
borderColor: '#22C55E',
borderWidth: 2,
backgroundColor: '#F0FDF4',
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
},
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
liveBadge: { backgroundColor: '#FEE2E2' },
meBadge: { backgroundColor: '#BBF7D0' },
badgeText: { fontSize: 10, fontWeight: '900' },
liveText: { color: '#EF4444' },
meText: { color: '#15803D' },
arrow: { fontSize: 10, color: '#94A3B8' },
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
label: {
fontSize: 11,
color: '#94A3B8',
textTransform: 'uppercase',
fontWeight: '600',
},
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
collapsibleContainer: { overflow: 'hidden' },
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
stepBtn: {
width: 45,
height: 45,
backgroundColor: colors.primary,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
},
disabledBtn: { backgroundColor: '#CBD5E1' },
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
inputBox: {
flex: 1,
height: 45,
backgroundColor: '#F1F5F9',
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
submitBtn: {
height: 50,
backgroundColor: colors.primary,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
},
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
});
@@ -0,0 +1,370 @@
import React, { memo, useCallback, useRef, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput } from 'react-native';
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
import colors from '../../../Constant/Color';
import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
import MainStyle from '../../../Styles/MainStyle';
import axios, { all } from 'axios';
import Variables from '../../../Constant/Variable';
import { useSelector } from 'react-redux';
import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
import { Loader } from '../../../Component/Index';
const ReverseBiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => {
const Token = useSelector((state: any) => state.token.token)
const [allState, setAllState] = useState({ isLoading: false })
const [showDetail, setShowDetail] = useState(false);
const [value, setValue] = useState(`${item.bidAmount}`);
const canDecrease = bidValue > item.emdAmount;
const canIncrease = bidValue < item.bidAmount;
// Animation Part Rohit Start
const expandedRef = useRef(false);
const animation = useSharedValue(0);
const toggleExpand = useCallback(() => {
expandedRef.current = !expandedRef.current;
animation.value = withSpring(expandedRef.current ? 1 : 0, {
damping: 15,
stiffness: 140,
});
}, []);
const bodyStyle = useAnimatedStyle(() => ({
height: interpolate(animation.value, [0, 1], [0, 190]),
opacity: interpolate(animation.value, [0, 0.5, 1], [0, 0, 1]),
marginTop: interpolate(animation.value, [0, 1], [0, 15]),
}));
const bodyStyle2 = useAnimatedStyle(() => ({
height: interpolate(animation.value, [0, 1], [0, 100]),
opacity: interpolate(animation.value, [0, 0.5, 1], [0, 0, 1]),
marginTop: interpolate(animation.value, [0, 1], [0, 15]),
}));
const arrowStyle = useAnimatedStyle(() => ({
transform: [
{
rotate: `${interpolate(animation.value, [0, 1], [0, 180])}deg`,
},
],
}));
// Animation Part Rohit End
const MAX_VALUE = Number(item.auctionStartValue);
const handleChange = (text: string) => {
let cleaned = text.replace(/[^0-9.]/g, '');
if ((cleaned.match(/\./g) || []).length > 1) return;
if (cleaned === '') {
setValue('');
return;
}
const numericValue = Number(cleaned);
if (numericValue < 0) return;
if (numericValue > MAX_VALUE) return;
setValue(cleaned);
};
const oneTimePlaceBid = async () => {
try {
setAllState((prev: any) => ({ ...prev, isLoading: true }))
const payload = {
itemId: item.id,
bidAmount: Number(value),
auctionId: SelectedAuction.auctionId,
contractorId: Token.contractorId,
fullName: Token.fullName,
};
const res = await axios.post(
`${Variables.socketUrl}bidding/placeBidForAuction`,
[payload],
{ headers: { Authentication: `Bearer ${Token.token}` } }
);
if (res.data.statusCode === 200) {
showSuccessAlert(res.data.message);
}
setAllBoolean((prev: any) => ({ ...prev, reload: !prev.reload }))
} catch (error) {
if (axios.isAxiosError(error)) {
showErrorAlert(error.response?.data.message)
} else {
console.log('UNKNOWN ERROR:', error);
}
} finally {
setAllState((prev: any) => ({ ...prev, isLoading: false }))
}
}
return (
<View>
{!showDetail ? (
<Animated.View
layout={Layout.springify()}
entering={FadeInUp.delay(100)}
style={[
styles.card,
{
borderColor: item.me ? '#22C55E' : '#EF4444',
backgroundColor: item.me ? '#F0FDF4' : '#FDF0F0',
},
]}
>
{/* HEADER */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => setShowDetail(true)}
style={styles.cardHeader}
>
<View style={{ flex: 1 }}>
<Text style={styles.productName}>{item.productName}</Text>
<Text style={styles.itemIdText}>
Initial Amt : ₹{item.auctionStartValue}
</Text>
</View>
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
<View
style={[
styles.badge,
item.me ? styles.meBadge : styles.liveBadge,
]}
>
<Text
style={[
styles.badgeText,
item.me ? styles.meText : styles.liveText,
]}
>
{item.me ? 'LEADING' : 'LIVE'}
</Text>
</View>
<Animated.Text style={[styles.arrow, arrowStyle]}>
▼
</Animated.Text>
</View>
<Text style={styles.itemIdText}>
{SelectedAuction?.auctionNumber}
</Text>
</TouchableOpacity>
</TouchableOpacity>
{/* PRICE INFO */}
<View style={styles.priceRow}>
<View>
<Text style={styles.label}>Current Bid</Text>
<Text style={styles.mainPrice}>₹{item.bidAmount || item.auctionStartValue}</Text>
</View>
<View style={{ alignItems: 'flex-end' }}>
<Text style={styles.label}>Decrement</Text>
<Text style={styles.incrementText}>
-₹{item.emdAmount}
</Text>
</View>
</View>
{/* COLLAPSIBLE BODY */}
{/* <Animated.View style={[styles.collapsibleContainer, (allBoolean.oneTimeBid && allBoolean.rebid) ? bodyStyle : (allBoolean.oneTimeBid && !item.me) ? bodyStyle : bodyStyle2]}> */}
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
<View style={styles.divider} />
<Text style={styles.bidLabel}>Set Your Bid</Text>
<View style={styles.stepperContainer}>
{/* DECREASE */}
{!allBoolean.oneTimeBid && <TouchableOpacity
onPress={onDecrease}
disabled={!canDecrease}
style={[
styles.stepBtn,
!canDecrease && styles.disabledBtn,
]}
>
<Text style={styles.stepText}>−</Text>
</TouchableOpacity>}
{/* VALUE */}
{!allBoolean.oneTimeBid && <View style={styles.inputBox}>
<Text style={styles.currency}>₹</Text>
<Text style={styles.bidValueText}>{bidValue}</Text>
</View>}
{
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
<Text style={styles.currency2}>₹</Text>
<TextInput
value={value}
onChangeText={handleChange}
keyboardType="decimal-pad"
placeholder="0.00"
style={styles.input}
placeholderTextColor="#94A3B8"
/>
</View>
}
{/* INCREASE */}
{!allBoolean.oneTimeBid && <TouchableOpacity
onPress={onIncrease}
disabled={!canIncrease}
style={[
styles.stepBtn,
!canIncrease && styles.disabledBtn,
]}
>
<Text style={styles.stepText}>+</Text>
</TouchableOpacity>}
</View>
<TouchableOpacity
style={[styles.submitBtn]}
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
>
{
allState.isLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitBtnText}>Confirm Bid</Text>
}
</TouchableOpacity>
{/* {(allBoolean.oneTimeBid && allBoolean.rebid) ?
<TouchableOpacity
style={[styles.submitBtn]}
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
>
{
allState.isLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitBtnText}>Confirm Bid</Text>
}
</TouchableOpacity> :
(allBoolean.oneTimeBid && !item.me) ? <TouchableOpacity
style={[styles.submitBtn]}
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
>
{
allState.isLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitBtnText}>Confirm Bid</Text>
}
</TouchableOpacity> : null
} */}
</Animated.View>
</Animated.View>
) : (
<AuctionItemDetail
data={item}
onClose={() => setShowDetail(false)}
visible={showDetail}
/>
)}
</View>
);
};
export default ReverseBiddingCard;
const styles = StyleSheet.create({
inputBox2: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#F1F5F9',
borderRadius: 12,
height: 45,
paddingHorizontal: 12,
width: '100%'
},
currency2: {
fontSize: 16,
fontWeight: 'bold',
color: '#64748B',
marginRight: 4,
},
input: {
flex: 1,
fontSize: 18,
fontWeight: '700',
color: '#0F172A',
width: '100%'
},
card: {
backgroundColor: '#FFF',
borderRadius: 24,
padding: 20,
marginBottom: 16,
borderWidth: 1,
borderColor: '#E2E8F0',
overflow: 'hidden',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOpacity: 0.05,
shadowRadius: 10,
},
android: { elevation: 2 },
}),
},
leadingCard: {
borderColor: '#22C55E',
borderWidth: 2,
backgroundColor: '#F0FDF4',
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
},
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
liveBadge: { backgroundColor: '#FEE2E2' },
meBadge: { backgroundColor: '#BBF7D0' },
badgeText: { fontSize: 10, fontWeight: '900' },
liveText: { color: '#EF4444' },
meText: { color: '#15803D' },
arrow: { fontSize: 10, color: '#94A3B8' },
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
label: {
fontSize: 11,
color: '#94A3B8',
textTransform: 'uppercase',
fontWeight: '600',
},
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
collapsibleContainer: { overflow: 'hidden' },
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
stepBtn: {
width: 45,
height: 45,
backgroundColor: colors.primary,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
},
disabledBtn: { backgroundColor: '#CBD5E1' },
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
inputBox: {
flex: 1,
height: 45,
backgroundColor: '#F1F5F9',
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
submitBtn: {
height: 50,
backgroundColor: colors.primary,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
},
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
});
@@ -0,0 +1,416 @@
import React, { useState } from 'react'
import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView } from 'react-native'
import { Formik } from 'formik'
import * as Yup from 'yup'
import { pick } from '@react-native-documents/picker'
import { useSelector } from 'react-redux'
import { apiCall } from '../../../Axios/Axios'
import { TECHNICAL_DOCUMENT_SUBMIT, TECHNICAL_VALUE_SUBMIT } from '../../../Services/BBPS/ApiUrls'
import { Props } from '../Type/BidType'
import colors from '../../../Constant/Color'
import { Loader } from '../../../Component/Index'
import { useNavigation } from '@react-navigation/native'
import NavigationString from '../../../Constant/NavigationString'
const TemplateFormModal: React.FC<Props> = ({ visible, template, setAllBoolean }) => {
if (!template) return null
const SelectedAuction = useSelector((state: any) => state.auction.auction)
const Navigation: any = useNavigation()
const [loading, setLoading] = useState(false)
const initialValues = template.labels.reduce((acc: any, label: any) => {
acc[label.labelName] = ''
return acc
}, {})
const validationSchema = Yup.object(
template.labels.reduce((acc: any, label: any) => {
if (label.mandatory) {
acc[label.labelName] =
label.labelType === '3'
? Yup.mixed().required(`${label.labelName} is required`)
: Yup.string().required(`${label.labelName} is required`)
}
return acc
}, {})
)
const pickFile = async (fieldName: string, setFieldValue: any) => {
try {
const result = await pick({
type: ['application/pdf'],
allowMultiSelection: false,
})
const file = result[0]
setFieldValue(fieldName, {
name: file.name ?? 'document.pdf',
uri: file.uri,
type: 'application/pdf',
size: file.size,
})
} catch (e) {
console.log(e)
}
}
const renderInput = (
label: any,
value: any,
setFieldValue: any,
error?: string,
touched?: boolean
) => {
const showError = touched && error
if (label.labelType === '3') {
return (
<View key={label.labelName} style={styles.field}>
<Text style={styles.label}>
{label.labelName}
{label.mandatory && <Text style={styles.required}> *</Text>}
</Text>
<TouchableOpacity
style={[
styles.filePicker,
showError && styles.errorBorder,
]}
onPress={() =>
pickFile(label.labelName, setFieldValue)
}
>
<Text
style={[
styles.fileText,
value?.name && styles.fileSelected,
]}
>
{value?.name || 'Upload PDF'}
</Text>
</TouchableOpacity>
{showError && (
<Text style={styles.errorText}>{error}</Text>
)}
</View>
)
}
return (
<View key={label.labelName} style={styles.field}>
<Text style={styles.label}>
{label.labelName}
{label.mandatory && <Text style={styles.required}> *</Text>}
</Text>
<TextInput
style={[
styles.input,
showError && styles.errorBorder,
]}
placeholder={`Enter ${label.labelName}`}
placeholderTextColor="#9CA3AF"
value={value}
onChangeText={(text) =>
setFieldValue(label.labelName, text)
}
keyboardType={
label.labelType === '1' || label.labelType === '4'
? 'numeric'
: 'default'
}
/>
{showError && (
<Text style={styles.errorText}>{error}</Text>
)}
</View>
)
}
const onClose = () => {
setAllBoolean((prev: any) => ({
...prev,
technicalDocModalVisible: false,
}))
}
const onSubmit = async (payload: any) => {
try {
const jsonBody = {
auctiontId: SelectedAuction.auctionId,
technicalDocumentTemplateId: payload.templateNameId,
labelValues: [] as any[],
}
const fileFormData = new FormData()
let hasFile = false
Object.entries(payload.values).forEach(
([labelName, labelValue]: any) => {
if (typeof labelValue === 'object' && labelValue?.uri) {
hasFile = true
fileFormData.append(labelName, {
uri: labelValue.uri,
name: labelValue.name,
type: labelValue.type,
} as any)
} else {
jsonBody.labelValues.push({
labelName,
labelValue: String(labelValue),
})
}
}
)
const rs1: any = await apiCall('post', TECHNICAL_VALUE_SUBMIT, jsonBody)
if (hasFile) {
fileFormData.append(
'auctiontId',
String(SelectedAuction.auctionId)
)
fileFormData.append(
'technicalDocumentTemplateId',
String(payload.templateNameId)
)
const res2: any = await apiCall(
'post',
TECHNICAL_DOCUMENT_SUBMIT,
fileFormData,
{},
'multipart/form-data'
)
console.log(res2, '-------------res2');
if (rs1.statusCode == 200 && res2.statusCode == 200) {
Navigation.navigate(NavigationString.StartBidding)
setAllBoolean((prev: any) => ({ ...prev, technicalDocModalVisible: false }))
}
} else {
if (rs1.statusCode == 200) {
Navigation.navigate(NavigationString.StartBidding)
setAllBoolean((prev: any) => ({ ...prev, technicalDocModalVisible: false }))
}
}
} catch (e) {
console.log(e)
} finally {
setLoading(false)
}
}
return (
<Modal visible={visible} animationType="fade" transparent>
<View style={styles.screen}>
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.headerTitle}>Technical Documents</Text>
<Text style={styles.headerSubtitle}>
Fill the form, Click save when you're done.
</Text>
</View>
<TouchableOpacity onPress={onClose} style={styles.closeBtn}>
<Text style={styles.closeText}>✕</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.card}>
<Text style={styles.templateHeading}>Template</Text>
<View style={styles.templateBox}>
<Text style={styles.templateName}>
{template.templateName}
</Text>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => {
setLoading(true)
onSubmit({
templateNameId: template.templateNameId,
auctionIds: template.auctionIds,
values,
})
}}
>
{({ handleSubmit, values, errors, touched, setFieldValue }) => (
<>
{template.labels.map((label: any) =>
renderInput(
label,
values[label.labelName],
setFieldValue,
// errors[label.labelName],
// touched[label.labelName]
)
)}
<View style={styles.submitRow}>
<TouchableOpacity
style={styles.submitBtn}
onPress={() => handleSubmit()}
disabled={loading}
>
{loading ? (
<Loader
size="small"
color={colors.white}
/>
) : (
<Text style={styles.submitText}>
Submit
</Text>
)}
</TouchableOpacity>
</View>
</>
)}
</Formik>
</View>
</View>
</ScrollView>
</View>
</Modal>
)
}
export default TemplateFormModal
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#F9FAFB',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 14,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderColor: '#E5E7EB',
},
headerLeft: {
flex: 1,
},
headerTitle: {
fontSize: 16,
fontWeight: '700',
color: '#111827',
},
headerSubtitle: {
fontSize: 12,
color: '#6B7280',
marginTop: 2,
},
closeBtn: {
padding: 6,
},
closeText: {
fontSize: 18,
color: '#6B7280',
},
content: {
paddingHorizontal: 16,
},
card: {
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
paddingVertical: 20
},
templateHeading: {
fontSize: 18,
fontWeight: '700',
textAlign: 'center',
marginBottom: 16,
color: '#111827',
},
templateBox: {
borderWidth: 1,
borderColor: '#E5E7EB',
borderRadius: 10,
padding: 16,
},
templateName: {
fontSize: 14,
fontWeight: '700',
color: '#111827',
marginBottom: 12,
},
field: {
marginBottom: 14,
},
label: {
fontSize: 13,
fontWeight: '600',
color: '#374151',
marginBottom: 6,
},
required: {
color: '#EF4444',
},
input: {
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E5E7EB',
borderRadius: 8,
padding: 12,
fontSize: 14,
color: '#111827',
},
filePicker: {
borderWidth: 1,
borderColor: '#E5E7EB',
borderRadius: 8,
padding: 12,
},
fileText: {
color: '#6B7280',
fontSize: 14,
},
fileSelected: {
color: '#111827',
fontWeight: '600',
},
errorText: {
color: '#EF4444',
fontSize: 12,
marginTop: 4,
},
errorBorder: {
borderColor: '#EF4444',
},
submitRow: {
marginTop: 20,
alignItems: 'flex-end',
height:50
},
submitBtn: {
backgroundColor: colors.primary,
paddingVertical: 10,
paddingHorizontal: 22,
borderRadius: 8,
minWidth: 120,
alignItems: 'center',
},
submitText: {
color: '#FFFFFF',
fontWeight: '700',
fontSize: 14,
},
})
+201 -46
View File
@@ -1,37 +1,30 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native';
import { pick } from '@react-native-documents/picker';
import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated';
import BouncyCheckbox from 'react-native-bouncy-checkbox';
import { InsideHeader, Skelton } from '../../Component/Index';
import { InsideHeader, Loader, Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
import EmdComplete from './Component/EmdComplete';
import NavigationString from '../../Constant/NavigationString';
import { useNavigation } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import { useAuctionItem, useEmdCheck } from '../../Services/BBPS/Hooks';
import { apiCall } from '../../Axios/Axios';
import { AUCTION_ITEM_SUBMIT, CHECK_TECHNICAL_DOCUMENTAION, GET_TECHNICAL_DOCUMENTAION } from '../../Services/BBPS/ApiUrls';
import TemplateFormModal from './Component/TemplateFormModal';
import { SelectedItem } from './Type/BidType';
const DUMMY_AUCTION_ITEMS = [
{ id: 1, productName: 'Iron Scrap Lot A', emdAmount: 50000, itemDocumentName: null },
{ id: 2, productName: 'Steel Rods Lot B', emdAmount: 75000, itemDocumentName: 'old_emd.pdf' },
{ id: 3, productName: 'Copper Wire Lot C', emdAmount: 60000, itemDocumentName: null },
];
type SelectedItem = {
auctionItemId: number;
productImageName: string;
file: {
name: string;
uri: string;
type: string;
} | null;
};
const Emd = () => {
const Navigation: any = useNavigation()
const SelectedAuction = useSelector((state: any) => state.auction.auction)
const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([]);
const [acceptedPolicy, setAcceptedPolicy] = useState(false);
const [isComplete, setIsComplete] = useState(false);
const [allBoolean, setAllBoolean] = useState({ isSubmitEmdLoading: false, isEmdComplete: false, auctionItems: [], technicalDocumentStatus: false, selectTemplate: {}, technicalDocModalVisible: false })
const { mutate } = useEmdCheck()
const { mutate: AuctionItemMutate, isPending } = useAuctionItem()
/* ---------------- Helpers ---------------- */
const isSelected = (id: number) => selectedItems.some(i => i.auctionItemId === id);
const toggleSelect = (item: any, checked: boolean) => {
@@ -70,27 +63,6 @@ const Emd = () => {
};
const handleSubmit = () => {
if (!acceptedPolicy) {
Alert.alert('Policy Required', 'Please accept terms');
return;
}
if (selectedItems.length === 0) {
Alert.alert('Select Item', 'Please select at least one item');
return;
}
for (const item of selectedItems) {
if (!item.productImageName || !item.file) {
Alert.alert('Incomplete', 'Upload documents for all selected items');
return;
}
}
console.log('✅ SUBMITTED:', selectedItems);
setIsComplete(true);
};
/* ---------------- Render Item ---------------- */
const renderItem = ({ item }: any) => {
@@ -141,6 +113,7 @@ const Emd = () => {
updateSelectedItem(item.id, { productImageName: text })
}
style={styles.input}
placeholderTextColor={colors.lightText}
/>
<TouchableOpacity
@@ -163,19 +136,197 @@ const Emd = () => {
);
};
// Api calls
const getAuctionItem = async () => {
try {
setAllBoolean((prev: any) => ({ ...prev, isLoading: true }))
if (SelectedAuction.auctionPattern === 'Forward') {
const payload = { auctionNumber: SelectedAuction.auctionNumber, status: 'Forward' }
AuctionItemMutate(payload, {
onSuccess: (res) => {
if (res.statusCode == 200) {
setAllBoolean((prev: any) => ({ ...prev, auctionItems: Array.isArray(res?.data) ? res.data : [res.data] }))
}
},
onError: (err) => {
}
})
} else if (SelectedAuction.auctionPattern === 'Reverse') {
const payload = { auctionNumber: SelectedAuction.auctionNumber, status: 'Reverse' }
AuctionItemMutate(payload, {
onSuccess: (res) => {
if (res.statusCode == 200) {
setAllBoolean((prev: any) => ({ ...prev, auctionItems: Array.isArray(res?.data) ? res.data : [res.data] }))
}
},
onError: (err) => {
}
})
}
} catch (error) {
console.log('Auction fetch error', error)
} finally {
setAllBoolean((prev: any) => ({ ...prev, isLoading: false }))
}
}
const getTemplateDocumentation = async () => {
try {
if (SelectedAuction.technicalDocReq) {
const res = await apiCall<any>('get', CHECK_TECHNICAL_DOCUMENTAION, {}, { auctionId: SelectedAuction.auctionId })
if (res.data) {
setAllBoolean((prev: any) => ({ ...prev, technicalDocumentStatus: true }))
} else {
const res = await apiCall<any>('get', GET_TECHNICAL_DOCUMENTAION,)
const auctionIdToFind = String(SelectedAuction.auctionId);
const matchedObject = res.data.find((item: any) =>
item.auctionIds
.split(',')
.map((id: any) => id.trim())
.includes(auctionIdToFind)
);
setAllBoolean((prev: any) => ({ ...prev, selectTemplate: matchedObject, technicalDocModalVisible: true }))
}
}
} catch (error) {
console.log('Auction fetch error', error)
} finally {
setAllBoolean((prev: any) => ({ ...prev, isLoading: false }))
}
}
const AlreadyEmdCheck = async () => {
try {
mutate({ auctionNumber: SelectedAuction.auctionNumber }, {
onSuccess: (res => {
console.log(SelectedAuction.auctionNumber, res , SelectedAuction,'iiiiiiiiiiiiiiiiiiiiiiiii');
if (res.statusCode == 200) {
if (res.message == 'No EMD Documents') {
setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: false }))
getAuctionItem()
} else {
if (!SelectedAuction.technicalDocReq) {
setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true, technicalDocumentStatus: true }))
} else {
setAllBoolean((prev: any) => ({ ...prev, isEmdComplete: true }))
getTemplateDocumentation()
}
}
}
}),
onError: (err) => {
}
})
} catch (error) {
console.log('EMD Check Error:', error)
} finally {
setAllBoolean((prev: any) => ({ ...prev, isLoading: false }))
}
}
const handleSubmit = async () => {
setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true }))
if (!acceptedPolicy) {
Alert.alert('Policy Required', 'Please accept terms');
return;
}
if (selectedItems.length === 0) {
Alert.alert('Select Item', 'Please select at least one item');
return;
}
for (const item of selectedItems) {
if (!item.productImageName || !item.file) {
Alert.alert('Incomplete', 'Upload documents for all selected items');
return;
}
}
try {
const formData = new FormData()
formData.append('auctionId', String(SelectedAuction.auctionId))
selectedItems.forEach((item: any) => {
formData.append(
'auctionItemId',
String(item.auctionItemId)
)
formData.append(
'productImageName',
item.productImageName
)
formData.append('files', {
uri: item.file.uri,
name: item.file.name,
type: item.file.type || 'application/pdf',
} as any)
})
const res = await apiCall<any>('post', AUCTION_ITEM_SUBMIT, formData, {}, 'multipart/form-data')
if (res.statusCode == 200) {
if (SelectedAuction.technicalDocReq) {
getTemplateDocumentation()
} else {
if (SelectedAuction.auctionPattern === 'Forward') {
Navigation.navigate(NavigationString.StartBidding)
} else {
Navigation.navigate(NavigationString.StartReverseBidding)
}
}
}
} catch (error) {
console.log('Submit error', error)
Alert.alert('Error', 'Failed to submit EMD')
} finally {
setAllBoolean((prev: any) => ({ ...prev, isSubmitEmdLoading: true }))
}
};
const EmdContinueHandle = async () => {
if (allBoolean.technicalDocumentStatus) {
if (SelectedAuction.auctionPattern === 'Forward') {
Navigation.navigate(NavigationString.StartBidding)
} else {
Navigation.navigate(NavigationString.StartReverseBidding)
}
} else {
getTemplateDocumentation()
}
}
useEffect(() => {
AlreadyEmdCheck()
}, [])
return (
<View style={{ flex: 1, backgroundColor: colors.white }}>
<InsideHeader title="EMD Submission" showArrow />
<View style={styles.container}>
{SelectedAuction.technicalDocReq && allBoolean.technicalDocModalVisible &&
<TemplateFormModal visible={allBoolean.technicalDocModalVisible} allBoolean={allBoolean} setAllBoolean={setAllBoolean} template={allBoolean.selectTemplate} />
}
{false ? (
{isPending ? (
<Skelton />
) : true ? (
<EmdComplete onNext={() => { Navigation.navigate(NavigationString.StartBidding) }} />
) : allBoolean.isEmdComplete ? (
<EmdComplete onNext={EmdContinueHandle} />
) : <>
<FlatList
data={DUMMY_AUCTION_ITEMS}
keyExtractor={item => item.id.toString()}
data={allBoolean.auctionItems}
keyExtractor={(item: any) => item.auctionId}
renderItem={renderItem}
contentContainerStyle={{ paddingBottom: 170 }}
/>
@@ -200,7 +351,10 @@ const Emd = () => {
onPress={handleSubmit}
disabled={!acceptedPolicy}
>
<Text style={styles.submitText}>Submit EMD</Text>
{
allBoolean.isSubmitEmdLoading ? <Loader size='small' color={colors.white} /> : <Text style={styles.submitText}>Submit EMD</Text>
}
</TouchableOpacity>
</View>
</>
@@ -277,6 +431,7 @@ const styles = StyleSheet.create({
padding: 12,
backgroundColor: '#FFFFFF',
fontSize: 14,
color: colors.black
},
uploadBtn: {
+243 -152
View File
@@ -1,154 +1,136 @@
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
FlatList,
TextInput,
TouchableOpacity,
StyleSheet,
Platform,
Alert,
} from 'react-native';
import React, { useCallback, useEffect, useState } from 'react';
import { View, FlatList, StyleSheet, Platform, Alert, BackHandler, Text } from 'react-native';
import LottieView from 'lottie-react-native';
import Animated, {
FadeInUp,
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
Layout,
interpolate,
} from 'react-native-reanimated';
import { InsideHeader } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useWebSocketService } from '../../socket/Socket';
import Variables from '../../Constant/Variable';
import { useSelector } from 'react-redux';
import { apiCall } from '../../Axios/Axios';
import { FORWARD_AUCTION_ITEM, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/BBPS/ApiUrls';
import BiddingCard from './Component/BiddingCard';
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import CountdownTimer from '../../Component/Counter/CountdownTimer';
import FontsFamily from '../../Constant/FontsFamily';
/* ---------------- Types ---------------- */
interface AuctionItem {
id: number;
productName: string;
auctionStartValueFigure: number;
bidVariationValue: number;
bidAmount: number;
bidAvilable: number;
me: boolean;
}
const StartBidding = () => {
const SelectedAuction = useSelector((state: any) => state.auction.auction);
const loginUser = useSelector((state: any) => state.token.token)
const Navigation: any = useNavigation()
const [items, setItems] = useState<any>([]);
const [bidValues, setBidValues] = useState<Record<number, number>>({});
const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, bidPayload: {} })
const { subscribe, createRoom, placeBid } = useWebSocketService(
Variables.webSocketUrl,
() => console.log('Connected!'),
(error) => console.log('WebSocket Error:', error),
loginUser.token,
);
/* ---------------- Dummy Data ---------------- */
const DUMMY_AUCTION = { auctionNumber: 'AUC-2025-001' };
const DUMMY_ITEMS: AuctionItem[] = [
{ id: 1, productName: 'Iron Scrap Lot', auctionStartValueFigure: 20000, bidVariationValue: 500, bidAmount: 20000, bidAvilable: 20000, me: false },
{ id: 2, productName: 'Steel Rod Bundle', auctionStartValueFigure: 35000, bidVariationValue: 1000, bidAmount: 35000, bidAvilable: 35000, me: true },
{ id: 3, productName: 'Copper Wire Roll', auctionStartValueFigure: 15000, bidVariationValue: 250, bidAmount: 15000, bidAvilable: 0, me: false },
];
const getAllAcutionItems = async () => {
try {
const payload = { auctionNumber: SelectedAuction.auctionNumber };
let auctionItem: any[] = [];
/* ---------------- Bidding Card ---------------- */
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid }: any) => {
const [expanded, setExpanded] = useState(false);
const isMin = bidValue <= item.bidAmount;
if (SelectedAuction.auctionPattern === 'Forward') {
const res = await apiCall<any>('get', FORWARD_AUCTION_ITEM, {}, payload);
if (res?.statusCode === 200) auctionItem = res.data || [];
} else {
const res = await apiCall<any>('get', REVERSE_AUCTION_ITEM, {}, payload);
if (res?.statusCode === 200) auctionItem = res.data || [];
}
const animation = useSharedValue(0);
const selectedRes = await apiCall<any>('get', SELECTED_AUCTION_ITEM, {}, payload);
console.log(selectedRes,'jhhhhhhhhhh');
const toggleExpand = () => {
setExpanded(!expanded);
animation.value = withSpring(expanded ? 0 : 1, { damping: 15 });
const uniqueArray: number[] = Array.from(
new Set(selectedRes?.data || [])
);
const filteredItems = auctionItem.filter(item =>
uniqueArray.includes(item.id)
);
const result = await createRoom(SelectedAuction.auctionNumber);
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, data => {
console.log('🔥 BID UPDATE:', data);
});
const bidAmountMap2 = new Map<number, { bidAmount: number; contractorId: number }>();
(result || []).forEach((r: any) => {
bidAmountMap2.set(r.auctionItemId, {
bidAmount: r.bidAmount,
contractorId: r.contractorId,
});
});
const bidAmountMap = new Map<number, number>();
(result || []).forEach((r: any) => {
bidAmountMap.set(r.auctionItemId, r.bidAmount);
});
const updatedFilteredItems = filteredItems.map(item => {
const bidData = bidAmountMap2.get(item.id);
return {
...item,
bidAmount: bidData?.bidAmount ?? item.auctionStartValueFigure,
bidAvilable: bidData?.bidAmount ?? 0,
me: bidData?.contractorId == loginUser.contractorId ? true : false
};
});
const initialBids: Record<number, number> = {};
updatedFilteredItems.forEach(item => {
initialBids[item.id] = item.bidAmount;
});
setBidValues(initialBids);
setItems(updatedFilteredItems);
} catch (error) {
console.error('Error fetching auction items:', error);
}
};
const bodyStyle = useAnimatedStyle(() => {
return {
height: interpolate(animation.value, [0, 1], [0, 190]),
opacity: interpolate(animation.value, [0, 0.5, 1], [0, 0, 1]),
marginTop: interpolate(animation.value, [0, 1], [0, 15]),
};
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message) => {
// console.log('RAW MESSAGE:', message[message.length - 1]);
const result = message[message.length - 1]
const updatedBidItem = (prev: any[]) =>
prev.map(item =>
item.id === result.auctionItemId
? { ...item, bidAmount: result.bidAmount, me: result.contractorId == loginUser.contractorId ? true : false }
: item
);
setItems(updatedBidItem);
setBidValues(prev => ({
...prev,
[result.auctionItemId]: result.bidAmount,
}));
try {
} catch (err) {
console.log('JSON parse error:', err);
}
});
const arrowStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${interpolate(animation.value, [0, 1], [0, 180])}deg` }],
}));
useEffect(() => {
getAllAcutionItems();
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data) => { });
}, []);
return (
<Animated.View
layout={Layout.springify()}
entering={FadeInUp.delay(item.id * 100)}
style={[styles.card, item.me && styles.leadingCard]}
>
{/* Header - Always Visible */}
<TouchableOpacity
activeOpacity={0.7}
onPress={toggleExpand}
style={styles.cardHeader}
>
<View style={{ flex: 1 }}>
<Text style={styles.productName}>{item.productName}</Text>
<Text style={styles.itemIdText}>{DUMMY_AUCTION.auctionNumber}</Text>
</View>
<View style={styles.headerRight}>
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
<Text style={[styles.badgeText, item.me ? styles.meText : styles.liveText]}>
{item.me ? 'LEADING' : 'LIVE'}
</Text>
</View>
<Animated.Text style={[styles.arrow, arrowStyle]}>▼</Animated.Text>
</View>
</TouchableOpacity>
{/* Main Price Info - Always Visible */}
<View style={styles.priceRow}>
<View>
<Text style={styles.label}>Current Bid</Text>
<Text style={styles.mainPrice}>₹{item.bidAmount.toLocaleString()}</Text>
</View>
<View style={{ alignItems: 'flex-end' }}>
<Text style={styles.label}>Increment</Text>
<Text style={styles.incrementText}>+₹{item.bidVariationValue}</Text>
</View>
</View>
{/* Collapsible Section */}
<Animated.View style={[styles.collapsibleContainer, bodyStyle]}>
<View style={styles.divider} />
<Text style={styles.bidLabel}>Set Your Bid</Text>
<View style={styles.stepperContainer}>
<TouchableOpacity
onPress={onDecrease}
disabled={isMin}
style={[styles.stepBtn, isMin && styles.disabledBtn]}
>
<Text style={styles.stepText}>−</Text>
</TouchableOpacity>
<View style={styles.inputBox}>
<Text style={styles.currency}>₹</Text>
<Text style={styles.bidValueText}>{bidValue.toLocaleString()}</Text>
</View>
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
<Text style={styles.stepText}>+</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.submitBtn}
onPress={onPlaceBid}
activeOpacity={0.8}
>
<Text style={styles.submitBtnText}>Confirm Bid</Text>
</TouchableOpacity>
</Animated.View>
</Animated.View>
);
};
/* ---------------- Main Screen ---------------- */
const StartBidding = () => {
const [items, setItems] = useState<AuctionItem[]>(DUMMY_ITEMS);
const [bidValues, setBidValues] = useState<Record<number, number>>({});
const [showSuccess, setShowSuccess] = useState(false);
useEffect(() => {
const initial: Record<number, number> = {};
items.forEach(item => {
items.forEach((item: any) => {
initial[item.id] = item.bidAmount + item.bidVariationValue;
});
setBidValues(initial);
@@ -158,38 +140,133 @@ const StartBidding = () => {
setBidValues(prev => ({ ...prev, [id]: prev[id] + val }));
};
const handlePlaceBid = (item: AuctionItem) => {
Alert.alert('Confirm', `Place bid for ₹${bidValues[item.id]}?`, [
{ text: 'No' },
{
text: 'Yes, Bid',
onPress: () => {
setItems(prev => prev.map(i => i.id === item.id ? { ...i, bidAmount: bidValues[item.id], me: true } : i));
setShowSuccess(true);
setTimeout(() => setShowSuccess(false), 2000);
}
}
]);
const submitBid = async (item: any) => {
const payload = {
itemId: item.id,
bidAmount: bidValues[item.id],
auctionId: SelectedAuction.auctionId,
contractorId: loginUser.contractorId,
fullName: loginUser.fullName,
roomId: SelectedAuction.auctionNumber,
};
setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, }));
Alert.alert(
'Confirm Bid',
`Are you sure you want to place a bid of ₹${payload.bidAmount}?`,
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Submit',
onPress: () => {
placeBidSubmit(payload);
},
},
],
{ cancelable: true }
);
};
const placeBidSubmit = async (data: any) => {
try {
const result: any = await placeBid(data);
if (result?.status && result.data) {
const updatedBidItem = (prev: any[]) =>
prev.map(item =>
item.id === result.data.auctionItemId
? { ...item, bidAmount: result.data.bidAmount, me: result.data?.contractorId == loginUser.contractorId ? true : false }
: item
);
// setItems(updatedBidItem);
// setBidValues((prev: any) => ({
// ...prev,
// [result.data.auctionItemId]: {
// ...prev[result.data.auctionItemId],
// bidAmount: result.data.bidAmount,
// },
// }));
getAllAcutionItems()
}
} catch (error) {
console.error('Error placing bid:', error);
}
};
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
Alert.alert(
'Confirm',
'Do you want to go back to Dashboard?',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Yes',
onPress: () => {
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Home }],
})
);
},
},
],
{ cancelable: true }
);
return true; // block default back action
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress
);
return () => subscription.remove();
}, [Navigation])
);
return (
<View style={styles.container}>
<InsideHeader title="Bidding Panel" showArrow />
<View style={styles.container2}>
<Text style={styles.title}>
{SelectedAuction.deptName}
</Text>
<View style={styles.timerWrapper}>
<CountdownTimer
endDate={SelectedAuction.keyDates.auctionBidding.endDateTime}
myStyle={{ fontSize: 30 }}
size={30}
/>
</View>
</View>
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
renderItem={({ item, index }) => (
<BiddingCard
item={item}
item={{ ...item, index }}
bidValue={bidValues[item.id] || 0}
onIncrease={() => handleUpdate(item.id, item.bidVariationValue)}
onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)}
onPlaceBid={() => handlePlaceBid(item)}
onPlaceBid={() => { submitBid(item) }}
SelectedAuction={SelectedAuction}
/>
)}
contentContainerStyle={{ padding: 16 }}
/>
{showSuccess && (
{allBoolean.showSuccess && (
<View style={styles.overlay}>
<LottieView
source={require('../../lottie/Success.json')}
@@ -205,9 +282,23 @@ const StartBidding = () => {
export default StartBidding;
/* ---------------- Modern Styles ---------------- */
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.white },
container2: {
paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 8,
},
title: {
fontSize: 18,
fontFamily: FontsFamily.poppinsSemiBold,
color: colors.black,
marginBottom: 6,
// textAlign:'center'
},
timerWrapper: {
alignItems: 'center',
},
card: {
backgroundColor: '#FFF',
borderRadius: 24,
+401
View File
@@ -0,0 +1,401 @@
import React, { useCallback, useEffect, useState } from 'react';
import { View, FlatList, StyleSheet, Platform, Alert, BackHandler, Text, ToastAndroid } from 'react-native';
import LottieView from 'lottie-react-native';
import { InsideHeader } from '../../Component/Index';
import colors from '../../Constant/Color';
import { useWebSocketService } from '../../socket/Socket';
import Variables from '../../Constant/Variable';
import { useSelector } from 'react-redux';
import { apiCall } from '../../Axios/Axios';
import { AUCTION_ONE_TIME_BID, GET_AUCTION_DETAILS, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/BBPS/ApiUrls';
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import CountdownTimer from '../../Component/Counter/CountdownTimer';
import FontsFamily from '../../Constant/FontsFamily';
import ReverseBiddingCard from './Component/ReverseBiddingCard';
const StartReverseBidding = () => {
const SelectedAuction = useSelector((state: any) => state.auction.auction);
const loginUser = useSelector((state: any) => state.token.token)
const Navigation: any = useNavigation()
const [items, setItems] = useState<any>([]);
const [bidValues, setBidValues] = useState<Record<number, number>>({});
const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, bidPayload: {}, oneTimeBid: false, rebid: false, reload: false })
const { subscribe, createRoom, placeBid } = useWebSocketService(
Variables.webSocketUrl,
() => console.log('Connected!'),
(error) => console.log('WebSocket Error:', error),
loginUser.token,
);
const getAuction = async () => {
const selectedRes = await apiCall<any>('get', GET_AUCTION_DETAILS, {}, { id: SelectedAuction.auctionId });
if (selectedRes.statusCode == 200 && selectedRes.data.contractorId.length) {
setAllBoolean((prev: any) => ({ ...prev, oneTimeBid: true }))
}
}
const getOneTimeBidValue = async () => {
try {
const selectedRes = await apiCall<any>('get', AUCTION_ONE_TIME_BID, {}, { auctionId: SelectedAuction.auctionId });
if (selectedRes.statusCode == 200) {
return selectedRes.data
} else {
return []
}
} catch (error) {
}
}
const getAllAcutionItems = async () => {
try {
const payload = { auctionNumber: SelectedAuction.auctionNumber };
let auctionItem: any[] = [];
if (SelectedAuction.auctionPattern === 'Reverse') {
const res = await apiCall<any>('get', REVERSE_AUCTION_ITEM, {}, payload);
if (res?.statusCode === 200) auctionItem = res.data || [];
}
const selectedRes = await apiCall<any>('get', SELECTED_AUCTION_ITEM, {}, payload);
const uniqueArray: number[] = Array.from(
new Set(selectedRes?.data || [])
);
const filteredItems = auctionItem.filter(item =>
uniqueArray.includes(item.id)
);
if (SelectedAuction.auctionCategories == 'Live') {
const result = await createRoom(SelectedAuction.auctionNumber);
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, data => {
console.log('🔥 BID UPDATE:', data);
});
const bidAmountMap2 = new Map<number, { bidAmount: number; contractorId: number }>();
(result || []).forEach((r: any) => {
bidAmountMap2.set(r.auctionItemId, {
bidAmount: r.bidAmount,
contractorId: r.contractorId,
});
});
// Check One Time bid enable butoon
if (SelectedAuction.auctionCategories != 'Live') {
const myBid = result.find(
(bid: any) => bid.contractorId === loginUser.contractorId
);
if (myBid) {
setAllBoolean((prev: any) => ({ ...prev, oneTimeBid: true }))
}
}
const bidAmountMap = new Map<number, number>();
(result || []).forEach((r: any) => {
bidAmountMap.set(r.auctionItemId, r.bidAmount);
});
const updatedFilteredItems = filteredItems.map(item => {
const bidData = bidAmountMap2.get(item.id);
return {
...item,
bidAmount: bidData?.bidAmount ?? item.auctionStartValue,
bidAvilable: bidData?.bidAmount ?? 0,
me: bidData?.contractorId == loginUser.contractorId ? true : false
};
});
const initialBids: Record<number, number> = {};
updatedFilteredItems.forEach(item => {
initialBids[item.id] = item.bidAmount;
});
setBidValues(initialBids);
setItems(updatedFilteredItems);
} else {
let result = []
if (SelectedAuction.rebidAllowed) {
setAllBoolean((prev: any) => ({ ...prev, rebid: true }))
}
if (SelectedAuction.auctionCategories != 'Live') {
result = await getOneTimeBidValue()
}
const bidMap = new Map<number, any>();
result.forEach((bid: any) => {
bidMap.set(bid.auctionItemId, bid);
});
const mergedData = auctionItem.map((item: any) => {
const bid = bidMap.get(item.id);
if (!bid) {
return {
...item,
bidAmount: item.auctionStartValue,
contractorId: item.contractorId,
};
}
return {
...item,
bidAmount: bid.bidAmount,
bidId: bid.bidId,
contractorId: bid.contractorId,
contractorName: bid.contractorName,
me: bid.contractorId === loginUser.contractorId,
bidTime: bid.bidTime,
};
});
const initialBids: Record<number, number> = {};
mergedData.forEach(item => {
initialBids[item.id] = item.bidAmount;
});
setBidValues(initialBids);
setItems(mergedData)
setBidValues(initialBids);
}
} catch (error) {
console.error('Error fetching auction items:', error);
}
};
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message) => {
const result = message[message.length - 1]
const updatedBidItem = (prev: any[]) =>
prev.map(item =>
item.id === result.auctionItemId
? { ...item, bidAmount: result.bidAmount, me: result.contractorId == loginUser.contractorId ? true : false }
: item
);
setItems(updatedBidItem);
setBidValues(prev => ({
...prev,
[result.auctionItemId]: result.bidAmount,
}));
try {
} catch (err) {
console.log('JSON parse error:', err);
}
});
useEffect(() => {
if (SelectedAuction.auctionCategories == 'One-time') {
getAuction()
}
getAllAcutionItems();
subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data) => { });
}, [allBoolean.reload]);
useEffect(() => {
const initial: Record<number, number> = {};
items.forEach((item: any) => {
initial[item.id] = item.bidAmount + item.bidVariationValue;
});
setBidValues(initial);
}, []);
const handleUpdate = (id: number, val: number) => {
setBidValues(prev => ({ ...prev, [id]: prev[id] + val }));
};
const submitBid = async (item: any) => {
const payload = {
itemId: item.id,
bidAmount: bidValues[item.id],
auctionId: SelectedAuction.auctionId,
contractorId: loginUser.contractorId,
fullName: loginUser.fullName,
roomId: SelectedAuction.auctionNumber,
};
setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, }));
Alert.alert(
'Confirm Bid',
`Are you sure you want to place a bid of ₹${payload.bidAmount}?`,
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Submit',
onPress: () => {
placeBidSubmit(payload);
},
},
],
{ cancelable: true }
);
};
const placeBidSubmit = async (data: any) => {
try {
const result: any = await placeBid(data);
console.log(result, 'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk');
if (result?.status && result.data) {
const updatedBidItem = (prev: any[]) =>
prev.map(item =>
item.id === result.data.auctionItemId
? { ...item, bidAmount: result.data.bidAmount, me: result.data?.contractorId == loginUser.contractorId ? true : false }
: item
);
getAllAcutionItems()
}
} catch (error) {
console.error('Error placing bid:', error);
}
};
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
Alert.alert(
'Confirm',
'Do you want to go back to Dashboard?',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Yes',
onPress: () => {
Navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: NavigationString.Home }],
})
);
},
},
],
{ cancelable: true }
);
return true; // block default back action
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress
);
return () => subscription.remove();
}, [Navigation])
);
return (
<View style={styles.container}>
<InsideHeader title="Bidding Panel" showArrow />
<View style={styles.container2}>
<Text style={styles.title}>
{SelectedAuction.deptName}
</Text>
<View style={styles.timerWrapper}>
<CountdownTimer
endDate={SelectedAuction.keyDates.auctionBidding.endDateTime}
myStyle={{ fontSize: 30 }}
size={30}
/>
</View>
</View>
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item, index }) => (
<ReverseBiddingCard
item={{ ...item, index }}
bidValue={bidValues[item.id] || 0}
onIncrease={() => handleUpdate(item.id, item.emdAmount)}
onDecrease={() => handleUpdate(item.id, -item.emdAmount)}
onPlaceBid={() => { submitBid(item) }}
SelectedAuction={SelectedAuction}
allBoolean={allBoolean}
setAllBoolean={setAllBoolean}
/>
)}
contentContainerStyle={{ padding: 16 }}
/>
{allBoolean.showSuccess && (
<View style={styles.overlay}>
<LottieView
source={require('../../lottie/Success.json')}
autoPlay
loop={false}
style={{ width: 200, height: 200 }}
/>
</View>
)}
</View>
);
};
export default StartReverseBidding;
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.white },
container2: {
paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 8,
},
title: {
fontSize: 18,
fontFamily: FontsFamily.poppinsSemiBold,
color: colors.black,
marginBottom: 6,
// textAlign:'center'
},
timerWrapper: {
alignItems: 'center',
},
card: {
backgroundColor: '#FFF',
borderRadius: 24,
padding: 20,
marginBottom: 16,
borderWidth: 1,
borderColor: '#E2E8F0',
overflow: 'hidden',
...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }),
},
leadingCard: { borderColor: '#22C55E', borderWidth: 2, backgroundColor: '#F0FDF4' },
cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' },
headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 },
productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
liveBadge: { backgroundColor: '#FEE2E2' },
meBadge: { backgroundColor: '#BBF7D0' },
badgeText: { fontSize: 10, fontWeight: '900' },
liveText: { color: '#EF4444' },
meText: { color: '#15803D' },
arrow: { fontSize: 10, color: '#94A3B8' },
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
label: { fontSize: 11, color: '#94A3B8', textTransform: 'uppercase', fontWeight: '600' },
mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
collapsibleContainer: { overflow: 'hidden' },
divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
stepBtn: { width: 45, height: 45, backgroundColor: colors.primary, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
disabledBtn: { backgroundColor: '#CBD5E1' },
stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
inputBox: { flex: 1, height: 45, backgroundColor: '#F1F5F9', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
submitBtn: { height: 50, backgroundColor: colors.primary, borderRadius: 15, justifyContent: 'center', alignItems: 'center' },
submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(255,255,255,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 99 },
});
+18 -1
View File
@@ -15,4 +15,21 @@ export type allBooleanType = {
activeItemId: number | null
acitveItemData: any
timeEnd: boolean
}
}
export interface Props {
visible: boolean
setAllBoolean: any
allBoolean: any
template: any
}
export type SelectedItem = {
auctionItemId: number;
productImageName: string;
file: {
name: string;
uri: string;
type: string;
} | null;
};
+3 -1
View File
@@ -3,6 +3,7 @@ import PurchaseAuctionList from "./Auction/PurchaseAuctionList";
import PurchaseList from "./Auction/PurchaseList";
import Emd from "./Bidding/Emd";
import StartBidding from "./Bidding/StartBidding";
import StartReverseBidding from "./Bidding/StartReverseBidding";
import Home from "./Home/Home";
import Login from "./Login/Login";
import Onboarding from "./Onboarding/Onboarding";
@@ -20,6 +21,7 @@ const Screens = {
PurchaseList,
Emd,
StartBidding,
PurchaseAuctionList
PurchaseAuctionList,
StartReverseBidding
}
export default Screens
+3 -1
View File
@@ -40,7 +40,7 @@ const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=`
const DOWNLOAD_DOC = `${BASE_URL}eTendering/download/docId?docId=`
const TWO_FACTOR_AUTH = `${BASE_URL}contractor/two-factor-auth?status=`
const AUCTION_LOGIN = `https://api.godemo.in/Auction/auction/loginWithoutDecryption`
const GET_AUCTION_DETAILS = `${BASE_URL_Auction}api/auction/getAuction/id?id=`
const GET_AUCTION_DETAILS = `${BASE_URL_Auction}api/auction/getAuction/id`
const TANDER_PURCHASE_STATUS = `https://procurelinc.in/EProcurementSB/tenderPayment/transactionStatus?orderNo=`
const GET_MY_AUCTION = `${BASE_URL_Auction}api/auction/getMyAuction`
const GET_MY_TENDER = "https://procurelinc.in/EProcurementSB/tenderBidding/getAllPurchasedTendersByConId"
@@ -61,10 +61,12 @@ const ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocu
// Rohit
const DASHBOARD_COUNT = `${BASE_URL_Auction}api/auction/home/dashboard/counts`
const AUCTION_ONE_TIME_BID =`${BASE_URL_Auction}api/bidding/getBidForAuction`
export {
// Rohit
DASHBOARD_COUNT,
AUCTION_ONE_TIME_BID,
ITEM_DOCUMENT_DOWNLOAD,
ITEM_DETAIL,
TECHNICAL_DOCUMENT_SUBMIT,
+14 -1
View File
@@ -1,5 +1,5 @@
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import { fetchDashboard, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseTender, offlineAuctionPurchase, userLogin } from './apis';
import { fetchAuctionItem, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseTender, offlineAuctionPurchase, userLogin } from './apis';
// User
@@ -74,4 +74,17 @@ export const useOfflineAuctionPurchase = () => {
return useMutation({
mutationFn: (payload: any) => offlineAuctionPurchase(payload),
});
};
// EMD
export const useEmdCheck = () => {
return useMutation({
mutationFn: (payload: any) => fetchEmdCheck(payload),
});
};
export const useAuctionItem = () => {
return useMutation({
mutationFn: (payload: any) => fetchAuctionItem(payload),
});
};
+30 -3
View File
@@ -1,5 +1,5 @@
import axios from "axios";
import { DASHBOARD_COUNT, GET_MY_AUCTION, GET_MY_TENDER, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION } from "./ApiUrls";
import { DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_MY_AUCTION, GET_MY_TENDER, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM } from "./ApiUrls";
import { apiCall } from "../../Axios/Axios";
// User
@@ -39,8 +39,35 @@ export const fetchLiveAuction = async (payload: any): Promise<any> => {
// Auction Purchase offline
export const offlineAuctionPurchase = async (payload: any): Promise<any> => {
try {
const response = await axios.post(PROCEED_PURCHASE_AUCTION, payload);
return response.data;
const response = await apiCall<any>('post', `${PURCHASE_OFFLINE_AUCTION}`, payload);
return response;
} catch (error: any) {
throw error.response?.data || error.message;
}
};
// EMD
export const fetchEmdCheck = async (payload: any): Promise<any> => {
try {
const response = await apiCall<any>('get', `${EMD_CHECK}`, {}, payload);
return response;
} catch (error: any) {
throw error.response?.data || error.message;
}
};
export const fetchAuctionItem = async (payload: any): Promise<any> => {
try {
if (payload.status == 'Reverse') {
const response = await apiCall<any>('get', `${REVERSE_AUCTION_ITEM}`, {}, payload);
return response;
} else {
const response = await apiCall<any>('get', `${FORARD_AUCTION_ITEM}`, {}, payload);
return response;
}
} catch (error: any) {
throw error.response?.data || error.message;
}
File diff suppressed because one or more lines are too long
+10 -12
View File
@@ -51,8 +51,8 @@ const reducer = (state: State, action: Action): State => {
export const useWebSocketService = (
webSocketUrl: string,
onConnectCallback: () => void,
onErrorCallback: (error: string) => void
) => {
onErrorCallback: (error: string) => void,
Token: any) => {
const [state, dispatch] = useReducer(reducer, {
client: null,
subscriptions: new Map(),
@@ -84,7 +84,7 @@ export const useWebSocketService = (
onConnect: () => {
isConnected.current = true;
console.log('✅ WebSocket connected');
// console.log('✅ WebSocket connected');
// 🔁 Flush queued subscriptions
pendingSubscriptions.current.forEach(sub =>
@@ -111,7 +111,7 @@ export const useWebSocketService = (
// 🧠 Queue if not connected
if (!client || !isConnected.current) {
console.log('⏳ Queued subscription:', destination);
// console.log('⏳ Queued subscription:', destination);
pendingSubscriptions.current.push({ destination, callback });
return;
}
@@ -123,7 +123,6 @@ export const useWebSocketService = (
try {
const parsed = JSON.parse(message.body);
console.log('📡 LIVE DATA:', parsed);
callback(parsed);
} catch (e) {
console.error('❌ JSON parse error', e);
@@ -151,7 +150,6 @@ export const useWebSocketService = (
// ================= ROOM =================
const createRoom = async (roomId: any) => {
const Token = useSelector((prev: any) => prev.token.token)
if (!roomId) return;
try {
@@ -160,7 +158,7 @@ export const useWebSocketService = (
null,
{
params: { roomId },
headers: { Authentication: `Bearer ${Token.token}` },
headers: { Authentication: `Bearer ${Token}` },
}
);
@@ -175,11 +173,11 @@ export const useWebSocketService = (
};
const joinRoom = async (roomId: any) => {
const Token = useSelector((prev: any) => prev.token.token)
try {
const { data } = await axios.get(
`${Variables.socketUrl}livebidding/joinRoom?roomId=${roomId}`,
{ headers: { Authentication: `Bearer ${Token.token}` } }
{ headers: { Authentication: `Bearer ${Token}` } }
);
if (data.statusCode === 200) return data.data.bids;
@@ -190,17 +188,17 @@ export const useWebSocketService = (
// ================= BID =================
const placeBid = async (BidDTO: any) => {
const Token = useSelector((prev: any) => prev.token.token)
try {
const res = await axios.post(
`${Variables.socketUrl}livebidding/placeBid`,
BidDTO,
{ headers: { Authentication: `Bearer ${Token.token}` } }
{ headers: { Authentication: `Bearer ${Token}` } }
);
if (res.data.statusCode === 200) {
showSuccessAlert(res.data.message);
return { data: res.data.data, status: true };
return { data: res.data.data, status: true, message: res.data.message };
}
} catch (err: any) {
if (err?.response?.data?.statusCode === 400) {
+340
View File
@@ -0,0 +1,340 @@
// import React, { useCallback, useEffect, useState } from 'react';
// import { View, FlatList, StyleSheet, Platform, Alert, BackHandler, Text, ToastAndroid } from 'react-native';
// import LottieView from 'lottie-react-native';
// import { InsideHeader } from '../../Component/Index';
// import colors from '../../Constant/Color';
// import { useWebSocketService } from '../../socket/Socket';
// import Variables from '../../Constant/Variable';
// import { useSelector } from 'react-redux';
// import { apiCall } from '../../Axios/Axios';
// import { FORWARD_AUCTION_ITEM, REVERSE_AUCTION_ITEM, SELECTED_AUCTION_ITEM } from '../../Services/BBPS/ApiUrls';
// import BiddingCard from './Component/BiddingCard';
// import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
// import NavigationString from '../../Constant/NavigationString';
// import CountdownTimer from '../../Component/Counter/CountdownTimer';
// import FontsFamily from '../../Constant/FontsFamily';
// const StartBidding = () => {
// const SelectedAuction = useSelector((state: any) => state.auction.auction);
// const loginUser = useSelector((state: any) => state.token.token)
// const Navigation: any = useNavigation()
// const [items, setItems] = useState<any>([]);
// const [bidValues, setBidValues] = useState<Record<number, number>>({});
// const [allBoolean, setAllBoolean] = useState({ showSuccess: false, showDialog: false, bidPayload: {} })
// const { subscribe, createRoom, placeBid } = useWebSocketService(
// Variables.webSocketUrl,
// () => console.log('Connected!'),
// (error) => console.log('WebSocket Error:', error),
// loginUser.token,
// );
// const getAllAcutionItems = async () => {
// try {
// const payload = { auctionNumber: SelectedAuction.auctionNumber };
// let auctionItem: any[] = [];
// if (SelectedAuction.auctionPattern === 'Forward') {
// const res = await apiCall<any>('get', FORWARD_AUCTION_ITEM, {}, payload);
// if (res?.statusCode === 200) auctionItem = res.data || [];
// } else if(SelectedAuction.auctionPattern === 'Reverse') {
// const res = await apiCall<any>('get', REVERSE_AUCTION_ITEM, {}, payload);
// if (res?.statusCode === 200) auctionItem = res.data || [];
// }
// const selectedRes = await apiCall<any>('get', SELECTED_AUCTION_ITEM, {}, payload);
// const uniqueArray: number[] = Array.from(
// new Set(selectedRes?.data || [])
// );
// const filteredItems = auctionItem.filter(item =>
// uniqueArray.includes(item.id)
// );
// const result = await createRoom(SelectedAuction.auctionNumber);
// subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, data => {
// console.log('🔥 BID UPDATE:', data);
// });
// const bidAmountMap2 = new Map<number, { bidAmount: number; contractorId: number }>();
// (result || []).forEach((r: any) => {
// bidAmountMap2.set(r.auctionItemId, {
// bidAmount: r.bidAmount,
// contractorId: r.contractorId,
// });
// });
// const bidAmountMap = new Map<number, number>();
// (result || []).forEach((r: any) => {
// bidAmountMap.set(r.auctionItemId, r.bidAmount);
// });
// const updatedFilteredItems = filteredItems.map(item => {
// const bidData = bidAmountMap2.get(item.id);
// return {
// ...item,
// bidAmount: bidData?.bidAmount ?? item.auctionStartValueFigure,
// bidAvilable: bidData?.bidAmount ?? 0,
// me: bidData?.contractorId == loginUser.contractorId ? true : false
// };
// });
// const initialBids: Record<number, number> = {};
// updatedFilteredItems.forEach(item => {
// initialBids[item.id] = item.bidAmount;
// });
// setBidValues(initialBids);
// setItems(updatedFilteredItems);
// } catch (error) {
// console.error('Error fetching auction items:', error);
// }
// };
// subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, async (message) => {
// const result = message[message.length - 1]
// const updatedBidItem = (prev: any[]) =>
// prev.map(item =>
// item.id === result.auctionItemId
// ? { ...item, bidAmount: result.bidAmount, me: result.contractorId == loginUser.contractorId ? true : false }
// : item
// );
// setItems(updatedBidItem);
// setBidValues(prev => ({
// ...prev,
// [result.auctionItemId]: result.bidAmount,
// }));
// try {
// } catch (err) {
// console.log('JSON parse error:', err);
// }
// });
// useEffect(() => {
// getAllAcutionItems();
// subscribe(`/topic/room/${SelectedAuction.auctionNumber}`, (data) => { });
// }, []);
// useEffect(() => {
// const initial: Record<number, number> = {};
// items.forEach((item: any) => {
// initial[item.id] = item.bidAmount + item.bidVariationValue;
// });
// setBidValues(initial);
// }, []);
// const handleUpdate = (id: number, val: number) => {
// setBidValues(prev => ({ ...prev, [id]: prev[id] + val }));
// };
// const submitBid = async (item: any) => {
// const payload = {
// itemId: item.id,
// bidAmount: bidValues[item.id],
// auctionId: SelectedAuction.auctionId,
// contractorId: loginUser.contractorId,
// fullName: loginUser.fullName,
// roomId: SelectedAuction.auctionNumber,
// };
// setAllBoolean((prev: any) => ({ ...prev, bidPayload: payload, }));
// Alert.alert(
// 'Confirm Bid',
// `Are you sure you want to place a bid of ₹${payload.bidAmount}?`,
// [
// {
// text: 'Cancel',
// style: 'cancel',
// },
// {
// text: 'Submit',
// onPress: () => {
// placeBidSubmit(payload);
// },
// },
// ],
// { cancelable: true }
// );
// };
// const placeBidSubmit = async (data: any) => {
// try {
// const result: any = await placeBid(data);
// if (result?.status && result.data) {
// const updatedBidItem = (prev: any[]) =>
// prev.map(item =>
// item.id === result.data.auctionItemId
// ? { ...item, bidAmount: result.data.bidAmount, me: result.data?.contractorId == loginUser.contractorId ? true : false }
// : item
// );
// // ToastAndroid.show(result.message, ToastAndroid.SHORT)
// // setItems(updatedBidItem);
// // setBidValues((prev: any) => ({
// // ...prev,
// // [result.data.auctionItemId]: {
// // ...prev[result.data.auctionItemId],
// // bidAmount: result.data.bidAmount,
// // },
// // }));
// getAllAcutionItems()
// }
// } catch (error) {
// console.error('Error placing bid:', error);
// }
// };
// useFocusEffect(
// useCallback(() => {
// const onBackPress = () => {
// Alert.alert(
// 'Confirm',
// 'Do you want to go back to Dashboard?',
// [
// { text: 'Cancel', style: 'cancel' },
// {
// text: 'Yes',
// onPress: () => {
// Navigation.dispatch(
// CommonActions.reset({
// index: 0,
// routes: [{ name: NavigationString.Home }],
// })
// );
// },
// },
// ],
// { cancelable: true }
// );
// return true; // block default back action
// };
// const subscription = BackHandler.addEventListener(
// 'hardwareBackPress',
// onBackPress
// );
// return () => subscription.remove();
// }, [Navigation])
// );
// return (
// <View style={styles.container}>
// <InsideHeader title="Bidding Panel" showArrow />
// <View style={styles.container2}>
// <Text style={styles.title}>
// {SelectedAuction.deptName}
// </Text>
// <View style={styles.timerWrapper}>
// <CountdownTimer
// endDate={SelectedAuction.keyDates.auctionBidding.endDateTime}
// myStyle={{ fontSize: 30 }}
// size={30}
// />
// </View>
// </View>
// <FlatList
// data={items}
// keyExtractor={item => item.id.toString()}
// renderItem={({ item, index }) => (
// <BiddingCard
// item={{ ...item, index }}
// bidValue={bidValues[item.id] || 0}
// onIncrease={() => handleUpdate(item.id, item.bidVariationValue)}
// onDecrease={() => handleUpdate(item.id, -item.bidVariationValue)}
// onPlaceBid={() => { submitBid(item) }}
// SelectedAuction={SelectedAuction}
// />
// )}
// contentContainerStyle={{ padding: 16 }}
// />
// {allBoolean.showSuccess && (
// <View style={styles.overlay}>
// <LottieView
// source={require('../../lottie/Success.json')}
// autoPlay
// loop={false}
// style={{ width: 200, height: 200 }}
// />
// </View>
// )}
// </View>
// );
// };
// export default StartBidding;
// const styles = StyleSheet.create({
// container: { flex: 1, backgroundColor: colors.white },
// container2: {
// paddingHorizontal: 16,
// paddingTop: 12,
// paddingBottom: 8,
// },
// title: {
// fontSize: 18,
// fontFamily: FontsFamily.poppinsSemiBold,
// color: colors.black,
// marginBottom: 6,
// // textAlign:'center'
// },
// timerWrapper: {
// alignItems: 'center',
// },
// card: {
// backgroundColor: '#FFF',
// borderRadius: 24,
// padding: 20,
// marginBottom: 16,
// borderWidth: 1,
// borderColor: '#E2E8F0',
// overflow: 'hidden',
// ...Platform.select({ ios: { shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 }, android: { elevation: 2 } }),
// },
// leadingCard: { borderColor: '#22C55E', borderWidth: 2, backgroundColor: '#F0FDF4' },
// cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' },
// headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 },
// productName: { fontSize: 18, fontWeight: 'bold', color: '#1E293B' },
// itemIdText: { fontSize: 12, color: '#64748B', marginTop: 2 },
// badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
// liveBadge: { backgroundColor: '#FEE2E2' },
// meBadge: { backgroundColor: '#BBF7D0' },
// badgeText: { fontSize: 10, fontWeight: '900' },
// liveText: { color: '#EF4444' },
// meText: { color: '#15803D' },
// arrow: { fontSize: 10, color: '#94A3B8' },
// priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
// label: { fontSize: 11, color: '#94A3B8', textTransform: 'uppercase', fontWeight: '600' },
// mainPrice: { fontSize: 22, fontWeight: '900', color: '#0F172A' },
// incrementText: { fontSize: 16, fontWeight: '700', color: '#3B82F6' },
// collapsibleContainer: { overflow: 'hidden' },
// divider: { height: 1, backgroundColor: '#E2E8F0', marginVertical: 10 },
// bidLabel: { fontSize: 13, fontWeight: '600', color: '#475569', marginBottom: 10 },
// stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
// stepBtn: { width: 45, height: 45, backgroundColor: colors.primary, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
// disabledBtn: { backgroundColor: '#CBD5E1' },
// stepText: { color: '#FFF', fontSize: 20, fontWeight: 'bold' },
// inputBox: { flex: 1, height: 45, backgroundColor: '#F1F5F9', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
// currency: { fontSize: 16, fontWeight: 'bold', color: '#64748B', marginRight: 4 },
// bidValueText: { fontSize: 18, fontWeight: '800', color: '#0F172A' },
// submitBtn: { height: 50, backgroundColor: colors.primary, borderRadius: 15, justifyContent: 'center', alignItems: 'center' },
// submitBtnText: { color: '#FFF', fontSize: 16, fontWeight: 'bold' },
// overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(255,255,255,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 99 },
// });