second commit
This commit is contained in:
Generated
+13962
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -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({}));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -8,6 +8,7 @@ const NavigationString = {
|
||||
PurchaseList: 'PurchaseList',
|
||||
Emd: 'Emd',
|
||||
StartBidding: 'StartBidding',
|
||||
PurchaseAuctionList:'PurchaseAuctionList'
|
||||
PurchaseAuctionList:'PurchaseAuctionList',
|
||||
StartReverseBidding:'StartReverseBidding'
|
||||
}
|
||||
export default NavigationString
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) }}>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
@@ -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,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
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
};
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
// });
|
||||
Reference in New Issue
Block a user