diff --git a/src/Constant/NavigationString.tsx b/src/Constant/NavigationString.tsx
index 72aecd0..ff2ed1e 100644
--- a/src/Constant/NavigationString.tsx
+++ b/src/Constant/NavigationString.tsx
@@ -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
\ No newline at end of file
diff --git a/src/Navigation/MainStack.jsx b/src/Navigation/MainStack.jsx
index 3f3d987..85efda9 100644
--- a/src/Navigation/MainStack.jsx
+++ b/src/Navigation/MainStack.jsx
@@ -36,6 +36,11 @@ export default function MainStack() {
+
+
+
+
+
diff --git a/src/Redux/Slices/SelectedPurchaseTender.ts b/src/Redux/Slices/SelectedPurchaseTender.ts
new file mode 100644
index 0000000..edd9b68
--- /dev/null
+++ b/src/Redux/Slices/SelectedPurchaseTender.ts
@@ -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
+ ) => {
+ 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;
diff --git a/src/Redux/Slices/SelectedTender.ts b/src/Redux/Slices/SelectedTender.ts
new file mode 100644
index 0000000..ca7e58a
--- /dev/null
+++ b/src/Redux/Slices/SelectedTender.ts
@@ -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;
diff --git a/src/Redux/Store.js b/src/Redux/Store.js
index d9be3ce..a475847 100644
--- a/src/Redux/Store.js
+++ b/src/Redux/Store.js
@@ -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',
diff --git a/src/Screens/Bidding/Emd.tsx b/src/Screens/Bidding/Emd.tsx
index 613b940..2b168b4 100644
--- a/src/Screens/Bidding/Emd.tsx
+++ b/src/Screens/Bidding/Emd.tsx
@@ -93,14 +93,14 @@ const Emd = () => {
EMD ₹ {item.emdAmount}
- toggleSelect(item, checked)}
- />
+ /> */}
diff --git a/src/Screens/Login/Login.tsx b/src/Screens/Login/Login.tsx
index 4ac7a9a..4bb4af1 100644
--- a/src/Screens/Login/Login.tsx
+++ b/src/Screens/Login/Login.tsx
@@ -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({
diff --git a/src/Screens/Tender/LiveTender.tsx b/src/Screens/Tender/LiveTender.tsx
new file mode 100644
index 0000000..3e48882
--- /dev/null
+++ b/src/Screens/Tender/LiveTender.tsx
@@ -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 (
+
+
+
+ );
+ }, [isFetchingNextPage]);
+
+ const renderItem = useCallback(
+ ({ item, index }: any) => {
+ const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
+ return { Dispatch(togglePurchaseTender(item)) }}
+ // onDetailsPress={() => navigation.navigate('LiveTenderDetail', {item})}
+ />
+ },
+ [SelectedPurchaseList]
+ );
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ { }} placeholder="Search...." />
+
+ {showRefreshBanner && (
+
+
+
+ Refreshing auctions
+
+
+ )}
+
+ String(item.auctionId)}
+ renderItem={renderItem}
+ showsVerticalScrollIndicator={false}
+ contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
+ onEndReached={loadMore}
+ onEndReachedThreshold={0.2}
+ ListFooterComponent={renderFooter}
+ refreshControl={
+
+ }
+ initialNumToRender={6}
+ maxToRenderPerBatch={6}
+ windowSize={7}
+ removeClippedSubviews
+ updateCellsBatchingPeriod={50}
+ />
+
+ );
+};
+
+export default LiveTender;
\ No newline at end of file
diff --git a/src/Screens/Tender/LiveTenderDetail.tsx b/src/Screens/Tender/LiveTenderDetail.tsx
new file mode 100644
index 0000000..5cd345a
--- /dev/null
+++ b/src/Screens/Tender/LiveTenderDetail.tsx
@@ -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();
+ const { tender } = route.params;
+ console.log("live tender details : ", tender)
+ return (
+
+ LiveTenderDetail
+
+ )
+}
+
+export default LiveTenderDetail
+
+const styles = StyleSheet.create({})
\ No newline at end of file
diff --git a/src/Screens/Tender/LiveTenderTile.tsx b/src/Screens/Tender/LiveTenderTile.tsx
new file mode 100644
index 0000000..bc5104c
--- /dev/null
+++ b/src/Screens/Tender/LiveTenderTile.tsx
@@ -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 = ({
+ 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 (
+
+ {/* Status Tag */}
+
+
+ navigation.navigate('LiveTenderDetail', data)}
+ >
+ {/* Header */}
+
+
+
+ #{tenderNumber}
+
+ ₹{tenderFees}
+
+
+ {/* Work Title */}
+
+ {tenderOfWork ?? 'General Tender Work'}
+
+
+ {/* Info Grid */}
+
+
+
+
+ Start
+ {tenderDispatchDate ?? '-'}
+
+
+
+
+
+
+ Deadline
+
+ {moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')}
+
+
+
+
+
+ {/* Action Bar */}
+
+
+
+ Details
+
+
+
+
+ {purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
+
+ {!purchaseEnded && (
+
+ )}
+
+
+
+ {/* Tender Detail Modal */}
+ {/* setShowDetail(false)} /> */}
+
+
+ );
+};
+
+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',
+ },
+});
diff --git a/src/Screens/Tender/Modal/FilterModal.tsx b/src/Screens/Tender/Modal/FilterModal.tsx
new file mode 100644
index 0000000..fa9094c
--- /dev/null
+++ b/src/Screens/Tender/Modal/FilterModal.tsx
@@ -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 = ({ visible, onClose, onApply }) => {
+ const [fromDate, setFromDate] = useState(null);
+ const [toDate, setToDate] = useState(null);
+ const [status, setStatus] = useState(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 (
+ <>
+
+ {/* Overlay */}
+
+
+ {/* Content */}
+
+ Filter Auctions
+
+ {/* ===== DATE RANGE ===== */}
+ Date Range
+
+
+ openDatePicker('FROM')}
+ >
+ From
+
+ {fromDate
+ ? moment(fromDate).format('DD MMM YYYY')
+ : 'Select date'}
+
+
+
+ openDatePicker('TO')}
+ >
+ To
+
+ {toDate
+ ? moment(toDate).format('DD MMM YYYY')
+ : 'Select date'}
+
+
+
+
+ {/* ===== STATUS ===== */}
+ Auction Status
+
+
+ {[
+ { label: 'Running', value: 'RUNNING' },
+ { label: 'Ended', value: 'ENDED' },
+ { label: 'Not Started', value: 'NOT_STARTED' },
+ ].map(item => {
+ const active = status === item.value;
+ return (
+ setStatus(item.value as AuctionStatus)}
+ style={[
+ styles.statusPill,
+ active && styles.statusActive,
+ ]}
+ >
+
+ {item.label}
+
+
+ );
+ })}
+
+
+ {/* ===== FOOTER ===== */}
+
+ {/*
+ Cancel
+ */}
+
+
+ Apply
+
+
+
+
+
+ {/* ===== DATE PICKER ===== */}
+ 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
+ },
+});
diff --git a/src/Screens/Tender/Modal/TenderFullDetail.tsx b/src/Screens/Tender/Modal/TenderFullDetail.tsx
new file mode 100644
index 0000000..eaa78e8
--- /dev/null
+++ b/src/Screens/Tender/Modal/TenderFullDetail.tsx
@@ -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 = ({ 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) => (
+
+
+
+ {title}
+
+ {children}
+
+ );
+
+ const InfoBox = ({ label, value, full = false }: any) => (
+
+ {label}
+ {value || '-'}
+
+ );
+
+ return (
+
+
+
+
+ {/* Header */}
+
+
+
+
+
+
+ Tender Overview
+ Ref: {tender?.tenderNumber}
+
+
+
+
+ {isLive ? 'LIVE' : 'CLOSED'}
+
+
+
+
+
+ {/* Basic Details */}
+
+
+ {/* Work & Estimate */}
+
+
+ {/* Department & Location */}
+
+
+ {/* Fee & Charges */}
+
+
+ {/* Bid Info */}
+
+
+ {/* Key Dates */}
+
+
+ {/* Documents */}
+
+ {tender?.tenderDocuments?.map((doc: any) => (
+
+
+ {doc.fileName}
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+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);
\ No newline at end of file
diff --git a/src/Screens/Tender/ProceedToBidDetail.tsx b/src/Screens/Tender/ProceedToBidDetail.tsx
new file mode 100644
index 0000000..086ef56
--- /dev/null
+++ b/src/Screens/Tender/ProceedToBidDetail.tsx
@@ -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()
+ // 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 }) => (
+
+
+
+ {label}: {status ? "Submitted" : "Not Submitted"}
+
+
+ );
+
+ // Envelope log card component
+ const EnvelopeLogCard = ({ log, title }) => (
+
+
+ {title}
+
+ {log?.envelope}
+
+
+
+
+ Activity:
+ {log?.activity}
+
+
+
+ Date & Time:
+ {log?.dateTime}
+
+
+
+ System Info:
+ {log?.osName} / {log?.browserName}
+
+
+
+ IP Address:
+ {log?.ipAddress}
+
+
+
+ Status Message:
+ {log?.logMessage}
+
+
+ {log?.headingMsg && (
+
+ Heading:
+ {log?.headingMsg}
+
+ )}
+
+ );
+
+ // Information row component
+ const InfoRow = ({ label, value, isHighlighted = false }) => (
+
+ {label}:
+ {value}
+
+ );
+
+ // Feature chip component
+ const FeatureChip = ({ label, enabled }) => (
+
+
+ {label} {enabled ? '✓' : '✗'}
+
+
+ );
+
+ return (
+
+
+
+
+
+ {/* Tender Header Section */}
+
+
+
+ {tenderData.tenderNumber}
+ ID: {tenderData.tenderId}
+
+
+
+ {tenderData.biddingStatus ? "Active" : "Inactive"}
+
+
+
+
+ {tenderData.nameofWork}
+
+
+
+ NIT No.
+ {tenderData.nitNo}
+
+
+
+ EMD Amount
+ ₹{tenderData.emdInFig}
+
+
+
+
+ {/* Envelope Submission Status */}
+
+ Envelope Submission Status
+
+
+
+
+
+
+
+ {/* Envelope Logs */}
+ {tenderData.envelopeA_Logs && (
+
+ Submission Logs
+
+
+
+
+ )}
+
+ {/* Tender Details */}
+
+ Tender Details
+
+
+
+
+
+
+
+
+
+ {/* Time Period */}
+
+ Submission Time Period
+
+
+ Starts On:
+ {formatDate(tenderData.submittedStartDate)}
+
+
+ Ends On:
+ {formatDate(tenderData.submittedEndDate)}
+
+
+
+
+ {/* Feature Flags */}
+
+ Available Features
+
+
+
+
+
+
+
+
+
+
+ {/* Final Status */}
+
+ Overall Status
+
+ All Envelopes Submitted:
+
+ {tenderData.envelopeASubmittStatus && tenderData.envelopeBSubmittStatus && tenderData.envelopeCSubmittStatus
+ ? "COMPLETED" : "PENDING"}
+
+
+
+ Bidding Status:
+
+ {tenderData.biddingStatus ? "ACTIVE" : "CLOSED"}
+
+
+
+
+
+
+
+
+
+
+ {
+ tenderData?.proceedtoBid && !tenderData?.viewBid && tenderData?.biddingStatus && tenderData?.bidWithdrawStatus === false ? (
+
+
+ Start Bidding
+
+
+ ) : One time bidding is completed
+ }
+
+
+ )
+}
+
+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,
+ },
+});
\ No newline at end of file
diff --git a/src/Screens/Tender/ProceedToPurTender.tsx b/src/Screens/Tender/ProceedToPurTender.tsx
new file mode 100644
index 0000000..133e099
--- /dev/null
+++ b/src/Screens/Tender/ProceedToPurTender.tsx
@@ -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] }) => (
+
+
+ {item.auctionNumber}
+ {item.auctionPattern}
+
+
+
+
+ End Date: {item.purchaseEnd}
+
+
+
+
+ Tender No: {item.tenderNumber}
+
+
+
+
+ Tender Fees: ₹{item.documentFees}
+
+
+
+
+ GST ({item.gst}%): ₹{item.gst / 100}
+
+
+
+
+ Portal Charge: ₹{item.portalCharge}
+
+
+
+
+ Total: ₹{calculateTotalAmount(item)}
+
+
+ );
+
+ return (
+
+
+
+ item.tenderId.toString()}
+ contentContainerStyle={styles.container}
+ />
+
+
+
+ handlePurchase('online')}
+ >
+ Purchase Online
+
+
+ handlePurchase('offline')}
+ >
+ Purchase Offline
+
+
+
+
+ {loading && (
+
+
+
+ )}
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/Screens/Tender/PurchaseTenderAllList.tsx b/src/Screens/Tender/PurchaseTenderAllList.tsx
new file mode 100644
index 0000000..59d1e0d
--- /dev/null
+++ b/src/Screens/Tender/PurchaseTenderAllList.tsx
@@ -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({ 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 (
+
+
+
+ );
+ }, [isFetchingNextPage]);
+
+ return (
+
+ { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
+
+ {showRefreshBanner && (
+
+
+
+ Refreshing tenders
+
+
+ )}
+ {isLoading ?
+ : String(item.tenderId || item.id)}
+ showsVerticalScrollIndicator={false}
+ contentContainerStyle={{ paddingBottom: 200, paddingHorizontal: 10 }}
+ renderItem={({ item, index }) => (
+ {
+ 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 &&
+ { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: false })) }} onApply={() => { }} />
+ }
+
+
+ );
+};
+
+export default PurchaseTenderAllList;
+
diff --git a/src/Screens/Tender/PurchaseTenderList.tsx b/src/Screens/Tender/PurchaseTenderList.tsx
new file mode 100644
index 0000000..6771ab3
--- /dev/null
+++ b/src/Screens/Tender/PurchaseTenderList.tsx
@@ -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(null);
+ const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
+ const toggleExpand = (index: number) => {
+ setExpandedIndex(prev => (prev === index ? null : index));
+ };
+
+
+ return (
+
+
+
+
+ {/* Header */}
+
+ Tender Purchase List
+
+
+
+ {/* Cards */}
+ {SelectedPurchaseList.map((item: any, index: any) => {
+ const expanded = expandedIndex === index;
+ return (
+
+ toggleExpand(index)}
+ >
+
+
+
+ Tender No: {item.tenderNumber}
+
+
+
+ Status:{' '}
+
+ {item.tenderStatus === 'Opened' ? 'Open' : 'Approved'}
+
+
+
+
+ {/* Expanded Content */}
+ {expanded && (
+
+ {/* {renderRow('', item.departmentName)} */}
+ {/* {renderRow('Pattern', item.auctionPattern)} */}
+ {renderRow('Fees', `₹ ${item.documentFees}`)}
+ {renderRow('Department', item.departmentName)}
+ {renderRow('Purchase End', item.purchaseEnd)}
+
+ { Dispatch(togglePurchaseAuction(item)) }}>
+
+ Remove
+
+
+ )}
+
+ );
+ })}
+
+
+ {/* Footer */}
+ { Navigation.navigate(NavigationString.ProceedToPurTender) }}>
+ Proceed to Purchase
+
+
+ );
+};
+
+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 (
+
+ {title}
+
+
+
+
+ );
+};
+
+const renderRow = (label: string, value: string) => (
+
+ {label}
+ {value}
+
+);
+
+
+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',
+ },
+});
diff --git a/src/Screens/Tender/PurchaseTenderTile.tsx b/src/Screens/Tender/PurchaseTenderTile.tsx
new file mode 100644
index 0000000..30b79db
--- /dev/null
+++ b/src/Screens/Tender/PurchaseTenderTile.tsx
@@ -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();
+
+ 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 (
+
+
+
+
+
+ {/* Header */}
+
+
+
+
+
+ {isLive ? 'LIVE' : 'CLOSED'}
+
+
+ {item?.tenderNumber}
+
+
+
+
+ ₹{Number(item?.tenderFeeInFig || 0).toLocaleString('en-IN')}
+
+ Tender Fee
+
+
+
+ {/* Date */}
+ {
+ setShowDetail(true)
+ }}>
+
+ Bid Ends:
+ {moment(
+ item?.keydates?.bidSubmissionDate?.endDateTime,
+ 'DD-MM-YYYY HH:mm:ss'
+ ).format('DD MMM YYYY, hh:mm A')}
+
+
+
+ {/* Footer */}
+
+
+ Remaining Time
+
+ {isLive ? (
+
+ ) : (
+ --:--:--
+ )}
+
+
+ {
+ if (isLive) {
+ navigation.navigate('ProceedToBidDetail', { tender });
+ }
+ }}
+ activeOpacity={0.85}
+ style={[
+ styles.actionButton,
+ { backgroundColor: statusColor },
+ !isLive && styles.disabledButton,
+ ]}
+ >
+
+ {isLive ? 'Proceed to Bid' : 'Tender Closed'}
+
+
+
+
+ {/* Tender Detail Modal */}
+
+ setShowDetail(false)} />
+
+
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/Screens/Tender/Tender.tsx b/src/Screens/Tender/Tender.tsx
index f2d6c67..b293127 100644
--- a/src/Screens/Tender/Tender.tsx
+++ b/src/Screens/Tender/Tender.tsx
@@ -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 (
-
- Tender
-
- )
-}
+ 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
\ No newline at end of file
+ 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 (
+
+
+
+ {/* Switch Tabs */}
+
+
+
+
+ onTabPress('live', 0)}
+ >
+
+ Live ({liveCount})
+
+
+
+ onTabPress('purchase', 1)}
+ >
+
+ Purchased ({purchaseCount})
+
+
+
+ {/* Cart */}
+
+ {SelectedPurchaseList.length &&
+ {SelectedPurchaseList.length}
+ }
+ { Navigation.navigate(NavigationString.PurchaseTenderList) }}>
+
+
+
+
+ {selectedTab === 'live' ? : }
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/Screens/Tender/tenderBidding/TenderBidding.tsx b/src/Screens/Tender/tenderBidding/TenderBidding.tsx
new file mode 100644
index 0000000..53b7ec3
--- /dev/null
+++ b/src/Screens/Tender/tenderBidding/TenderBidding.tsx
@@ -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;
+ B: Record;
+ C: Record;
+};
+
+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(route.params?.tenderData || null);
+ const tenderData = route.params?.bidData || null;
+ const [activeEnvelope, setActiveEnvelope] = useState('A');
+ const [isLoading, setIsLoading] = useState(false);
+ const [envelopForm, setEnvelopForm] = useState({
+ A: {},
+ B: {},
+ C: {},
+ });
+ const [loading, setLoading] = useState(false)
+ const [loadingUpload, setLoadingUpload] = useState(false)
+ const [check, setCheck] = useState(false)
+
+
+ const InfoRow = ({ title, value }: InfoRowProps) => (
+
+ {title}
+ {value || '-'}
+
+ );
+
+ const EnvelopeButton = ({ title, envelopeType, onPress }: EnvelopeButtonProps & { envelopeType: EnvelopeType }) => (
+
+ {title}
+
+ );
+
+
+ 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 (
+
+
+
+ {/* Label Name */}
+ {labelName || '-'}
+ {item?.[`labelMandatory${activeEnvelope}`] && *}
+
+
+
+ {/*
+ Label Type
+ {labelType || '-'}
+ */}
+
+
+ {showInput ? (
+ <>
+ {labelType === '1' && (
+
+ handleEnvelopeChange(envelopeType, sequenceId, text)
+ }
+ keyboardType="numeric"
+ />
+ )}
+
+ {labelType === '2' && (
+
+ handleEnvelopeChange(envelopeType, sequenceId, text)
+ }
+ keyboardType="default"
+ />
+ )}
+ {labelType === '3' && (
+ <>
+ pickPdf(activeEnvelope, sequenceId)}
+ >
+
+ {envelopForm?.[activeEnvelope]?.[sequenceId]
+ ? 'Change Document'
+ : 'Upload Document'}
+
+
+
+ {envelopForm?.[activeEnvelope]?.[sequenceId] && (
+
+ {(envelopForm[activeEnvelope][sequenceId] as any)?.name}
+
+ )}
+
+ >
+ )}
+
+ {labelType === '4' && (
+
+ handleEnvelopeChange(envelopeType, sequenceId, text)
+ }
+ keyboardType="numeric"
+ />
+ )}
+
+ >
+ ) : Envelop {activeEnvelope} unavailable}
+
+ );
+ };
+
+ 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 = {
+ '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 (
+ <>
+
+ Loading...
+
+ >
+ );
+ }
+
+ // console.log("route for date : ", bidData)
+ // console.log("bid envelop : ", tenderData)
+ return (
+
+
+
+ {/* Tender Info */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Envelope Buttons */}
+
+ Bid Envelopes
+
+ {tenderData?.showEA && (
+ setActiveEnvelope('A')}
+ />
+ )}
+ {tenderData?.showEB && (
+ {
+ if (tenderData?.envelopeASubmittStatus) {
+ setActiveEnvelope('B')
+ } else {
+ Alert.alert('Info', 'First Fill Envelop A First')
+ }
+ }}
+ />
+ )}
+ {tenderData?.showEC && (
+ setActiveEnvelope('C')}
+ />
+ )}
+
+
+ {/* Envelope A List */}
+ {tenderData?.[`tenderEnvelope${activeEnvelope}List`]?.length > 0 && activeEnvelope !== 'C' && (
+
+ Envelope {activeEnvelope} Details
+ item?.[`tenderEnvelope${activeEnvelope}SequnceId`]?.toString()}
+ renderItem={({ item }) => renderEnvelopeItem({ item, envelopeType: activeEnvelope })}
+ scrollEnabled={false}
+ />
+ {/*
+ setCheck(!check)}>
+ {check ? :
+
+ }
+ Check First Terms&Condition
+ */}
+
+ )}
+
+ {hasEnvelopes() ? (
+ {
+ if (!check) {
+ Alert.alert('Info', 'check first terms & condition')
+ }
+ if (activeEnvelope === 'A') {
+ handleSubmitEnvelopeA()
+ } else {
+ handleSubmitEnvelopeB()
+ }
+ }}
+ >
+ {loading ? 'Submitting...' : `Submit Bid (${activeEnvelope})`}
+
+ ) : (
+ navigation.goBack()}
+
+ >
+ Go Back
+
+ )}
+
+
+
+
+ );
+};
+
+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,
+ },
+});
\ No newline at end of file
diff --git a/src/Screens/Tender/types/types.tsx b/src/Screens/Tender/types/types.tsx
new file mode 100644
index 0000000..5ec8e48
--- /dev/null
+++ b/src/Screens/Tender/types/types.tsx
@@ -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
+}
\ No newline at end of file
diff --git a/src/Screens/index.ts b/src/Screens/index.ts
index e5e7037..0133591 100644
--- a/src/Screens/index.ts
+++ b/src/Screens/index.ts
@@ -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
\ No newline at end of file
diff --git a/src/Services/BBPS/ApiUrls.tsx b/src/Services/BBPS/ApiUrls.tsx
index 270a659..bb6adc2 100644
--- a/src/Services/BBPS/ApiUrls.tsx
+++ b/src/Services/BBPS/ApiUrls.tsx
@@ -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
};
diff --git a/src/Services/BBPS/Hooks.ts b/src/Services/BBPS/Hooks.ts
index a362d0a..94ea64c 100644
--- a/src/Services/BBPS/Hooks.ts
+++ b/src/Services/BBPS/Hooks.ts
@@ -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 = () => {
diff --git a/src/Services/BBPS/apis.ts b/src/Services/BBPS/apis.ts
index ea7f34b..c82c2f8 100644
--- a/src/Services/BBPS/apis.ts
+++ b/src/Services/BBPS/apis.ts
@@ -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 => {
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 => {
return data
}
-
-export const fetchPurchaseTender = async (): Promise => {
- const data = await apiCall('get', `${GET_MY_TENDER}`);
- return data
-}
-export const fetchLiveTender = async (): Promise => {
- const data = await apiCall('get', `${LIVE_TENDER}`);
- return data
-}
export const fetchPurchaseAuction = async (): Promise => {
const data = await apiCall('get', `${GET_MY_AUCTION}`);
return data
@@ -73,6 +65,53 @@ export const fetchAuctionItem = async (payload: any): Promise => {
}
};
+// Tender Purchase offline
+export const offlineTenderPurchase = async (payload: any): Promise => {
+ try {
+ const response = await apiCall('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(
+ 'get',
+ `${GET_MY_TENDER}?page=${page}&size=${size}`
+ );
+};
+
+
+
+export const fetchLiveTender = async (): Promise => {
+ const data = await apiCall('get', `${LIVE_TENDER}`);
+ return data
+}
+export const fetchTenderById = async (tenderId: string): Promise => {
+ const data = await apiCall(
+ 'get',
+ `${GET_TENDER_DETAILS}${tenderId}`
+ );
+ return data;
+};
+
+export const proceedToBid = async (tenderId: string): Promise => {
+ const data = await apiCall(
+ 'get',
+ `${PROCEED_TO_BID}${tenderId}`
+ );
+ return data;
+}
+
+
export const fetchPurchaseAuctionList = async (payload: any): Promise => {
const data = await apiCall('post', `${PURCHASE_AUCTION_LIST}`, payload);