Files
ProcureLinc-Mobile-App/src/Screens/Bidding/Component/BiddingCard.tsx
T

301 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { memo, useCallback, useRef, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform, TextInput, Alert } from 'react-native';
import Animated, { FadeInUp, Layout, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
import colors_const from '../../../Constant/Color';
import AuctionItemDetail from '../../Auction/Modal/AuctionItemDetail';
import MainStyle from '../../../Styles/MainStyle';
import { useTheme } from '../../../Hooks/useTheme';
import AppText from '../../../components/AppText';
import { Loader } from '../../../Component/Index';
import { useSelector } from 'react-redux';
import axios from 'axios';
import Variables from '../../../Constant/Variable';
import { showErrorAlert, showSuccessAlert } from '../../../Constant/ShowDailog';
const BiddingCard = ({ item, bidValue, onIncrease, onDecrease, onPlaceBid, SelectedAuction, allBoolean, setAllBoolean }: any) => {
const { colors } = useTheme();
const styles = React.useMemo(() => makeStyles(colors), [colors]);
const Token = useSelector((state: any) => state.token.token)
const [showDetail, setShowDetail] = useState(false);
const [value, setValue] = useState(`${item.bidAmount}`);
const [allState, setAllState] = useState({ isLoading: 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 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`,
},
],
}));
const MAX_VALUE = Number(item.bidAmount);
const handleChange = (text: string) => {
let cleaned = text.replace(/[^0-9.]/g, '');
if ((cleaned.match(/\./g) || []).length > 1) return;
setValue(cleaned);
};
const oneTimePlaceBid = async () => {
try {
if (Number(value) < MAX_VALUE) {
showErrorAlert("Enter the greather vlaue to current bid value")
return false
}
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, item.me ? styles.leadingCard : undefined, { borderColor: item.me ? colors.success : colors.error, backgroundColor: item.me ? `${colors.success}10` : `${colors.error}10` }]}
>
{/* HEADER */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => { setShowDetail(true) }}
style={styles.cardHeader}
>
<View style={{ flex: 1 }}>
<AppText variant="h4" color={colors.text} bold style={styles.productName as any}>{item.productName}</AppText>
{/* auctionStartValueFigure */}
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
Initial Amt : {item?.auctionStartValueFigure}
</AppText>
</View>
<TouchableOpacity style={styles.headerRight} onPress={toggleExpand}>
<View style={[MainStyle.flexBetween, { gap: 10 }]}>
<View style={[styles.badge, item.me ? styles.meBadge : styles.liveBadge]}>
<AppText variant="caption" bold style={[styles.badgeText as any, item.me ? styles.meText : styles.liveText]}>
{item.me ? 'LEADING' : 'LIVE'}
</AppText>
</View>
<Animated.Text style={[styles.arrow, { color: colors.textSecondary }, arrowStyle]}>
▼
</Animated.Text>
</View>
<AppText variant="caption" color={colors.textSecondary} style={styles.itemIdText as any}>
{SelectedAuction?.auctionNumber}
</AppText>
</TouchableOpacity>
</TouchableOpacity>
{/* PRICE INFO */}
<View style={styles.priceRow}>
<View>
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Current Bid</AppText>
<AppText variant="h2" color={colors.text} bold style={styles.mainPrice as any}>₹{item?.bidAmount || item.auctionStartValueFigure}</AppText>
</View>
<View style={{ alignItems: 'flex-end' }}>
<AppText variant="caption" color={colors.textSecondary} semiBold style={styles.label as any}>Increment</AppText>
<AppText variant="body" color={colors.primary} bold style={styles.incrementText as any}>
+₹{item.bidVariationValue}
</AppText>
</View>
</View>
{/* COLLAPSIBLE BODY */}
<Animated.View style={[styles.collapsibleContainer, (!allBoolean.rebid && item.contractorId == undefined) ? bodyStyle : bodyStyle2]}>
<View style={styles.divider} />
<AppText variant="bodySmall" color={colors.textSecondary} semiBold style={styles.bidLabel as any}>Set Your Bid</AppText>
{!allBoolean.oneTimeBid && <View style={styles.stepperContainer}>
<TouchableOpacity
onPress={onDecrease}
disabled={isMin}
style={[styles.stepBtn, isMin && styles.disabledBtn]}
>
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>−</AppText>
</TouchableOpacity>
<View style={styles.inputBox}>
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency as any}>₹</AppText>
<AppText variant="h4" color={colors.text} bold style={styles.bidValueText as any}>{bidValue}</AppText>
</View>
<TouchableOpacity onPress={onIncrease} style={styles.stepBtn}>
<AppText variant="h3" color="#FFF" bold style={styles.stepText as any}>+</AppText>
</TouchableOpacity>
</View>}
{
(allBoolean.oneTimeBid || allBoolean.rebid) && <View style={styles.inputBox2}>
<AppText variant="body" color={colors.textSecondary} bold style={styles.currency2 as any}>₹</AppText>
<TextInput
value={value}
onChangeText={handleChange}
keyboardType="decimal-pad"
placeholder="0.00"
style={[styles.input, { color: colors.text }]}
placeholderTextColor={colors.textSecondary}
/>
</View>
}
{(!allBoolean.rebid && item.contractorId == undefined) ? <TouchableOpacity
style={[styles.submitBtn]}
onPress={(allBoolean.oneTimeBid || allBoolean.rebid) ? oneTimePlaceBid : onPlaceBid}
>
{
allState.isLoading ? <Loader size='small' color="#FFF" /> : <AppText variant="button" color="#FFF" bold style={styles.submitBtnText as any}>Confirm Bid</AppText>
}
</TouchableOpacity> : null}
</Animated.View>
</Animated.View>
:
<AuctionItemDetail data={item} onClose={() => { setShowDetail(false) }} visible={showDetail} />
}
</View>
)
};
export default memo(BiddingCard);
const makeStyles = (colors: any) => StyleSheet.create({
inputBox2: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background,
borderRadius: 12,
height: 45,
paddingHorizontal: 12,
width: '100%',
marginBottom: 20
},
currency2: {
marginRight: 4,
} as any,
input: {
flex: 1,
fontSize: 18,
fontWeight: '700',
width: '100%'
},
card: {
borderRadius: 24,
padding: 20,
marginBottom: 16,
borderWidth: 1,
overflow: 'hidden',
},
leadingCard: {
borderWidth: 2,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
},
headerRight: { flexDirection: 'column', alignItems: 'center', gap: 8 },
productName: {} as any,
itemIdText: { marginTop: 2 } as any,
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
liveBadge: { backgroundColor: `${colors.error}20` },
meBadge: { backgroundColor: `${colors.success}20` },
badgeText: { fontSize: 10 } as any,
liveText: { color: colors.error },
meText: { color: colors.success },
arrow: { fontSize: 10 },
priceRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
label: {
textTransform: 'uppercase',
} as any,
mainPrice: {} as any,
incrementText: {} as any,
collapsibleContainer: { overflow: 'hidden' },
divider: { height: 1, backgroundColor: colors.border, marginVertical: 10 },
bidLabel: { marginBottom: 10 } as any,
stepperContainer: { flexDirection: 'row', gap: 12, marginBottom: 20 },
stepBtn: {
width: 45,
height: 45,
backgroundColor: colors.primary,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
},
disabledBtn: { backgroundColor: colors.border },
stepText: {} as any,
inputBox: {
flex: 1,
height: 45,
backgroundColor: colors.background,
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
currency: { marginRight: 4 } as any,
bidValueText: {} as any,
submitBtn: {
height: 50,
backgroundColor: colors.primary,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
},
submitBtnText: {} as any,
});