Merge branch 'etender' of https://git.zeptic.in/rohit/Procurelinc
This commit is contained in:
@@ -9,6 +9,12 @@ const NavigationString = {
|
||||
Emd: 'Emd',
|
||||
StartBidding: 'StartBidding',
|
||||
PurchaseAuctionList:'PurchaseAuctionList',
|
||||
StartReverseBidding:'StartReverseBidding'
|
||||
StartReverseBidding:'StartReverseBidding',
|
||||
// tender
|
||||
PurchaseTenderList:'PurchaseTenderList',
|
||||
ProceedToPurTender:'ProceedToPurTender',
|
||||
TenderBidding:'TenderBidding',
|
||||
ProceedToBidDetail:'ProceedToBidDetail',
|
||||
LiveTenderDetail:'LiveTenderDetail',
|
||||
}
|
||||
export default NavigationString
|
||||
@@ -36,6 +36,11 @@ 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.PurchaseTenderList} component={Screens.PurchaseTenderList} options={option} />
|
||||
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} />
|
||||
<Stack.Screen name={NavigationString.TenderBidding} component={Screens.TenderBidding} options={option} />
|
||||
<Stack.Screen name={NavigationString.ProceedToBidDetail} component={Screens.ProceedToBidDetail} options={option} />
|
||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
||||
<Stack.Screen name={NavigationString.StartReverseBidding} component={Screens.StartReverseBidding} options={option} />
|
||||
|
||||
</Stack.Navigator>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface TenderItem {
|
||||
TenderNumber: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ✅ DEFAULT = EMPTY ARRAY
|
||||
const initialState: TenderItem[] = [];
|
||||
|
||||
export const selectPurchaseTenderSlice = createSlice({
|
||||
name: 'selectPurchaseTender',
|
||||
initialState,
|
||||
reducers: {
|
||||
togglePurchaseTender: (
|
||||
state,
|
||||
action: PayloadAction<TenderItem>
|
||||
) => {
|
||||
const index = state.findIndex(
|
||||
item => item.tenderId === action.payload.tenderId
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
state.splice(index, 1);
|
||||
} else {
|
||||
state.push(action.payload);
|
||||
}
|
||||
},
|
||||
|
||||
clearPurchaseTender: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
togglePurchaseTender,
|
||||
clearPurchaseTender,
|
||||
} = selectPurchaseTenderSlice.actions;
|
||||
|
||||
export default selectPurchaseTenderSlice.reducer;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
tender: '',
|
||||
};
|
||||
|
||||
export const tenderSlice = createSlice({
|
||||
name: 'tender',
|
||||
initialState,
|
||||
reducers: {
|
||||
setTender: (state, { payload }) => {
|
||||
state.tender = payload;
|
||||
},
|
||||
removeTender: (state) => {
|
||||
state.tender = '';
|
||||
},
|
||||
setTenderDetails: (state, { payload }) => {
|
||||
state.tender = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
export const { setTender , removeTender, setTenderDetails } = tenderSlice.actions;
|
||||
export default tenderSlice.reducer;
|
||||
@@ -5,11 +5,15 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {tokenSlice} from './Slices/Token';
|
||||
import {auctionSlice} from './Slices/SelectedAuction';
|
||||
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
|
||||
import selectPurchaseTenderReducer from './Slices/SelectedPurchaseTender';
|
||||
import { tenderSlice } from './Slices/SelectedTender';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
token: tokenSlice.reducer,
|
||||
auction: auctionSlice.reducer,
|
||||
selectPurchaseAuction: selectPurchaseAuctionReducer,
|
||||
selectPurchaseTender: selectPurchaseTenderReducer,
|
||||
tender: tenderSlice.reducer,
|
||||
});
|
||||
const persistConfig = {
|
||||
key: 'root',
|
||||
|
||||
@@ -93,14 +93,14 @@ const Emd = () => {
|
||||
<Text style={styles.subText}>EMD ₹ {item.emdAmount}</Text>
|
||||
</View>
|
||||
|
||||
<BouncyCheckbox
|
||||
{/* <BouncyCheckbox
|
||||
size={20}
|
||||
fillColor={colors.primary}
|
||||
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
|
||||
innerIconStyle={{ borderRadius: 6 }}
|
||||
isChecked={selected}
|
||||
onPress={(checked) => toggleSelect(item, checked)}
|
||||
/>
|
||||
/> */}
|
||||
</View>
|
||||
|
||||
<Text style={styles.meta}>
|
||||
|
||||
@@ -38,6 +38,7 @@ const Login = () => {
|
||||
mutate({ password: values.password, emailAdd: values.email }, {
|
||||
onSuccess: (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
console.log("login token : ", res)
|
||||
Dispatch(setToken({ ...res.data, token: res.token }))
|
||||
Navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native';
|
||||
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useFetchLiveAuction, useFetchLiveTender } from '../../Services/BBPS/Hooks';
|
||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||
import { Skelton } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { togglePurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender';
|
||||
import LiveTenderTile from './LiveTenderTile';
|
||||
|
||||
const LiveTender = () => {
|
||||
const navigation: any = useNavigation();
|
||||
const Dispatch = useDispatch()
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender();
|
||||
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRefetching) {
|
||||
setShowRefreshBanner(true);
|
||||
} else {
|
||||
const timer = setTimeout(() => {
|
||||
setShowRefreshBanner(false);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isRefetching]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage]);
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
if (!isFetchingNextPage) return null;
|
||||
return (
|
||||
<View style={{ paddingVertical: 12 }}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}, [isFetchingNextPage]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: any) => {
|
||||
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
|
||||
return <LiveTenderTile
|
||||
data={item}
|
||||
index={index}
|
||||
tenderId={item.tenderId}
|
||||
tenderDispatchDate={item.purchaseStart}
|
||||
isPurchased={isPurchase !== -1 ? true : false}
|
||||
purchaseEnd={item.purchaseEnd}
|
||||
tenderOfWork={item.departmentName}
|
||||
tenderFees={item.documentFees}
|
||||
tenderNumber={item.tenderNumber}
|
||||
// onPurchasePress={() => { Dispatch(togglePurchaseTender(item)) }}
|
||||
// onDetailsPress={() => navigation.navigate('LiveTenderDetail', {item})}
|
||||
/>
|
||||
},
|
||||
[SelectedPurchaseList]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Skelton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchInput onChange={() => { }} placeholder="Search...." />
|
||||
|
||||
{showRefreshBanner && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(200)}
|
||||
exiting={FadeOutUp.duration(500)}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 14,
|
||||
marginTop: 6,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#FFFFFF',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 8,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
elevation: 4,
|
||||
zIndex: 10, // Ensure it stays on top
|
||||
position: 'absolute',
|
||||
top: 60, // Adjust based on your SearchInput height
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: '#374151',
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
Refreshing auctions
|
||||
</Text>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
<FlatList
|
||||
data={data?.data ?? []}
|
||||
keyExtractor={(item) => String(item.auctionId)}
|
||||
renderItem={renderItem}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0.2}
|
||||
ListFooterComponent={renderFooter}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
tintColor="transparent" // Set to transparent if you only want your custom banner to show
|
||||
colors={[colors.primary]}
|
||||
/>
|
||||
}
|
||||
initialNumToRender={6}
|
||||
maxToRenderPerBatch={6}
|
||||
windowSize={7}
|
||||
removeClippedSubviews
|
||||
updateCellsBatchingPeriod={50}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveTender;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import React from 'react'
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
|
||||
const LiveTenderDetail = () => {
|
||||
const route = useRoute<any>();
|
||||
const { tender } = route.params;
|
||||
console.log("live tender details : ", tender)
|
||||
return (
|
||||
<View>
|
||||
<Text>LiveTenderDetail</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default LiveTenderDetail
|
||||
|
||||
const styles = StyleSheet.create({})
|
||||
@@ -0,0 +1,246 @@
|
||||
import React, { memo } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import moment from 'moment';
|
||||
import colors from '../../Constant/Color';
|
||||
import { LiveTenderTileProps } from './types/types';
|
||||
import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native';
|
||||
import TenderDetail from './Modal/TenderFullDetail';
|
||||
import TenderFullDetail from './Modal/TenderFullDetail';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
|
||||
data,
|
||||
isPurchased = false,
|
||||
onPurchasePress,
|
||||
tenderDispatchDate,
|
||||
tenderOfWork,
|
||||
purchaseEnd,
|
||||
tenderFees,
|
||||
tenderNumber,
|
||||
onDetailsPress
|
||||
}) => {
|
||||
const navigation: any = useNavigation();
|
||||
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
|
||||
|
||||
console.log("live tender data : ", data)
|
||||
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={() => navigation.navigate('LiveTenderDetail', data)}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.badge}>
|
||||
<Tag size={14} color={colors.primary} />
|
||||
<Text style={styles.badgeText}>#{tenderNumber}</Text>
|
||||
</View>
|
||||
<Text style={styles.priceText}>₹{tenderFees}</Text>
|
||||
</View>
|
||||
|
||||
{/* Work Title */}
|
||||
<Text style={styles.workTitle} numberOfLines={2}>
|
||||
{tenderOfWork ?? 'General Tender 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}>{tenderDispatchDate ?? '-'}</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={onDetailsPress}>
|
||||
<Info size={18} color={colors.primary} />
|
||||
<Text style={styles.iconBtnText}>Details</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.mainButton,
|
||||
purchaseEnded
|
||||
? styles.btnDisabled
|
||||
: isPurchased
|
||||
? styles.btnPurchased
|
||||
: styles.btnActive,
|
||||
]}
|
||||
onPress={onPurchasePress}
|
||||
disabled={purchaseEnded}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.mainButtonText,
|
||||
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
|
||||
]}
|
||||
>
|
||||
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
|
||||
</Text>
|
||||
{!purchaseEnded && (
|
||||
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Tender Detail Modal */}
|
||||
{/* <TenderFullDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} /> */}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LiveTenderTile);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
cardWrapper: {
|
||||
marginVertical: 10,
|
||||
marginHorizontal: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
|
||||
// Modern shadow
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 20,
|
||||
elevation: 8,
|
||||
},
|
||||
statusTag: {
|
||||
width: 6,
|
||||
height: '100%',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.primary + '25',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 12,
|
||||
gap: 6,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: colors.primary,
|
||||
},
|
||||
priceText: {
|
||||
fontSize: 20,
|
||||
fontWeight: '800',
|
||||
color: '#0f172a',
|
||||
},
|
||||
workTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#334155',
|
||||
lineHeight: 24,
|
||||
marginBottom: 16,
|
||||
},
|
||||
infoGrid: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#f0f4f8',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 16,
|
||||
marginBottom: 18,
|
||||
},
|
||||
infoItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
infoTextGroup: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 10,
|
||||
color: '#94a3b8',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '700',
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
color: '#1e293b',
|
||||
},
|
||||
actionBar: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
iconBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#e2e8f0',
|
||||
},
|
||||
iconBtnText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: colors.primary,
|
||||
},
|
||||
mainButton: {
|
||||
flex: 1,
|
||||
height: 46,
|
||||
borderRadius: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
btnActive: {
|
||||
backgroundColor: '#4ade80', // light green
|
||||
},
|
||||
btnPurchased: {
|
||||
backgroundColor: colors.error,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.error,
|
||||
},
|
||||
btnDisabled: {
|
||||
backgroundColor: '#e2e8f0',
|
||||
},
|
||||
mainButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,259 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Pressable,
|
||||
} from 'react-native';
|
||||
import DatePicker from 'react-native-date-picker';
|
||||
import moment from 'moment';
|
||||
import colors from '../../../Constant/Color';
|
||||
import FontsFamily from '../../../Constant/FontsFamily';
|
||||
|
||||
type AuctionStatus = 'RUNNING' | 'ENDED' | 'NOT_STARTED' | null;
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onApply: (filters: {
|
||||
fromDate: Date | null;
|
||||
toDate: Date | null;
|
||||
status: AuctionStatus;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const FilterModal: React.FC<Props> = ({ visible, onClose, onApply }) => {
|
||||
const [fromDate, setFromDate] = useState<Date | null>(null);
|
||||
const [toDate, setToDate] = useState<Date | null>(null);
|
||||
const [status, setStatus] = useState<AuctionStatus>(null);
|
||||
|
||||
const [openPicker, setOpenPicker] = useState(false);
|
||||
const [pickerType, setPickerType] = useState<'FROM' | 'TO'>('FROM');
|
||||
|
||||
const openDatePicker = (type: 'FROM' | 'TO') => {
|
||||
setPickerType(type);
|
||||
setOpenPicker(true);
|
||||
};
|
||||
|
||||
const handleConfirm = (date: Date) => {
|
||||
if (pickerType === 'FROM') {
|
||||
setFromDate(date);
|
||||
} else {
|
||||
setToDate(date);
|
||||
}
|
||||
setOpenPicker(false);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onApply({ fromDate, toDate, status });
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={visible}
|
||||
animationType="slide"
|
||||
transparent
|
||||
statusBarTranslucent
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
{/* Overlay */}
|
||||
<Pressable style={styles.overlay} onPress={onClose} />
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Filter Auctions</Text>
|
||||
|
||||
{/* ===== DATE RANGE ===== */}
|
||||
<Text style={styles.sectionTitle}>Date Range</Text>
|
||||
|
||||
<View style={styles.dateRow}>
|
||||
<TouchableOpacity
|
||||
style={styles.dateBox}
|
||||
onPress={() => openDatePicker('FROM')}
|
||||
>
|
||||
<Text style={styles.dateLabel}>From</Text>
|
||||
<Text style={styles.dateValue}>
|
||||
{fromDate
|
||||
? moment(fromDate).format('DD MMM YYYY')
|
||||
: 'Select date'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.dateBox}
|
||||
onPress={() => openDatePicker('TO')}
|
||||
>
|
||||
<Text style={styles.dateLabel}>To</Text>
|
||||
<Text style={styles.dateValue}>
|
||||
{toDate
|
||||
? moment(toDate).format('DD MMM YYYY')
|
||||
: 'Select date'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* ===== STATUS ===== */}
|
||||
<Text style={styles.sectionTitle}>Auction Status</Text>
|
||||
|
||||
<View style={styles.statusRow}>
|
||||
{[
|
||||
{ label: 'Running', value: 'RUNNING' },
|
||||
{ label: 'Ended', value: 'ENDED' },
|
||||
{ label: 'Not Started', value: 'NOT_STARTED' },
|
||||
].map(item => {
|
||||
const active = status === item.value;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.value}
|
||||
onPress={() => setStatus(item.value as AuctionStatus)}
|
||||
style={[
|
||||
styles.statusPill,
|
||||
active && styles.statusActive,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.statusText,
|
||||
active && styles.statusActiveText,
|
||||
]}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* ===== FOOTER ===== */}
|
||||
<View style={styles.footer}>
|
||||
{/* <TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
|
||||
<Text style={styles.cancelText}>Cancel</Text>
|
||||
</TouchableOpacity> */}
|
||||
|
||||
<TouchableOpacity style={styles.applyBtn} onPress={handleApply}>
|
||||
<Text style={styles.applyText}>Apply</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* ===== DATE PICKER ===== */}
|
||||
<DatePicker
|
||||
modal
|
||||
open={openPicker}
|
||||
date={
|
||||
pickerType === 'FROM'
|
||||
? fromDate || new Date()
|
||||
: toDate || new Date()
|
||||
}
|
||||
mode="date"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={() => setOpenPicker(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterModal;
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
},
|
||||
container: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
backgroundColor: '#fff',
|
||||
borderTopLeftRadius: 22,
|
||||
borderTopRightRadius: 22,
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
marginBottom: 10,
|
||||
color: '#475569',
|
||||
},
|
||||
dateRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 20,
|
||||
},
|
||||
dateBox: {
|
||||
width: '48%',
|
||||
padding: 14,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#F8FAFC',
|
||||
borderWidth: 1,
|
||||
borderColor: '#E2E8F0',
|
||||
},
|
||||
dateLabel: {
|
||||
fontSize: 11,
|
||||
color: '#64748B',
|
||||
marginBottom: 4,
|
||||
},
|
||||
dateValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#0F172A',
|
||||
},
|
||||
statusRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 10,
|
||||
},
|
||||
statusPill: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: '#CBD5E1',
|
||||
backgroundColor: '#F8FAFC',
|
||||
},
|
||||
statusActive: {
|
||||
backgroundColor: colors.primary,
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 13,
|
||||
color: '#334155',
|
||||
fontFamily:FontsFamily.poppinsSemiBold
|
||||
},
|
||||
statusActiveText: {
|
||||
color: '#fff',
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 24,
|
||||
},
|
||||
cancelBtn: {
|
||||
padding: 12,
|
||||
},
|
||||
|
||||
applyBtn: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
width: '100%'
|
||||
},
|
||||
applyText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
fontFamily: FontsFamily.poppinsSemiBold
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,370 @@
|
||||
import React, { memo, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
Platform,
|
||||
StatusBar,
|
||||
} from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
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 { useFetchTenderById } from '../../../Services/BBPS/Hooks';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const TenderFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||
|
||||
|
||||
const inset = useSafeAreaInsets();
|
||||
const { data: tenderResponse }: any = useFetchTenderById(data?.tenderId);
|
||||
const tender = tenderResponse?.data?.[0];
|
||||
|
||||
const formatDate = (date?: string) => {
|
||||
if (!date) return '-';
|
||||
return moment(date, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A');
|
||||
};
|
||||
|
||||
if (!tender) return null;
|
||||
|
||||
const isLive = tender?.liveStatus === true;
|
||||
|
||||
const Section = ({ title, icon: Icon, children }: any) => (
|
||||
<View style={styles.sectionContainer}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Icon size={20} color={colors.primary} />
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
</View>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
|
||||
const InfoBox = ({ label, value, full = false }: any) => (
|
||||
<View style={[styles.infoBox, full && { width: '100%' }]}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.value}>{value || '-'}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isVisible={visible}
|
||||
style={styles.fullModal}
|
||||
animationIn="slideInRight"
|
||||
animationOut="slideOutRight"
|
||||
useNativeDriver
|
||||
statusBarTranslucent
|
||||
>
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<StatusBar barStyle="dark-content" />
|
||||
|
||||
{/* Header */}
|
||||
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||
<ArrowLeft size={24} color="#1e293b" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.headerTitle}>Tender Overview</Text>
|
||||
<Text style={styles.headerSub}>Ref: {tender?.tenderNumber}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.liveBox}>
|
||||
<View
|
||||
style={[
|
||||
styles.dot,
|
||||
{ backgroundColor: isLive ? '#22c55e' : '#94a3b8' },
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.liveText}>{isLive ? 'LIVE' : 'CLOSED'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
|
||||
{/* Basic Details */}
|
||||
<Section title="Basic Details" icon={Layers}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Tender No" value={tender?.tenderNumber} />
|
||||
<InfoBox label="Type" value={tender?.typeOfTender} />
|
||||
<InfoBox label="Category" value={tender?.tenderCategory} />
|
||||
<InfoBox label="Mode" value={tender?.selectiveTender} />
|
||||
<InfoBox label="Tender Status" value={tender?.tenderStatus} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Work & Estimate */}
|
||||
<Section title="Work & Estimate" icon={Briefcase}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Name of Work" value={tender?.nameOfWork} full />
|
||||
<InfoBox label="Area of Work" value={tender?.areaOfWork} />
|
||||
<InfoBox label="Estimate (Fig)" value={tender?.estimateValueInFig} />
|
||||
<InfoBox label="Estimate (Words)" value={tender?.estimateValueInWord} />
|
||||
<InfoBox
|
||||
label="Completion Period"
|
||||
value={`${tender?.workCompletionPeriod} ${tender?.workCompletionType}`}
|
||||
/>
|
||||
<InfoBox label="Rainy Season" value={tender?.rainySeason} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Department & Location */}
|
||||
<Section title="Department & Location" icon={Layers}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Department" value={tender?.deptName} full />
|
||||
<InfoBox label="Circle" value={tender?.circle} />
|
||||
<InfoBox label="Division" value={tender?.tenderSelectedDivision} />
|
||||
<InfoBox label="Sub Division" value={tender?.tenderSelectedSubDivision} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Fee & Charges */}
|
||||
<Section title="Fee & Charges" icon={FileText}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Tender Fee" value={tender?.tenderFeeInFig} />
|
||||
<InfoBox label="EMD" value={tender?.emdInFig} />
|
||||
<InfoBox label="Portal Charge" value={tender?.portalCharge} />
|
||||
<InfoBox label="GST (%)" value={tender?.gst} />
|
||||
<InfoBox label="GST Amount" value={tender?.gstAmmount} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Bid Info */}
|
||||
<Section title="Bid Information" icon={Eye}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox label="Envelope Type" value={tender?.envelopeType} />
|
||||
<InfoBox label="Contractor Class" value={tender?.contractorClass} />
|
||||
<InfoBox label="Bid Validation" value={`${tender?.bidValidatePeriod} ${tender?.bidValidateType}`} />
|
||||
<InfoBox label="Re-Bid Allowed" value={tender?.tenderRebid} />
|
||||
<InfoBox label="Bid Withdraw" value={tender?.tenderBidWithdraw} />
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Key Dates */}
|
||||
<Section title="Key Dates" icon={Calendar}>
|
||||
<View style={styles.grid}>
|
||||
<InfoBox
|
||||
label="Purchase Start"
|
||||
value={formatDate(
|
||||
tender?.tenderKeyDatesAmendment?.tenderPurchaseDate?.startDateTime
|
||||
)}
|
||||
full
|
||||
/>
|
||||
<InfoBox
|
||||
label="Purchase End"
|
||||
value={formatDate(
|
||||
tender?.tenderKeyDatesAmendment?.tenderPurchaseDate?.endDateTime
|
||||
)}
|
||||
full
|
||||
/>
|
||||
<InfoBox
|
||||
label="Bid Submission Start"
|
||||
value={formatDate(
|
||||
tender?.tenderKeyDatesAmendment?.bidSubmissionDate?.startDateTime
|
||||
)}
|
||||
full
|
||||
/>
|
||||
<InfoBox
|
||||
label="Bid Submission End"
|
||||
value={formatDate(
|
||||
tender?.tenderKeyDatesAmendment?.bidSubmissionDate?.endDateTime
|
||||
)}
|
||||
full
|
||||
/>
|
||||
</View>
|
||||
</Section>
|
||||
|
||||
{/* Documents */}
|
||||
<Section title="Documents" icon={Download}>
|
||||
{tender?.tenderDocuments?.map((doc: any) => (
|
||||
<View key={doc.docId} style={styles.fileRow}>
|
||||
<FileText size={18} color={colors.primary} />
|
||||
<Text style={styles.fileName}>{doc.fileName}</Text>
|
||||
<Download size={18} color="#94a3b8" />
|
||||
</View>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fullModal: {
|
||||
margin: 0,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f1f5f9',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
backBtn: {
|
||||
padding: 8,
|
||||
marginRight: 8,
|
||||
},
|
||||
headerTitleContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
headerTitleText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: '#0f172a',
|
||||
},
|
||||
headerIdText: {
|
||||
fontSize: 13,
|
||||
color: '#64748b',
|
||||
},
|
||||
liveIndicator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f8fafc',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 20,
|
||||
gap: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e2e8f0',
|
||||
},
|
||||
dot: { width: 8, height: 8, borderRadius: 4 },
|
||||
liveText: { fontSize: 11, fontWeight: '800', color: '#475569' },
|
||||
|
||||
scrollView: { flex: 1, backgroundColor: '#f8fafc' },
|
||||
scrollContent: { padding: 16, paddingBottom: 100 },
|
||||
|
||||
sectionContainer: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#eef2f6',
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
marginBottom: 20,
|
||||
paddingBottom: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f8fafc',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#1e293b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
grid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
infoBox: { marginBottom: 16 },
|
||||
label: { fontSize: 12, color: '#94a3b8', fontWeight: '600', marginBottom: 4 },
|
||||
value: { fontSize: 15, fontWeight: '700', color: '#334155' },
|
||||
spacer: { height: 12 },
|
||||
|
||||
timelineContainer: { paddingLeft: 8 },
|
||||
timelineRow: { flexDirection: 'row', alignItems: 'center', gap: 15 },
|
||||
iconCircle: { width: 34, height: 34, borderRadius: 10, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' },
|
||||
timelineConnector: { width: 2, height: 20, backgroundColor: '#f1f5f9', marginLeft: 16 },
|
||||
|
||||
tableWrapper: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 12 },
|
||||
tableHead: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 12 },
|
||||
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
||||
tableRow: { flexDirection: 'row', alignItems: 'center' },
|
||||
itemMain: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
||||
itemSub: { fontSize: 12, color: '#64748b' },
|
||||
itemQty: { flex: 1, fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||
actionIcon: { padding: 6 },
|
||||
|
||||
fileCard: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
||||
fileIcon: { width: 40, height: 40, borderRadius: 8, backgroundColor: '#fee2e2', alignItems: 'center', justifyContent: 'center' },
|
||||
fileName: { fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||
fileSize: { fontSize: 12, color: '#94a3b8' },
|
||||
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#f1f5f9',
|
||||
},
|
||||
mainBtn: {
|
||||
backgroundColor: colors.primary,
|
||||
height: 56,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: colors.primary,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 8,
|
||||
elevation: 4,
|
||||
},
|
||||
btnDisabled: { backgroundColor: '#cbd5e1' },
|
||||
mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' },
|
||||
fileRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
paddingVertical: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f1f5f9',
|
||||
width: '80%',
|
||||
},
|
||||
liveBox: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e2e8f0',
|
||||
},
|
||||
headerSub: {
|
||||
fontSize: 13,
|
||||
color: '#64748b',
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: '#0f172a',
|
||||
},
|
||||
});
|
||||
|
||||
export default memo(TenderFullDetail);
|
||||
@@ -0,0 +1,553 @@
|
||||
import { StyleSheet, Text, View, ScrollView, StatusBar, TouchableOpacity } from 'react-native'
|
||||
import React from 'react'
|
||||
import { InsideHeader } from '../../Component/Index'
|
||||
import colors from '../../Constant/Color'
|
||||
import { useNavigation, useRoute } from '@react-navigation/native'
|
||||
import { useProceedToBid } from '../../Services/BBPS/Hooks'
|
||||
|
||||
const ProceedToBidDetail = () => {
|
||||
// Sample data based on your JSON
|
||||
const route = useRoute()
|
||||
const tenderData = route.params?.tender;
|
||||
const {data, refetch} = useProceedToBid(tenderData?.tenderId);
|
||||
|
||||
const navigation = useNavigation<any>()
|
||||
// Format date from array
|
||||
|
||||
const handleStartBidding = () => {
|
||||
refetch();
|
||||
if (data) {
|
||||
navigation.navigate('TenderBidding', { tenderData: tenderData , bidData: data.data });
|
||||
}
|
||||
}
|
||||
const formatDate = (dateArray: any[]) => {
|
||||
if (!dateArray || dateArray.length < 3) return "N/A";
|
||||
const [year, month, day, hour = 0, minute = 0] = dateArray;
|
||||
return `${day.toString().padStart(2, '0')}/${month.toString().padStart(2, '0')}/${year} ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// Status indicator component
|
||||
const StatusIndicator = ({ status, label }) => (
|
||||
<View style={styles.statusContainer}>
|
||||
<View style={[styles.statusDot, { backgroundColor: status ? colors.success : colors.error }]} />
|
||||
<Text style={[styles.statusText, { color: status ? colors.success : colors.error }]}>
|
||||
{label}: {status ? "Submitted" : "Not Submitted"}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
// Envelope log card component
|
||||
const EnvelopeLogCard = ({ log, title }) => (
|
||||
<View style={styles.envelopeCard}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.cardTitle}>{title}</Text>
|
||||
<View style={[styles.envelopeBadge, { backgroundColor: colors.primary }]}>
|
||||
<Text style={styles.envelopeBadgeText}>{log?.envelope}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>Activity:</Text>
|
||||
<Text style={styles.value}>{log?.activity}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>Date & Time:</Text>
|
||||
<Text style={styles.value}>{log?.dateTime}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>System Info:</Text>
|
||||
<Text style={styles.value}>{log?.osName} / {log?.browserName}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>IP Address:</Text>
|
||||
<Text style={styles.value}>{log?.ipAddress}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>Status Message:</Text>
|
||||
<Text style={[styles.value, styles.successText]}>{log?.logMessage}</Text>
|
||||
</View>
|
||||
|
||||
{log?.headingMsg && (
|
||||
<View style={styles.logDetailRow}>
|
||||
<Text style={styles.label}>Heading:</Text>
|
||||
<Text style={styles.value}>{log?.headingMsg}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
// Information row component
|
||||
const InfoRow = ({ label, value, isHighlighted = false }) => (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>{label}:</Text>
|
||||
<Text style={[styles.infoValue, isHighlighted && styles.highlightedValue]}>{value}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
// Feature chip component
|
||||
const FeatureChip = ({ label, enabled }) => (
|
||||
<View style={[styles.chip, { backgroundColor: enabled ? colors.successLight : colors.lightGray }]}>
|
||||
<Text style={[styles.chipText, { color: enabled ? colors.success : colors.darkGray }]}>
|
||||
{label} {enabled ? '✓' : '✗'}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<StatusBar backgroundColor={colors.primary} barStyle="light-content" />
|
||||
<InsideHeader title="Tender Detail" showArrow />
|
||||
|
||||
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
|
||||
{/* Tender Header Section */}
|
||||
<View style={styles.headerCard}>
|
||||
<View style={styles.tenderHeader}>
|
||||
<View>
|
||||
<Text style={styles.tenderNumber}>{tenderData.tenderNumber}</Text>
|
||||
<Text style={styles.tenderId}>ID: {tenderData.tenderId}</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: tenderData.biddingStatus ? colors.success : colors.warning }]}>
|
||||
<Text style={styles.statusBadgeText}>
|
||||
{tenderData.biddingStatus ? "Active" : "Inactive"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.workDescription}>{tenderData.nameofWork}</Text>
|
||||
|
||||
<View style={styles.headerInfoRow}>
|
||||
<View style={styles.headerInfoItem}>
|
||||
<Text style={styles.headerInfoLabel}>NIT No.</Text>
|
||||
<Text style={styles.headerInfoValue}>{tenderData.nitNo}</Text>
|
||||
</View>
|
||||
<View style={styles.separator} />
|
||||
<View style={styles.headerInfoItem}>
|
||||
<Text style={styles.headerInfoLabel}>EMD Amount</Text>
|
||||
<Text style={styles.headerInfoValue}>₹{tenderData.emdInFig}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Envelope Submission Status */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Envelope Submission Status</Text>
|
||||
<View style={styles.statusGrid}>
|
||||
<StatusIndicator status={tenderData.envelopeASubmittStatus} label="Envelope A" />
|
||||
<StatusIndicator status={tenderData.envelopeBSubmittStatus} label="Envelope B" />
|
||||
<StatusIndicator status={tenderData.envelopeCSubmittStatus} label="Envelope C" />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Envelope Logs */}
|
||||
{tenderData.envelopeA_Logs && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Submission Logs</Text>
|
||||
<EnvelopeLogCard log={tenderData.envelopeA_Logs} title="Envelope A Submission" />
|
||||
<EnvelopeLogCard log={tenderData.envelopeB_Logs} title="Envelope B Submission" />
|
||||
<EnvelopeLogCard log={tenderData.envelopeC_Logs} title="Envelope C Submission" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Tender Details */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Tender Details</Text>
|
||||
<View style={styles.detailsCard}>
|
||||
<InfoRow label="Envelope Type" value={tenderData.envelopeType} />
|
||||
<InfoRow label="Tender Stages" value={tenderData.tenderStages} />
|
||||
<InfoRow label="Bid Withdrawal" value={tenderData.bidWithDraw} isHighlighted={tenderData.bidWithdrawStatus} />
|
||||
<InfoRow label="Re-bid" value={tenderData.reBid} />
|
||||
<InfoRow label="DSC Status" value={tenderData.dscStatus ? "Required" : "Not Required"} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Time Period */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Submission Time Period</Text>
|
||||
<View style={styles.timeCard}>
|
||||
<View style={styles.timeRow}>
|
||||
<Text style={styles.timeLabel}>Starts On:</Text>
|
||||
<Text style={styles.timeValue}>{formatDate(tenderData.submittedStartDate)}</Text>
|
||||
</View>
|
||||
<View style={styles.timeRow}>
|
||||
<Text style={styles.timeLabel}>Ends On:</Text>
|
||||
<Text style={styles.timeValue}>{formatDate(tenderData.submittedEndDate)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Feature Flags */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Available Features</Text>
|
||||
<View style={styles.chipsContainer}>
|
||||
<FeatureChip label="View Bid" enabled={tenderData.viewBid} />
|
||||
<FeatureChip label="Proceed to Bid" enabled={tenderData.proceedtoBid} />
|
||||
<FeatureChip label="Show Envelope B" enabled={tenderData.showEB} />
|
||||
<FeatureChip label="Show Envelope C" enabled={tenderData.showEC} />
|
||||
<FeatureChip label="QCBS" enabled={tenderData.showQCBS} />
|
||||
<FeatureChip label="LCS" enabled={tenderData.showLCS} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Final Status */}
|
||||
<View style={styles.finalStatusCard}>
|
||||
<Text style={styles.finalStatusTitle}>Overall Status</Text>
|
||||
<View style={styles.finalStatusRow}>
|
||||
<Text style={styles.finalStatusLabel}>All Envelopes Submitted:</Text>
|
||||
<Text style={[styles.finalStatusValue,
|
||||
{
|
||||
color: tenderData.envelopeASubmittStatus && tenderData.envelopeBSubmittStatus && tenderData.envelopeCSubmittStatus
|
||||
? colors.success : colors.error
|
||||
}]}>
|
||||
{tenderData.envelopeASubmittStatus && tenderData.envelopeBSubmittStatus && tenderData.envelopeCSubmittStatus
|
||||
? "COMPLETED" : "PENDING"}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.finalStatusRow}>
|
||||
<Text style={styles.finalStatusLabel}>Bidding Status:</Text>
|
||||
<Text style={[styles.finalStatusValue,
|
||||
{ color: tenderData.biddingStatus ? colors.success : colors.error }]}>
|
||||
{tenderData.biddingStatus ? "ACTIVE" : "CLOSED"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.footerSpace} />
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
<View style={styles.footer}>
|
||||
{
|
||||
tenderData?.proceedtoBid && !tenderData?.viewBid && tenderData?.biddingStatus && tenderData?.bidWithdrawStatus === false ? (
|
||||
|
||||
<TouchableOpacity style={styles.proceedButton} onPress={handleStartBidding}>
|
||||
<Text style={styles.proceedButtonText}>Start Bidding</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
) : <Text style={styles.footerText}>One time bidding is completed</Text>
|
||||
} </View>
|
||||
</View>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ProceedToBidDetail
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
marginBottom: 60,
|
||||
},
|
||||
headerCard: {
|
||||
backgroundColor: colors.primaryLight,
|
||||
borderRadius: 12,
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: colors.primary,
|
||||
},
|
||||
tenderHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
tenderNumber: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
color: colors.primary,
|
||||
},
|
||||
tenderId: {
|
||||
fontSize: 14,
|
||||
color: colors.darkGray,
|
||||
marginTop: 4,
|
||||
},
|
||||
statusBadge: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 20,
|
||||
},
|
||||
statusBadgeText: {
|
||||
color: colors.white,
|
||||
fontWeight: '600',
|
||||
fontSize: 12,
|
||||
},
|
||||
workDescription: {
|
||||
fontSize: 16,
|
||||
color: colors.darkText,
|
||||
lineHeight: 22,
|
||||
marginBottom: 16,
|
||||
},
|
||||
headerInfoRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
},
|
||||
headerInfoItem: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerInfoLabel: {
|
||||
fontSize: 12,
|
||||
color: colors.darkGray,
|
||||
marginBottom: 4,
|
||||
},
|
||||
headerInfoValue: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: colors.primary,
|
||||
},
|
||||
separator: {
|
||||
width: 1,
|
||||
height: 30,
|
||||
backgroundColor: colors.lightGray,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.primary,
|
||||
marginBottom: 12,
|
||||
paddingLeft: 4,
|
||||
},
|
||||
statusGrid: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
statusContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
statusDot: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
marginRight: 10,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
},
|
||||
envelopeCard: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
marginBottom: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.lightGray,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: colors.primary,
|
||||
},
|
||||
envelopeBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
},
|
||||
envelopeBadgeText: {
|
||||
color: colors.white,
|
||||
fontWeight: 'bold',
|
||||
fontSize: 12,
|
||||
},
|
||||
logDetailRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
color: colors.darkGray,
|
||||
flex: 1,
|
||||
},
|
||||
value: {
|
||||
fontSize: 14,
|
||||
color: colors.darkText,
|
||||
fontWeight: '500',
|
||||
flex: 2,
|
||||
textAlign: 'right',
|
||||
},
|
||||
successText: {
|
||||
color: colors.success,
|
||||
fontWeight: '600',
|
||||
},
|
||||
detailsCard: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
infoRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.lightestGray,
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 14,
|
||||
color: colors.darkGray,
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
color: colors.darkText,
|
||||
},
|
||||
highlightedValue: {
|
||||
color: colors.success,
|
||||
fontWeight: '600',
|
||||
},
|
||||
timeCard: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
timeRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.lightestGray,
|
||||
},
|
||||
timeLabel: {
|
||||
fontSize: 15,
|
||||
color: colors.darkGray,
|
||||
fontWeight: '500',
|
||||
},
|
||||
timeValue: {
|
||||
fontSize: 15,
|
||||
color: colors.primary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
chipsContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 10,
|
||||
},
|
||||
chip: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
marginBottom: 8,
|
||||
},
|
||||
chipText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
finalStatusCard: {
|
||||
backgroundColor: colors.primaryLight,
|
||||
borderRadius: 12,
|
||||
padding: 20,
|
||||
marginTop: 10,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
finalStatusTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: colors.primary,
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
finalStatusRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
paddingVertical: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
finalStatusLabel: {
|
||||
fontSize: 16,
|
||||
color: colors.darkText,
|
||||
fontWeight: '500',
|
||||
},
|
||||
finalStatusValue: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
footerSpace: {
|
||||
height: 30,
|
||||
},
|
||||
success: {
|
||||
color: colors.success,
|
||||
},
|
||||
error: {
|
||||
color: colors.error,
|
||||
},
|
||||
primary: {
|
||||
color: colors.primary,
|
||||
},
|
||||
warning: {
|
||||
color: colors.warning,
|
||||
},
|
||||
successLight: {
|
||||
backgroundColor: colors.successLight,
|
||||
},
|
||||
lightGray: {
|
||||
backgroundColor: colors.lightGray,
|
||||
},
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: colors.white,
|
||||
padding: 16,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.lightGray,
|
||||
elevation: 10,
|
||||
},
|
||||
proceedButton: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 30,
|
||||
alignItems: 'center',
|
||||
},
|
||||
proceedButtonText: {
|
||||
color: colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
footerText: {
|
||||
textAlign: 'center',
|
||||
color: colors.darkGray,
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,247 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
||||
import { InsideHeader, Loader } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useOfflineTenderPurchase } from '../../Services/BBPS/Hooks';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
|
||||
const ProceedToPurTender = () => {
|
||||
const navigation: any = useNavigation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||
|
||||
const { mutate, isPending } = useOfflineTenderPurchase()
|
||||
const calculateTotalAmount = (item: any) => {
|
||||
const gstAmount =
|
||||
(Number(item.documentFees) + Number(item.portalCharge)) *
|
||||
(Number(item.gst) / 100);
|
||||
|
||||
const payableAmount =
|
||||
Number(item.documentFees) + Number(item.portalCharge) + gstAmount;
|
||||
return payableAmount
|
||||
}
|
||||
|
||||
const handleOfflinePurchase = async () => {
|
||||
const orderNo = `ORD${Date.now()}`;
|
||||
const body = {
|
||||
tenderDetails: SelectedPurchaseList.map((item: any, index:number) => ({
|
||||
gst: item.gst,
|
||||
regId: item.regId || 0,
|
||||
deptId: item.deptId || 1,
|
||||
gstAmount: item.gst / 100,
|
||||
payableAmount: calculateTotalAmount(item),
|
||||
portalCharge: item.portalCharge,
|
||||
tenderFees: item.documentFees,
|
||||
tenderId: item.tenderId,
|
||||
tenderNumber: item.tenderNumber,
|
||||
tenderStatus: item.tenderStatus ?? "",
|
||||
tenderType: item.tenderType ?? "",
|
||||
isPurchased: item.purchaseStatus ?? false,
|
||||
totalPayableAmount: calculateTotalAmount(item),
|
||||
percentageAmount: calculateTotalAmount(item) / 100,
|
||||
newTenderDocumentFees: item.documentFees * index + 1,
|
||||
})),
|
||||
orderNo: orderNo,
|
||||
};
|
||||
|
||||
console.log("tender offline body : ", body)
|
||||
try {
|
||||
mutate(body, {
|
||||
onSuccess: (res) => {
|
||||
console.log(res, '---------------------purchsase response');
|
||||
Alert.alert('Offline Purchase Successful!', `Order No: ${orderNo}`);
|
||||
navigation.goBack()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error, "================purchse Error");
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log("purchase offline error", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePurchase = (type: 'online' | 'offline') => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase();
|
||||
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: any[0] }) => (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.title}>{item.auctionNumber}</Text>
|
||||
<Text style={styles.pattern}>{item.auctionPattern}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<Calendar size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>End Date: {item.purchaseEnd}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<User size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Tender No: {item.tenderNumber}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Tender Fees: ₹{item.documentFees}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>GST ({item.gst}%): ₹{item.gst / 100}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRow}>
|
||||
<DollarSign size={18} color={colors.primary} />
|
||||
<Text style={styles.text}>Portal Charge: ₹{item.portalCharge}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.totalRow}>
|
||||
<CreditCard size={20} color={colors.white} />
|
||||
<Text style={styles.total}>Total: ₹{calculateTotalAmount(item)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<InsideHeader title="Tender Purchase" showArrow />
|
||||
<View style={styles.screen}>
|
||||
<FlatList
|
||||
data={SelectedPurchaseList}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.tenderId.toString()}
|
||||
contentContainerStyle={styles.container}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.bottomButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { marginRight: 8 }]}
|
||||
onPress={() => handlePurchase('online')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Purchase Online</Text>
|
||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { marginLeft: 8 }]}
|
||||
onPress={() => handlePurchase('offline')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Purchase Offline</Text>
|
||||
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{loading && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<Loader />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProceedToPurTender;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
padding: 16,
|
||||
paddingBottom: 120,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
elevation: 5,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.12,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowRadius: 8,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
},
|
||||
pattern: {
|
||||
fontSize: 14,
|
||||
color: colors.primary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
cardRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
marginBottom: 4,
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: '#555',
|
||||
},
|
||||
totalRow: {
|
||||
marginTop: 10,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
total: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
},
|
||||
bottomButtonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.white,
|
||||
width: '100%',
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 10,
|
||||
elevation: 5
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
fontSize: 14,
|
||||
},
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.2)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 10,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import React, { useState, useCallback, memo, useEffect } from 'react';
|
||||
import { View, Text, StyleSheet, FlatList, RefreshControl, ActivityIndicator } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import moment from 'moment';
|
||||
import { useFetchPurchaseTender } from '../../Services/BBPS/Hooks';
|
||||
import PurchaseTenderTile from './PurchaseTenderTile';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setTender } from '../../Redux/Slices/SelectedTender';
|
||||
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||
import { Skelton } from '../../Component/Index';
|
||||
import { allBooleanProps } from './types/types';
|
||||
import FilterModal from './Modal/FilterModal';
|
||||
import colors from '../../Constant/Color';
|
||||
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
|
||||
|
||||
const PurchaseTenderAllList = () => {
|
||||
const navigation = useNavigation() as any;
|
||||
const Dispatch = useDispatch()
|
||||
|
||||
const [allBoolean, setAllBoolean] = useState<allBooleanProps>({ isShowFilter: false })
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchPurchaseTender();
|
||||
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
||||
|
||||
|
||||
console.log("data for tender : ", data)
|
||||
useEffect(() => {
|
||||
if (isRefetching) {
|
||||
setShowRefreshBanner(true);
|
||||
} else {
|
||||
const timer = setTimeout(() => {
|
||||
setShowRefreshBanner(false);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isRefetching]);
|
||||
const format = 'DD-MM-YYYY HH:mm:ss';
|
||||
|
||||
function isTenderActive(item: any): boolean {
|
||||
const now = moment();
|
||||
const start = moment(item?.keyDates?.TenderBidding?.startDateTime, format);
|
||||
const end = moment(item?.keyDates?.TenderBidding?.endDateTime, format);
|
||||
|
||||
if (!start.isValid() || !end.isValid()) return false;
|
||||
|
||||
return now.isBetween(start, end, undefined, '[]');
|
||||
}
|
||||
|
||||
function isTenderEnd(item: any): boolean {
|
||||
const now = moment();
|
||||
const end = moment(item?.keyDates?.TenderBidding?.endDateTime, format);
|
||||
|
||||
if (!end.isValid()) return false;
|
||||
|
||||
return now.isAfter(end);
|
||||
}
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}, [hasNextPage, isFetchingNextPage]);
|
||||
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
if (!isFetchingNextPage) return null;
|
||||
return (
|
||||
<View style={{ paddingVertical: 12 }}>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}, [isFetchingNextPage]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchInput onChange={() => { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
|
||||
|
||||
{showRefreshBanner && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(200)}
|
||||
exiting={FadeOutUp.duration(500)}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 14,
|
||||
marginTop: 6,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#FFFFFF',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 8,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
elevation: 4,
|
||||
zIndex: 10, // Ensure it stays on top
|
||||
position: 'absolute',
|
||||
top: 60, // Adjust based on your SearchInput height
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: '#374151',
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
Refreshing tenders
|
||||
</Text>
|
||||
</Animated.View>
|
||||
)}
|
||||
{isLoading ? <Skelton />
|
||||
: <FlatList
|
||||
data={data?.result ?? []}
|
||||
keyExtractor={(item) => String(item.tenderId || item.id)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 200, paddingHorizontal: 10 }}
|
||||
renderItem={({ item, index }) => (
|
||||
<PurchaseTenderTile
|
||||
item={item}
|
||||
onPressDetails={() => {
|
||||
navigation.navigate('TenderDetail', { item, onAddToPurchase: () => { }, routeFrom: 'PurchasedTender' });
|
||||
}}
|
||||
onStartBidding={() => {
|
||||
Dispatch(setTender(item))
|
||||
navigation.navigate(NavigationString.TenderBidding, { data: item })
|
||||
}}
|
||||
buttonEnable={isTenderActive(item)}
|
||||
isTenderEnd={isTenderEnd(item)}
|
||||
/>
|
||||
)}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0.6}
|
||||
ListFooterComponent={renderFooter}
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
/>}
|
||||
|
||||
|
||||
{allBoolean.isShowFilter && <View style={{ flex: 1 }}>
|
||||
<FilterModal visible={allBoolean.isShowFilter} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: false })) }} onApply={() => { }} />
|
||||
</View>}
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseTenderAllList;
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||
import { ChevronDown, Trash2, Info } from 'lucide-react-native';
|
||||
import { InsideHeader } from '../../Component/Index';
|
||||
import colors from '../../Constant/Color';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||
|
||||
const PurchaseTenderList = () => {
|
||||
const Navigation: any = useNavigation()
|
||||
const Dispatch = useDispatch()
|
||||
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
||||
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||
const toggleExpand = (index: number) => {
|
||||
setExpandedIndex(prev => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.screen}>
|
||||
<InsideHeader title="Purchase" showArrow />
|
||||
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.heading}>Tender Purchase List</Text>
|
||||
<Info size={20} color="#0F172A" />
|
||||
</View>
|
||||
|
||||
{/* Cards */}
|
||||
{SelectedPurchaseList.map((item: any, index: any) => {
|
||||
const expanded = expandedIndex === index;
|
||||
return (
|
||||
<Animated.View
|
||||
key={item.auctionId}
|
||||
layout={Layout.springify()}
|
||||
style={styles.card}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => toggleExpand(index)}
|
||||
>
|
||||
<CardHeader
|
||||
title={item.auctionOfWork}
|
||||
expanded={expanded}
|
||||
/>
|
||||
|
||||
<Text style={styles.subTitle}>
|
||||
Tender No: {item.tenderNumber}
|
||||
</Text>
|
||||
|
||||
<Text style={styles.subTitle}>
|
||||
Status:{' '}
|
||||
<Text
|
||||
style={{
|
||||
color: item.tenderStatus === 'Opened' ? '#16A34A' : '#dc6f26ff',
|
||||
fontWeight: '700',
|
||||
}}
|
||||
>
|
||||
{item.tenderStatus === 'Opened' ? 'Open' : 'Approved'}
|
||||
</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(220)}
|
||||
exiting={FadeOut.duration(180)}
|
||||
layout={Layout.springify()}
|
||||
style={styles.details}
|
||||
>
|
||||
{/* {renderRow('', item.departmentName)} */}
|
||||
{/* {renderRow('Pattern', item.auctionPattern)} */}
|
||||
{renderRow('Fees', `₹ ${item.documentFees}`)}
|
||||
{renderRow('Department', item.departmentName)}
|
||||
{renderRow('Purchase End', item.purchaseEnd)}
|
||||
|
||||
<TouchableOpacity style={styles.removeButton} onPress={() => { Dispatch(togglePurchaseAuction(item)) }}>
|
||||
<Trash2 size={18} color="#fff" />
|
||||
<Text style={styles.removeText}>Remove</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* Footer */}
|
||||
<TouchableOpacity disabled={Boolean(!SelectedPurchaseList.length)} style={[styles.proceedButton, { backgroundColor: Boolean(!SelectedPurchaseList.length) ? colors.lightText : colors.primary }]} onPress={() => { Navigation.navigate(NavigationString.ProceedToPurTender) }}>
|
||||
<Text style={styles.proceedText}>Proceed to Purchase</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseTenderList;
|
||||
|
||||
const CardHeader = ({ title, expanded }: {
|
||||
title: string;
|
||||
expanded: boolean;
|
||||
}) => {
|
||||
const rotation = useSharedValue(expanded ? 1 : 0);
|
||||
rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 });
|
||||
const arrowStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${rotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Animated.View style={arrowStyle}>
|
||||
<ChevronDown size={22} color={colors.black} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRow = (label: string, value: string) => (
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<Text style={styles.value}>{value}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F8FAFC',
|
||||
},
|
||||
container: {
|
||||
padding: 16,
|
||||
paddingBottom: 110,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 14,
|
||||
},
|
||||
heading: {
|
||||
fontSize: 22,
|
||||
fontWeight: '800',
|
||||
color: colors.black,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 18,
|
||||
padding: 16,
|
||||
marginBottom: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E5E7EB',
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
flex: 1,
|
||||
marginRight: 12,
|
||||
},
|
||||
subTitle: {
|
||||
fontSize: 14,
|
||||
color: '#64748B',
|
||||
marginTop: 6,
|
||||
},
|
||||
details: {
|
||||
marginTop: 16,
|
||||
paddingTop: 14,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#E5E7EB',
|
||||
},
|
||||
row: {
|
||||
marginVertical: 6,
|
||||
},
|
||||
label: {
|
||||
fontSize: 13,
|
||||
color: '#64748B',
|
||||
},
|
||||
value: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: '#0F172A',
|
||||
},
|
||||
removeButton: {
|
||||
marginTop: 18,
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 14,
|
||||
paddingVertical: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
removeText: {
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
},
|
||||
proceedButton: {
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 18,
|
||||
alignItems: 'center',
|
||||
},
|
||||
proceedText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,297 @@
|
||||
import React, { use, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
Alert,
|
||||
} 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 { useFetchTenderById, useProceedToBid } from '../../Services/BBPS/Hooks';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import TenderFullDetail from './Modal/TenderFullDetail';
|
||||
|
||||
const PurchaseTenderTile = ({
|
||||
item,
|
||||
onPressDetails,
|
||||
}: any) => {
|
||||
const scale = useSharedValue(1);
|
||||
const elevation = useSharedValue(8);
|
||||
const navigation = useNavigation<any>();
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
elevation: elevation.value,
|
||||
}));
|
||||
|
||||
const { data: tenderResponse, }: any = useFetchTenderById(item?.tenderId);
|
||||
const tender = tenderResponse?.data;
|
||||
|
||||
// console.log("item my : ", item)
|
||||
|
||||
{
|
||||
if (item?.nitNumber == 'TN/111') {
|
||||
console.log("tender item : ", item?.nitNumber)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handlePressIn = () => {
|
||||
scale.value = withTiming(0.97, { duration: 120 });
|
||||
elevation.value = withTiming(3, { duration: 120 });
|
||||
};
|
||||
|
||||
const handlePressOut = () => {
|
||||
scale.value = withTiming(1, { duration: 160 });
|
||||
elevation.value = withTiming(8, { duration: 160 });
|
||||
};
|
||||
|
||||
const isLive = item?.biddingStatus === true;
|
||||
|
||||
const statusColor = isLive ? colors.primary : '#94A3B8';
|
||||
const lightStatusBg = isLive ? '#EFF6FF' : '#F8FAFC';
|
||||
const [showDetail, setShowDetail] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(item?.tenderNumber == 'TN/210'){
|
||||
console.log("tender item : ", item)
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeInUp.duration(400)}
|
||||
style={[styles.shadowContainer, animatedStyle]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={onPressDetails}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
style={styles.cardInternal}
|
||||
>
|
||||
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
|
||||
|
||||
<View style={styles.mainInfo}>
|
||||
{/* Header */}
|
||||
<View style={styles.headerRow}>
|
||||
<View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: lightStatusBg }]}>
|
||||
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
||||
<Text style={[styles.statusText, { color: statusColor }]}>
|
||||
{isLive ? 'LIVE' : 'CLOSED'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.title}>{item?.tenderNumber}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.amountContainer}>
|
||||
<Text style={styles.amount}>
|
||||
₹{Number(item?.tenderFeeInFig || 0).toLocaleString('en-IN')}
|
||||
</Text>
|
||||
<Text style={styles.feeLabel}>Tender Fee</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Date */}
|
||||
<TouchableOpacity style={styles.dateRow} onPress={() => {
|
||||
setShowDetail(true)
|
||||
}}>
|
||||
<Text style={styles.dateText}>
|
||||
<Text style={styles.dateLabel}>Bid Ends: </Text>
|
||||
{moment(
|
||||
item?.keydates?.bidSubmissionDate?.endDateTime,
|
||||
'DD-MM-YYYY HH:mm:ss'
|
||||
).format('DD MMM YYYY, hh:mm A')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Footer */}
|
||||
<View style={styles.footer}>
|
||||
<View style={styles.footerTop}>
|
||||
<Text style={styles.footerLabel}>Remaining Time</Text>
|
||||
|
||||
{isLive ? (
|
||||
<CountdownTimer
|
||||
endDate={
|
||||
item?.keydates?.bidSubmissionDate?.endDateTime
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.endedText}>--:--:--</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
disabled={!isLive}
|
||||
onPress={() => {
|
||||
if (isLive) {
|
||||
navigation.navigate('ProceedToBidDetail', { tender });
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.85}
|
||||
style={[
|
||||
styles.actionButton,
|
||||
{ backgroundColor: statusColor },
|
||||
!isLive && styles.disabledButton,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.addText}>
|
||||
{isLive ? 'Proceed to Bid' : 'Tender Closed'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{/* Tender Detail Modal */}
|
||||
|
||||
<TenderFullDetail visible={showDetail} data={item} onClose={() => setShowDetail(false)} />
|
||||
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchaseTenderTile;
|
||||
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,162 @@
|
||||
import { View, Text } from 'react-native'
|
||||
import React from 'react'
|
||||
import React, { useState, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Animated,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { InsideHeader } from '../../Component/Index';
|
||||
import {
|
||||
useFetchLiveTender,
|
||||
useFetchPurchaseTender,
|
||||
} from '../../Services/BBPS/Hooks';
|
||||
import colors from '../../Constant/Color';
|
||||
import { ShoppingCart } from 'lucide-react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import FontsFamily from '../../Constant/FontsFamily';
|
||||
import { useSelector } from 'react-redux';
|
||||
import LiveTender from './LiveTender';
|
||||
import PurchaseTenderAllList from './PurchaseTenderAllList';
|
||||
const { width } = Dimensions.get('screen')
|
||||
const TAB_WIDTH = 140;
|
||||
const TAB_HEIGHT = 44;
|
||||
|
||||
const Tender = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>Tender</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
const Navigation = useNavigation() as any
|
||||
const { data: liveTenderData = {} }: any = useFetchLiveTender();
|
||||
const { data: purchaseTenderData = {} } : any = useFetchPurchaseTender();
|
||||
const SelectedPurchaseList = useSelector((state: any) => state?.selectPurchaseTender);
|
||||
|
||||
export default Tender
|
||||
const liveCount = liveTenderData?.totalCount || 0;
|
||||
|
||||
const purchaseCount = purchaseTenderData?.pages?.[0]?.totalCount || 0;
|
||||
const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live');
|
||||
|
||||
const translateX = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const onTabPress = (tab: 'live' | 'purchase', index: number) => {
|
||||
setSelectedTab(tab);
|
||||
Animated.spring(translateX, {
|
||||
toValue: index * TAB_WIDTH,
|
||||
useNativeDriver: true,
|
||||
friction: 8,
|
||||
}).start();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<InsideHeader title="Tender" showArrow />
|
||||
|
||||
{/* Switch Tabs */}
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||
<View style={styles.switchWrapper}>
|
||||
<Animated.View
|
||||
pointerEvents="none"
|
||||
style={[
|
||||
styles.activeTab,
|
||||
{
|
||||
transform: [{ translateX }],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.tab}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onTabPress('live', 0)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
selectedTab === 'live' && styles.activeText,
|
||||
]}
|
||||
>
|
||||
Live ({liveCount})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.tab}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onTabPress('purchase', 1)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
selectedTab === 'purchase' && styles.activeText,
|
||||
]}
|
||||
>
|
||||
Purchased ({purchaseCount})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{/* Cart */}
|
||||
<View>
|
||||
{SelectedPurchaseList.length && <View style={{ borderWidth: 2, borderColor: colors.white, backgroundColor: colors.primaryLight, position: 'absolute', borderRadius: 50, zIndex: 20, right: 0, top: -10, paddingHorizontal: 2 }}>
|
||||
<Text style={{ fontSize: 12, color: colors.black }}>{SelectedPurchaseList.length}</Text>
|
||||
</View>}
|
||||
<TouchableOpacity style={{ padding: 10, backgroundColor: colors.primaryLight, borderRadius: 20 }} onPress={() => { Navigation.navigate(NavigationString.PurchaseTenderList) }}>
|
||||
<ShoppingCart size={22} color={colors.black} strokeWidth={2} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{selectedTab === 'live' ? <LiveTender /> : <PurchaseTenderAllList />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tender;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
|
||||
/* ---------- Switch UI ---------- */
|
||||
|
||||
switchWrapper: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 14,
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 18,
|
||||
padding: 2,
|
||||
height: TAB_HEIGHT,
|
||||
},
|
||||
|
||||
tab: {
|
||||
width: TAB_WIDTH,
|
||||
height: TAB_HEIGHT - 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 2,
|
||||
},
|
||||
|
||||
tabText: {
|
||||
fontSize: 14,
|
||||
color: '#64748b',
|
||||
lineHeight: 18,
|
||||
fontFamily: FontsFamily.poppinsSemiBold
|
||||
},
|
||||
|
||||
activeText: {
|
||||
color: colors.primary,
|
||||
},
|
||||
|
||||
activeTab: {
|
||||
position: 'absolute',
|
||||
top: 2,
|
||||
left: 2,
|
||||
width: TAB_WIDTH,
|
||||
height: TAB_HEIGHT - 4,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 6,
|
||||
elevation: 3,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
FlatList,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
ToastAndroid,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import React, { use, useEffect, useState } from 'react';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import MyInput from '../../../../src/Component/MyInput';
|
||||
import { pick, types } from '@react-native-documents/picker';
|
||||
import { InsideHeader } from '../../../Component/Index';
|
||||
import colors from '../../../Constant/Color';
|
||||
|
||||
type EnvelopeType = 'A' | 'B' | 'C';
|
||||
type EnvelopeFormState = {
|
||||
A: Record<number, string>;
|
||||
B: Record<number, string>;
|
||||
C: Record<number, string>;
|
||||
};
|
||||
|
||||
interface EnvelopeButtonProps {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
interface InfoRowProps {
|
||||
title: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface TenderBiddingData {
|
||||
data?: {
|
||||
tenderNumber?: string;
|
||||
nitNo?: string;
|
||||
emdInFig?: string;
|
||||
tenderRebid?: boolean;
|
||||
showEA?: boolean;
|
||||
showEB?: boolean;
|
||||
showEC?: boolean;
|
||||
tenderEnvelopeAList?: any[];
|
||||
tenderEnvelopeBList?: any[];
|
||||
tenderEnvelopeCList?: any[];
|
||||
};
|
||||
}
|
||||
|
||||
const TenderBidding = () => {
|
||||
const route = useRoute();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [bidData] = useState<any>(route.params?.tenderData || null);
|
||||
const tenderData = route.params?.bidData || null;
|
||||
const [activeEnvelope, setActiveEnvelope] = useState<EnvelopeType | null>('A');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [envelopForm, setEnvelopForm] = useState<EnvelopeFormState>({
|
||||
A: {},
|
||||
B: {},
|
||||
C: {},
|
||||
});
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadingUpload, setLoadingUpload] = useState(false)
|
||||
const [check, setCheck] = useState(false)
|
||||
|
||||
|
||||
const InfoRow = ({ title, value }: InfoRowProps) => (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.label}>{title}</Text>
|
||||
<Text style={styles.value}>{value || '-'}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const EnvelopeButton = ({ title, envelopeType, onPress }: EnvelopeButtonProps & { envelopeType: EnvelopeType }) => (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.envelopeButton,
|
||||
{
|
||||
borderWidth: activeEnvelope === envelopeType ? 1 : 0,
|
||||
borderColor: activeEnvelope === envelopeType ? colors.primary : 'transparent'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Text style={styles.envelopeButtonText}>{title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
|
||||
const handleEnvelopeChange = (
|
||||
envelopeType: EnvelopeType,
|
||||
sequenceId: number,
|
||||
value: any
|
||||
) => {
|
||||
setEnvelopForm(prev => ({
|
||||
...prev,
|
||||
[envelopeType]: {
|
||||
...prev[envelopeType],
|
||||
[sequenceId]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const pickPdf = async (
|
||||
envelopeType: EnvelopeType,
|
||||
sequenceId: number
|
||||
) => {
|
||||
try {
|
||||
const res = await pick({
|
||||
type: types.pdf,
|
||||
allowMultiSelection: false,
|
||||
});
|
||||
|
||||
const file = res[0];
|
||||
|
||||
handleEnvelopeChange(envelopeType, sequenceId, file);
|
||||
} catch (err: any) {
|
||||
if (err?.code === 'DOCUMENT_PICKER_CANCELED') {
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const renderEnvelopeItem = ({
|
||||
item,
|
||||
envelopeType,
|
||||
}: {
|
||||
item: any;
|
||||
envelopeType: EnvelopeType;
|
||||
}) => {
|
||||
const sequenceId = item?.[`tenderEnvelope${envelopeType}SequnceId`];
|
||||
|
||||
if (!sequenceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelName = item?.[`labelName${envelopeType}`] || '';
|
||||
const labelType = item?.[`labelType${envelopeType}`] || '';
|
||||
|
||||
const showInput =
|
||||
activeEnvelope === envelopeType !== 'C' &&
|
||||
!bidData?.[`envelope${envelopeType}SubmittStatus`];
|
||||
|
||||
|
||||
return (
|
||||
<View key={sequenceId}>
|
||||
<View style={styles.envelopeItem}>
|
||||
<View style={{ flexDirection: 'row', gap: 20 }}>
|
||||
{/* <Text style={styles.label}>Label Name</Text> */}
|
||||
<Text style={styles.value}>{labelName || '-'}</Text>
|
||||
{item?.[`labelMandatory${activeEnvelope}`] && <Text style={{ color: validateEnvelope() ? 'gray' : 'red', }}>*</Text>}
|
||||
|
||||
</View>
|
||||
|
||||
{/* <View>
|
||||
<Text style={styles.label}>Label Type</Text>
|
||||
<Text style={styles.value}>{labelType || '-'}</Text>
|
||||
</View> */}
|
||||
</View>
|
||||
|
||||
{showInput ? (
|
||||
<>
|
||||
{labelType === '1' && (
|
||||
<MyInput
|
||||
placeholder={`Enter ${labelName} numeric value`}
|
||||
value={envelopForm?.[envelopeType]?.[sequenceId] ?? ''}
|
||||
onChangeText={(text: string) =>
|
||||
handleEnvelopeChange(envelopeType, sequenceId, text)
|
||||
}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
)}
|
||||
|
||||
{labelType === '2' && (
|
||||
<MyInput
|
||||
placeholder={`Enter ${labelName} Alph-numeric value`}
|
||||
value={envelopForm?.[envelopeType]?.[sequenceId] ?? ''}
|
||||
onChangeText={(text: string) =>
|
||||
handleEnvelopeChange(envelopeType, sequenceId, text)
|
||||
}
|
||||
keyboardType="default"
|
||||
/>
|
||||
)}
|
||||
{labelType === '3' && (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.envelopeButton,
|
||||
{ borderColor: colors.primary, borderWidth: 1, marginBottom: 8 }
|
||||
]}
|
||||
onPress={() => pickPdf(activeEnvelope, sequenceId)}
|
||||
>
|
||||
<Text style={styles.envelopeButtonText}>
|
||||
{envelopForm?.[activeEnvelope]?.[sequenceId]
|
||||
? 'Change Document'
|
||||
: 'Upload Document'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{envelopForm?.[activeEnvelope]?.[sequenceId] && (
|
||||
<Text style={{ fontSize: 12, color: '#555' }}>
|
||||
{(envelopForm[activeEnvelope][sequenceId] as any)?.name}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
|
||||
{labelType === '4' && (
|
||||
<MyInput
|
||||
placeholder={`Enter ${labelName} currency value`}
|
||||
value={envelopForm?.[envelopeType]?.[sequenceId] ?? ''}
|
||||
onChangeText={(text: string) =>
|
||||
handleEnvelopeChange(envelopeType, sequenceId, text)
|
||||
}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
)}
|
||||
|
||||
</>
|
||||
) : <Text>Envelop {activeEnvelope} unavailable</Text>}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const hasEnvelopes = () => {
|
||||
const hasA = tenderData?.tenderEnvelopeAList?.length > 0;
|
||||
const hasB = tenderData?.tenderEnvelopeBList?.length > 0;
|
||||
// const hasC = tenderData?.tenderEnvelopeCList?.length > 0;
|
||||
return hasA || hasB;
|
||||
};
|
||||
|
||||
const digitToWordMap: Record<string, string> = {
|
||||
'0': 'Zero',
|
||||
'1': 'One',
|
||||
'2': 'Two',
|
||||
'3': 'Three',
|
||||
'4': 'Four',
|
||||
'5': 'Five',
|
||||
'6': 'Six',
|
||||
'7': 'Seven',
|
||||
'8': 'Eight',
|
||||
'9': 'Nine',
|
||||
};
|
||||
|
||||
const convertDigitsToWords = (value: string): string => {
|
||||
return value
|
||||
.split('')
|
||||
.map(digit => digitToWordMap[digit] || '')
|
||||
.join(' ') || value;
|
||||
};
|
||||
|
||||
const buildEnvelopePayload = () => {
|
||||
const list =
|
||||
bidData?.[`tenderEnvelope${activeEnvelope}List`] || [];
|
||||
const formValues = envelopForm?.[activeEnvelope] || {};
|
||||
|
||||
// 🔹 NON-FILE FIELDS (labelType !== '3')
|
||||
const saveData = list
|
||||
.filter(
|
||||
(item: any) =>
|
||||
item[`labelType${activeEnvelope}`] !== '3'
|
||||
)
|
||||
.map((item: any) => {
|
||||
const seqId =
|
||||
item[`tenderEnvelope${activeEnvelope}SequnceId`];
|
||||
const value = formValues[seqId];
|
||||
|
||||
return {
|
||||
[`tenderEnvelope${activeEnvelope}SequnceId`]: seqId,
|
||||
[`labelName${activeEnvelope}`]:
|
||||
item[`labelName${activeEnvelope}`],
|
||||
[`labelType${activeEnvelope}`]:
|
||||
item[`labelType${activeEnvelope}`],
|
||||
[`labelMandatory${activeEnvelope}`]:
|
||||
item[`labelMandatory${activeEnvelope}`],
|
||||
fieldValue: value ?? '',
|
||||
fieldValueInWord: value
|
||||
? convertDigitsToWords(value)
|
||||
: '',
|
||||
};
|
||||
});
|
||||
|
||||
// 🔹 FILE FIELDS (labelType === '3') → upload separately
|
||||
const fileData = list
|
||||
.filter(
|
||||
(item: any) =>
|
||||
item[`labelType${activeEnvelope}`] === '3'
|
||||
)
|
||||
.map((item: any) => {
|
||||
const seqId =
|
||||
item[`tenderEnvelope${activeEnvelope}SequnceId`];
|
||||
|
||||
return {
|
||||
[`tenderEnvelope${activeEnvelope}SequnceId`]: seqId,
|
||||
[`labelName${activeEnvelope}`]:
|
||||
item[`labelName${activeEnvelope}`],
|
||||
[`labelType${activeEnvelope}`]:
|
||||
item[`labelType${activeEnvelope}`],
|
||||
[`labelMandatory${activeEnvelope}`]:
|
||||
item[`labelMandatory${activeEnvelope}`],
|
||||
};
|
||||
});
|
||||
|
||||
return payload = {
|
||||
deptId: bidData?.deptId,
|
||||
tenderId: bidData?.tenderId,
|
||||
|
||||
|
||||
[`tenderEnvelope${activeEnvelope}NameId`]:
|
||||
bidData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[
|
||||
`tenderEnvelope${activeEnvelope}NameId`
|
||||
],
|
||||
[`envelope${activeEnvelope}TemplateName`]:
|
||||
bidData?.[`tenderEnvelope${activeEnvelope}List`]?.[0]?.[
|
||||
`envelope${activeEnvelope}TemplateName`
|
||||
],
|
||||
|
||||
[`save${activeEnvelope}Data`]: envelopForm?.[activeEnvelope]?.[3]
|
||||
? fileData
|
||||
: saveData,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
const validateEnvelope = () => {
|
||||
const list = bidData?.[`tenderEnvelope${activeEnvelope}List`] || [];
|
||||
const formValues = envelopForm[activeEnvelope];
|
||||
|
||||
const missing = list.some((item: any) => {
|
||||
const seqId = item[`tenderEnvelope${activeEnvelope}SequnceId`];
|
||||
const isMandatory = item[`labelMandatory${activeEnvelope}`];
|
||||
const value = formValues?.[seqId];
|
||||
|
||||
if (!isMandatory) return false;
|
||||
|
||||
// File upload
|
||||
if (item[`labelType${activeEnvelope}`] === '3') {
|
||||
return !value;
|
||||
}
|
||||
|
||||
// Text / numeric
|
||||
return !value || value.toString().trim() === '';
|
||||
});
|
||||
|
||||
return !missing;
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitEnvelopeA = async () => {
|
||||
if (!validateEnvelope()) {
|
||||
Alert.alert('Validation Error', 'Please fill all mandatory fields');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildEnvelopePayload();
|
||||
console.log('Envelope A Payload:', payload);
|
||||
setLoading(true)
|
||||
if (envelopForm?.[activeEnvelope]?.[3]) {
|
||||
handleSubmitEnvelopeFile()
|
||||
}
|
||||
try {
|
||||
|
||||
const res = await apiCallForPostWithToken(SUBMIT_ENVELOPE_A, payload)
|
||||
console.log("res A envelop : ", res)
|
||||
ToastAndroid.show(res.message, ToastAndroid.SHORT)
|
||||
|
||||
|
||||
navigation.goBack()
|
||||
} catch (error) {
|
||||
console.log("error A envelop submit : ", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitEnvelopeB = async () => {
|
||||
if (!validateEnvelope()) {
|
||||
Alert.alert('Validation Error', 'Please fill all mandatory fields');
|
||||
return;
|
||||
}
|
||||
setLoading(true)
|
||||
const payload = buildEnvelopePayload();
|
||||
console.log('Envelope B Payload:', payload);
|
||||
if (envelopForm?.[activeEnvelope]?.[3]) {
|
||||
handleSubmitEnvelopeFile()
|
||||
}
|
||||
try {
|
||||
const res = await apiCallForPostWithToken(SUBMIT_ENVELOPE_B, payload)
|
||||
console.log("res B envelop : ", res)
|
||||
|
||||
ToastAndroid.show(res.message, ToastAndroid.SHORT)
|
||||
navigation.goBack()
|
||||
} catch (error) {
|
||||
console.log("error B envelop submit : ", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmitEnvelopeFile = async () => {
|
||||
|
||||
setLoadingUpload(true)
|
||||
const payload = buildEnvelopePayload();
|
||||
console.log(`Envelope file ${activeEnvelope} Payload:`, payload);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('files', {
|
||||
uri: envelopForm?.[activeEnvelope]?.[3].uri,
|
||||
name: envelopForm?.[activeEnvelope]?.[3].name,
|
||||
type: envelopForm?.[activeEnvelope]?.[3].type,
|
||||
});
|
||||
formData.append('requestDto', payload)
|
||||
|
||||
|
||||
console.log("reuestDto for fil doc : ", formData)
|
||||
// return
|
||||
const res = await apiCallForPostWithToken(activeEnvelope === 'B' ? SUBMIT_ENVELOPE_B : SUBMIT_ENVELOPE_A, payload)
|
||||
console.log(`res file ${activeEnvelope} Payload:`, res)
|
||||
ToastAndroid.show(res.message, ToastAndroid.SHORT)
|
||||
// navigation.goBack()
|
||||
} catch (error) {
|
||||
console.log(`Envelope file ${activeEnvelope} error:`, error)
|
||||
} finally {
|
||||
setLoadingUpload(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text>Loading...</Text>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// console.log("route for date : ", bidData)
|
||||
// console.log("bid envelop : ", tenderData)
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<InsideHeader title="Tender Bidding Start" showArrow />
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||
{/* Tender Info */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.row}>
|
||||
<InfoRow title="Tender Number" value={bidData?.tenderNumber || '-'} />
|
||||
<InfoRow title="NIT Number" value={bidData?.nitNo || '-'} />
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<InfoRow title="EMD (in Fig.)" value={bidData?.emdInFig || '-'} />
|
||||
<InfoRow
|
||||
title="Re-Bid Allowed"
|
||||
value={bidData?.tenderRebid ? 'Yes' : 'No'}
|
||||
/>
|
||||
</View>
|
||||
<InfoRow title="Bid Withdraw" value={bidData?.bidWithdraw || '-'} />
|
||||
</View>
|
||||
|
||||
{/* Envelope Buttons */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.sectionTitle}>Bid Envelopes</Text>
|
||||
<View style={styles.envelopeRow}>
|
||||
{tenderData?.showEA && (
|
||||
<EnvelopeButton
|
||||
title="Envelope A"
|
||||
envelopeType="A"
|
||||
onPress={() => setActiveEnvelope('A')}
|
||||
/>
|
||||
)}
|
||||
{tenderData?.showEB && (
|
||||
<EnvelopeButton
|
||||
title="Envelope B"
|
||||
envelopeType="B"
|
||||
onPress={() => {
|
||||
if (tenderData?.envelopeASubmittStatus) {
|
||||
setActiveEnvelope('B')
|
||||
} else {
|
||||
Alert.alert('Info', 'First Fill Envelop A First')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tenderData?.showEC && (
|
||||
<EnvelopeButton
|
||||
title="Envelope C"
|
||||
envelopeType="C"
|
||||
onPress={() => setActiveEnvelope('C')}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
{/* Envelope A List */}
|
||||
{tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.length > 0 && activeEnvelope !== 'C' && (
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.sectionTitle}>Envelope {activeEnvelope} Details</Text>
|
||||
<FlatList
|
||||
data={tenderData?.[`tenderEnvelope${activeEnvelope}List`]}
|
||||
keyExtractor={(item) => item?.[`tenderEnvelope${activeEnvelope}SequnceId`]?.toString()}
|
||||
renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })}
|
||||
scrollEnabled={false}
|
||||
/>
|
||||
{/* <View style={[styles.infoRow, { flexDirection: 'row', gap: 10 }]}>
|
||||
<TouchableOpacity onPress={() => setCheck(!check)}>
|
||||
{check ? <Fontisto name="checkbox-active" size={24} color={colors.primary} /> :
|
||||
<Fontisto name="checkbox-passive" size={24} color={colors.primary} />
|
||||
}</TouchableOpacity>
|
||||
<Text>Check First Terms&Condition</Text>
|
||||
</View> */}
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.footer}>
|
||||
{hasEnvelopes() ? (
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
onPress={() => {
|
||||
if (!check) {
|
||||
Alert.alert('Info', 'check first terms & condition')
|
||||
}
|
||||
if (activeEnvelope === 'A') {
|
||||
handleSubmitEnvelopeA()
|
||||
} else {
|
||||
handleSubmitEnvelopeB()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.primaryButtonText}>{loading ? 'Submitting...' : `Submit Bid (${activeEnvelope})`}</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={styles.secondaryButton}
|
||||
onPress={() => navigation.goBack()}
|
||||
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Go Back</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderBidding;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1, backgroundColor: colors.white
|
||||
},
|
||||
scrollContainer: {
|
||||
padding: 16,
|
||||
paddingBottom: '100%',
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 2,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 12,
|
||||
color: '#333',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
},
|
||||
infoRow: {
|
||||
flex: 1,
|
||||
marginBottom: 10,
|
||||
},
|
||||
label: {
|
||||
fontSize: 13,
|
||||
color: '#777',
|
||||
marginBottom: 2,
|
||||
},
|
||||
value: {
|
||||
fontSize: 15,
|
||||
color: '#222',
|
||||
fontWeight: '500',
|
||||
},
|
||||
envelopeRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
envelopeButton: {
|
||||
backgroundColor: '#EEF2FF',
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
borderRadius: 8,
|
||||
minWidth: 100,
|
||||
alignItems: 'center',
|
||||
},
|
||||
envelopeButtonText: {
|
||||
color: '#3F51B5',
|
||||
fontWeight: '600',
|
||||
fontSize: 14,
|
||||
},
|
||||
envelopeItem: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#eee',
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
marginBottom: 12,
|
||||
backgroundColor: '#f9f9f9',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
marginVertical: 20,
|
||||
},
|
||||
primaryButton: {
|
||||
backgroundColor: '#3F51B5',
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 28,
|
||||
borderRadius: 10,
|
||||
minWidth: 120,
|
||||
alignItems: 'center',
|
||||
},
|
||||
primaryButtonText: {
|
||||
color: '#fff',
|
||||
fontWeight: '600',
|
||||
fontSize: 15,
|
||||
},
|
||||
secondaryButton: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#EEF2FF',
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 28,
|
||||
borderRadius: 10,
|
||||
minWidth: 120,
|
||||
alignItems: 'center',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
color: '#3F51B5',
|
||||
fontWeight: '600',
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface LiveTenderTileProps {
|
||||
data: any
|
||||
onPurchasePress?: () => void;
|
||||
onDetailsPress?: () => void;
|
||||
index: number;
|
||||
tenderDispatchDate?: string;
|
||||
tenderOfWork?: string;
|
||||
purchaseEnd?: string;
|
||||
tenderFees?: string | number;
|
||||
isPurchased?: boolean;
|
||||
tenderId?: any;
|
||||
tenderNumber?: any;
|
||||
}
|
||||
|
||||
export interface allBooleanProps {
|
||||
showDetail?: boolean
|
||||
isShowFilter?: boolean
|
||||
}
|
||||
+11
-1
@@ -8,7 +8,12 @@ import Home from "./Home/Home";
|
||||
import Login from "./Login/Login";
|
||||
import Onboarding from "./Onboarding/Onboarding";
|
||||
import Splash from "./Splash/Splash";
|
||||
import LiveTenderDetail from "./Tender/LiveTenderDetail";
|
||||
import ProceedToBidDetail from "./Tender/ProceedToBidDetail";
|
||||
import ProceedToPurTender from "./Tender/ProceedToPurTender";
|
||||
import PurchaseTenderList from "./Tender/PurchaseTenderList";
|
||||
import Tender from "./Tender/Tender";
|
||||
import TenderBidding from "./Tender/tenderBidding/TenderBidding";
|
||||
|
||||
|
||||
const Screens = {
|
||||
@@ -22,6 +27,11 @@ const Screens = {
|
||||
Emd,
|
||||
StartBidding,
|
||||
PurchaseAuctionList,
|
||||
StartReverseBidding
|
||||
StartReverseBidding,
|
||||
PurchaseTenderList,
|
||||
ProceedToPurTender,
|
||||
TenderBidding,
|
||||
ProceedToBidDetail,
|
||||
LiveTenderDetail
|
||||
}
|
||||
export default Screens
|
||||
@@ -32,8 +32,9 @@ const PROCEED_PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/proceedToPurcha
|
||||
const PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuction`
|
||||
const PURCHASE_OFFLINE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuctionOffline`
|
||||
const PURCHASE_TENDER = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTender`
|
||||
const PURCHASE_TENDER_OFFLINE = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTenderOffline`
|
||||
const AUCTION_PURCHASE_STATUS = `https://api.godemo.in/Auction/api/auction/transactionStatus?orderNo=`
|
||||
const GET_TENDER_DETAILS = `${BASE_URL}eTendering/getTenderById?tenderId=`
|
||||
const GET_TENDER_DETAILS = `https://procurelinc.in/EProcurementSB/tenderBidding/getTenderDetail?tenderId=`
|
||||
const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=`
|
||||
// https://procurelinc.in/EProcurementSB/eTendering/download/docId?docId=158
|
||||
// const DOWNLOAD_DOC = `${BASE_URL}eTende ring/download/docId?docId=`
|
||||
@@ -69,6 +70,17 @@ const VIEW_AUCTION_DOCUMENTS = `${BASE_URL_Auction}api/auction/viewDocumentsForA
|
||||
const AUCTION_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocumentForAuction`
|
||||
const AUCTION_ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocument/auctionItemId`
|
||||
|
||||
//tender
|
||||
|
||||
const TENDER_LOGIN = `${BASE_URL}eproc/loginWithoutDecryption`
|
||||
const PROCEED_TO_BID = `${BASE_URL}tenderBidding/proceedToBid?tenderId=`
|
||||
const SUBMIT_ENVELOPE_A = `${BASE_URL}tenderBidding/getSaveEnvelopeAData`
|
||||
const SUBMIT_ENVELOPE_B = `${BASE_URL}tenderBidding/getSaveEnvelopeBData`
|
||||
const SUBMIT_ENVELOPE_FILE_A = `${BASE_URL}tenderBidding/addDocumentsEnvelopeA?requestDto=`
|
||||
const SUBMIT_ENVELOPE_FILE_B = `${BASE_URL}tenderBidding/addDocumentsEnvelopeB?requestDto=`
|
||||
|
||||
|
||||
|
||||
export {
|
||||
// Rohit
|
||||
VIEW_AUCTION_ITEM_DETAIL,
|
||||
@@ -132,6 +144,13 @@ export {
|
||||
AUCTION_LOGIN,
|
||||
TWO_FACTOR_AUTH,
|
||||
GET_MASTER_DATA,
|
||||
PURCHASE_OFFLINE_AUCTION
|
||||
PURCHASE_OFFLINE_AUCTION,
|
||||
TENDER_LOGIN,
|
||||
PURCHASE_TENDER_OFFLINE,
|
||||
PROCEED_TO_BID,
|
||||
SUBMIT_ENVELOPE_A,
|
||||
SUBMIT_ENVELOPE_B,
|
||||
SUBMIT_ENVELOPE_FILE_A,
|
||||
SUBMIT_ENVELOPE_FILE_B
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { fetchAuctionItem, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, getAuction, offlineAuctionPurchase, userLogin, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments } from './apis';
|
||||
import { fetchAuctionItem, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, getAuction, offlineAuctionPurchase, userLogin, offlineTenderPurchase, fetchTenderById, proceedToBid, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments } from './apis';
|
||||
|
||||
// User
|
||||
|
||||
@@ -20,15 +20,38 @@ export const useDashboard = () => {
|
||||
// Auction & Tender
|
||||
|
||||
export const useFetchPurchaseTender = () => {
|
||||
return useQuery({
|
||||
queryKey: [],
|
||||
queryFn: () => fetchPurchaseTender()
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['fetchPurchaseTender'],
|
||||
queryFn: ({ pageParam = 0 }) =>
|
||||
fetchPurchaseTender({
|
||||
page: pageParam,
|
||||
size: 10,
|
||||
}),
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
console.log('last page : ', lastPage)
|
||||
console.log("all page : ", allPages)
|
||||
const {totalCount } = lastPage;
|
||||
|
||||
const totalPages = Math.ceil(totalCount / 10);
|
||||
const nextPage = allPages.length;
|
||||
return nextPage < totalPages ? nextPage : undefined;
|
||||
},
|
||||
|
||||
select: (data) => ({
|
||||
...data,
|
||||
result: data.pages.flatMap(
|
||||
(page: any) => page?.data ?? []
|
||||
),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const useFetchLiveTender = () => {
|
||||
return useQuery({
|
||||
queryKey: [],
|
||||
queryKey: ['fetchLiveTender',],
|
||||
queryFn: () => fetchLiveTender()
|
||||
});
|
||||
};
|
||||
@@ -78,6 +101,30 @@ export const useOfflineAuctionPurchase = () => {
|
||||
mutationFn: (payload: any) => offlineAuctionPurchase(payload),
|
||||
});
|
||||
};
|
||||
export const useOfflineTenderPurchase = () => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: any) => offlineTenderPurchase(payload),
|
||||
});
|
||||
};
|
||||
|
||||
export const useFetchTenderById = (tenderId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['fetchTenderById', tenderId],
|
||||
queryFn: () => fetchTenderById(tenderId)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export const useProceedToBid = (tenderId?: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['proceedToBid', tenderId],
|
||||
queryFn: () => proceedToBid(tenderId as string),
|
||||
enabled: false, // 👈 manual trigger
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// EMD
|
||||
export const useEmdCheck = () => {
|
||||
|
||||
+50
-11
@@ -1,12 +1,13 @@
|
||||
import axios from "axios";
|
||||
import { AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL } from "./ApiUrls";
|
||||
import { AUCTION_DOCUMENT_DOWNLOAD, DASHBOARD_COUNT, EMD_CHECK, FORARD_AUCTION_ITEM, GET_AUCTION_DETAILS, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PROCEED_TO_BID, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN, PURCHASE_AUCTION_LIST, PURCHASE_OFFLINE_AUCTION, REVERSE_AUCTION_ITEM, VIEW_AUCTION_DOCUMENTS, VIEW_AUCTION_ITEM_DETAIL } from "./ApiUrls";
|
||||
import { apiCall } from "../../Axios/Axios";
|
||||
|
||||
// User
|
||||
|
||||
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
|
||||
try {
|
||||
const response = await axios.post(LOG_IN_URL, { ...payload, divice: "mobileApp" });
|
||||
// const response = await axios.post(LOG_IN_URL, { ...payload, divice: "mobileApp" });
|
||||
const response = await axios.post(TENDER_LOGIN, { ...payload, device: "mobileApp" });
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
throw error.response?.data || error.message;
|
||||
@@ -18,15 +19,6 @@ export const fetchDashboard = async (): Promise<any> => {
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
export const fetchPurchaseTender = async (): Promise<any> => {
|
||||
const data = await apiCall<any>('get', `${GET_MY_TENDER}`);
|
||||
return data
|
||||
}
|
||||
export const fetchLiveTender = async (): Promise<any> => {
|
||||
const data = await apiCall<any>('get', `${LIVE_TENDER}`);
|
||||
return data
|
||||
}
|
||||
export const fetchPurchaseAuction = async (): Promise<any> => {
|
||||
const data = await apiCall<any>('get', `${GET_MY_AUCTION}`);
|
||||
return data
|
||||
@@ -73,6 +65,53 @@ export const fetchAuctionItem = async (payload: any): Promise<any> => {
|
||||
}
|
||||
};
|
||||
|
||||
// Tender Purchase offline
|
||||
export const offlineTenderPurchase = async (payload: any): Promise<any> => {
|
||||
try {
|
||||
const response = await apiCall<any>('post', PURCHASE_TENDER_OFFLINE, payload);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
throw error.response?.data || error.message;
|
||||
}
|
||||
};
|
||||
|
||||
// tender
|
||||
export const fetchPurchaseTender = async ({
|
||||
page,
|
||||
size,
|
||||
}: {
|
||||
page: number;
|
||||
size: number;
|
||||
}) => {
|
||||
return apiCall<any>(
|
||||
'get',
|
||||
`${GET_MY_TENDER}?page=${page}&size=${size}`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const fetchLiveTender = async (): Promise<any> => {
|
||||
const data = await apiCall<any>('get', `${LIVE_TENDER}`);
|
||||
return data
|
||||
}
|
||||
export const fetchTenderById = async (tenderId: string): Promise<any> => {
|
||||
const data = await apiCall<any>(
|
||||
'get',
|
||||
`${GET_TENDER_DETAILS}${tenderId}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const proceedToBid = async (tenderId: string): Promise<any> => {
|
||||
const data = await apiCall<any>(
|
||||
'get',
|
||||
`${PROCEED_TO_BID}${tenderId}`
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const fetchPurchaseAuctionList = async (payload: any): Promise<any> => {
|
||||
const data = await apiCall<any>('post', `${PURCHASE_AUCTION_LIST}`, payload);
|
||||
|
||||
Reference in New Issue
Block a user