pre merge

This commit is contained in:
Abin Toppo
2026-01-14 13:37:51 +05:30
parent efec7d480f
commit 4b0717ff6d
19 changed files with 13598 additions and 509 deletions
+12583
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -36,6 +36,7 @@
"react-native-blob-util": "^0.24.6",
"react-native-bouncy-checkbox": "^4.1.2",
"react-native-date-picker": "^5.0.13",
"react-native-element-dropdown": "^2.12.4",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.30.0",
"react-native-gifted-charts": "^1.4.70",
+3 -22
View File
@@ -11,39 +11,20 @@ export const apiCall = async <R, D = {}>(
): Promise<R> => {
try {
const token: any = store.getState().token.token;
console.log("url : ", url)
console.log("token : ", token)
// Generate curl command
let curlCmd = `curl -X ${method.toUpperCase()} "${url}"`;
if (token?.token) curlCmd += ` -H "Authentication: Bearer ${token.token}"`;
curlCmd += ` -H "Content-Type: ${contentType || 'application/json'}"`;
if (data) {
if (contentType === 'multipart/form-data') {
curlCmd += ` --form "${JSON.stringify(data).slice(1,-1).replace(/"/g, '\\"')}"`;
} else {
curlCmd += ` -d '${JSON.stringify(data)}'`;
}
}
if (params) {
curlCmd += ` ${Object.entries(params).map(([k,v]) => `-${k}=${v}`).join(' ')}`;
}
console.log("=== CURL ===");
console.log(curlCmd);
console.log("============");
console.log("abin-------",url)
const response = await axios({
method,
url,
data,
params,
// withCredentials: true,
// baseURL: Variable.Main_Base,
headers: {
Authentication: `Bearer ${token?.token}`,
'Content-Type': contentType || 'application/json',
},
});
console.log("abin-------",response)
return response.data;
} catch (error: any) {
+1 -1
View File
@@ -12,7 +12,7 @@ import * as LucideIcons from 'lucide-react-native';
import Screens from '../Screens';
import colors from '../Constant/Color';
import NavigationString from '../Constant/NavigationString';
import EditableExcel from '../Screens/Tender/Component/EditableExcel';
import EditableExcel from '../Screens/Tender/Component/EnvelopeCExcelPreview';
const { width } = Dimensions.get('window');
const TAB_BAR_HEIGHT = 60;
+2 -3
View File
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native';
import { pick } from '@react-native-documents/picker';
import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated';
// import BouncyCheckbox from 'react-native-bouncy-checkbox';
import BouncyCheckbox from 'react-native-bouncy-checkbox';
import { InsideHeader, Loader, Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
@@ -94,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}>
+2 -4
View File
@@ -24,6 +24,7 @@ const Splash: React.FC = () => {
const isFocused = useIsFocused();
const fadeAnim = useRef(new Animated.Value(0)).current;
const reduxToken = useSelector((state: any) => state.token?.token);
console.log("user Token",reduxToken)
const store = useStore();
const { mutateAsync: registrationStateMutate } = useRagistrationState();
const [isHydrated, setIsHydrated] = useState(false);
@@ -49,7 +50,6 @@ const Splash: React.FC = () => {
const routeByOrgType = (params: { otherId: any; orgType: any }) => {
const orgType = String(params.orgType);
console.log("hitting here ")
if (orgType === '4' || orgType === '5' || orgType === '6') {
navigation.replace('StageThree', params);
return;
@@ -66,8 +66,7 @@ const Splash: React.FC = () => {
const handleGetData = async () => {
try {
const res = await registrationStateMutate();
console.log('Registration status response:', res);
console.log("ragister",res)
if (res?.statusCode === 404 || res?.statusCode === 401) {
navigation.replace('Registration');
return;
@@ -96,7 +95,6 @@ const Splash: React.FC = () => {
}
return;
}
console.error('API error:', res);
showToast(res?.message || 'Something went wrong');
navigation.replace('Registration');
@@ -1,243 +0,0 @@
import React, { useEffect, useState } from 'react';
import { View, Text, TextInput, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, } from 'react-native';
import RNFS from 'react-native-fs';
import * as XLSX from 'xlsx';
import axios from 'axios';
import { useSelector } from 'react-redux';
import { InsideHeader } from '../../../Component/Index';
import colors from '../../../Constant/Color';
const EXCEL_API_URL = 'https://procurelinc.in/EProcurementSB/eTendering/getEnvelopeCTemplate/tenderId?tenderId=2479';
// Excel column keys
const QTY_KEY = '__EMPTY_1';
const RATE_KEY = '__EMPTY_2';
const VALUE_KEY = '__EMPTY_3';
const TOTAL_LABEL = 'TOTAL';
const EnvelopeCExcelPreview = () => {
const [data, setData] = useState<any[]>([]);
const [columns, setColumns] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const token = useSelector((state: any) => state.token.token);
useEffect(() => {
loadExcelFromApi();
}, []);
// ArrayBuffer → Base64
const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
let binary = '';
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return global.btoa(binary);
};
// Calculate TOTAL VALUE
const calculateTotal = (rows: any[]) =>
rows.reduce((sum, row) => {
if (typeof row['DEMO DEPARTEMENT'] !== 'number') return sum;
const val = Number(row[VALUE_KEY]);
return sum + (isNaN(val) ? 0 : val);
}, 0);
// LOAD EXCEL
const loadExcelFromApi = async () => {
try {
setLoading(true);
const response = await axios.get(EXCEL_API_URL, {
responseType: 'arraybuffer',
headers: {
Authentication: `Bearer ${token.token}`,
},
});
const base64 = arrayBufferToBase64(response.data);
const workbook = XLSX.read(base64, { type: 'base64' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });
setColumns(Object.keys(rows[0] || {}));
setData(rows);
await RNFS.writeFile(
`${RNFS.DocumentDirectoryPath}/envelopeC.xlsx`,
base64,
'base64'
);
} catch (e) {
Alert.alert('Error', 'Failed to load Excel');
} finally {
setLoading(false);
}
};
// UPDATE CELL (QTY / RATE) + AUTO VALUE + AUTO TOTAL
const updateCell = (rowIndex: number, key: string, value: string) => {
const updated = [...data];
const row = updated[rowIndex];
// TOTAL row locked
if (row['DEMO DEPARTEMENT'] === TOTAL_LABEL) return;
// Allow only QTY & RATE edit
if (key === QTY_KEY || key === RATE_KEY) {
row[key] = value;
const qty = Number(row[QTY_KEY]);
const rate = Number(row[RATE_KEY]);
// 🔥 Auto VALUE = QTY × RATE
row[VALUE_KEY] =
!isNaN(qty) && !isNaN(rate) ? qty * rate : 0;
}
// 🔥 Update TOTAL
const totalRowIndex = updated.findIndex(
r => r['DEMO DEPARTEMENT'] === TOTAL_LABEL
);
if (totalRowIndex !== -1) {
updated[totalRowIndex][VALUE_KEY] = calculateTotal(updated);
}
setData(updated);
};
// SAVE UPDATED EXCEL
const saveUpdatedExcel = async () => {
try {
setLoading(true);
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const base64 = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'base64',
});
await RNFS.writeFile(
`${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`,
base64,
'base64'
);
Alert.alert('Success', 'Excel saved with auto-calculation');
} catch (e) {
Alert.alert('Error', 'Save failed');
} finally {
setLoading(false);
}
};
if (loading) {
return (
<View style={styles.loader}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<View style={styles.container}>
<InsideHeader title='Envelope-C Excel Preview' />
<ScrollView horizontal contentContainerStyle={{ paddingHorizontal: 10, paddingTop: 20, backgroundColor: colors.white }}>
<View>
{/* HEADER */}
<View style={styles.row}>
{columns.map(col => (
<Text key={col} style={styles.headerCell}>
{col}
</Text>
))}
</View>
{/* DATA */}
<ScrollView style={{ maxHeight: 420 }}>
{data.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}>
{columns.map(key => {
const isTotal = row['DEMO DEPARTEMENT'] === TOTAL_LABEL;
const isEditable =
!isTotal && (key === QTY_KEY || key === RATE_KEY);
return (
<TextInput
key={key}
value={String(row[key])}
editable={isEditable}
keyboardType={
key === QTY_KEY || key === RATE_KEY
? 'numeric'
: 'default'
}
style={[
styles.cell,
isTotal && styles.totalCell,
key === VALUE_KEY && styles.valueCell,
]}
onChangeText={text =>
updateCell(rowIndex, key, text)
}
/>
);
})}
</View>
))}
</ScrollView>
</View>
</ScrollView>
<TouchableOpacity style={styles.button} onPress={saveUpdatedExcel}>
<Text style={styles.buttonText}>Save Updated Excel</Text>
</TouchableOpacity>
</View>
);
};
export default EnvelopeCExcelPreview;
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
loader: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 18, fontWeight: '700', marginBottom: 10 },
row: { flexDirection: 'row' },
headerCell: {
minWidth: 140,
padding: 10,
backgroundColor: '#f1f5f9',
borderWidth: 1,
fontWeight: '700',
},
cell: {
minWidth: 140,
padding: 10,
borderWidth: 1,
borderColor: '#cbd5e1',
},
valueCell: {
backgroundColor: '#f8fafc',
},
totalCell: {
backgroundColor: '#e5e7eb',
fontWeight: '700',
},
button: {
marginTop: 15,
backgroundColor: '#2563eb',
padding: 14,
borderRadius: 8,
},
buttonText: {
color: '#fff',
textAlign: 'center',
fontWeight: '600',
},
});
+4
View File
@@ -13,8 +13,11 @@ import LiveTenderTile from './LiveTenderTile';
const LiveTender = () => {
const navigation: any = useNavigation();
const Dispatch = useDispatch()
const user = useSelector((state: any) => state?.token.token);
console.log("userdata",user)
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender();
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
useEffect(() => {
@@ -48,6 +51,7 @@ const LiveTender = () => {
({ item, index }: any) => {
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
return <LiveTenderTile
contractorId={user?.id}
data={item}
index={index}
tenderId={item.tenderId}
+454 -13
View File
@@ -1,18 +1,459 @@
import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
import { useRoute } from '@react-navigation/native';
import React, { useState } from 'react';
import {
Alert,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
StatusBar,
SafeAreaView,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import {
useFetchLiveTenderById,
} from '../../Services/BBPS/Hooks';
import { InsideHeader } from '../../Component/Index';
import colors from '../../Constant/Color';
import ReactNativeBlobUtil from 'react-native-blob-util';
import { GET_DOCID } from '../../Services/BBPS/ApiUrls';
import {
Layers,
Briefcase,
FileText,
Calendar,
ArrowLeft,
Download,
} from 'lucide-react-native';
import moment from 'moment';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const InfoRow = ({ label, value }: any) => (
<View style={styles.row}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value || '-'}</Text>
</View>
);
const Section = ({ title, icon: Icon, children }: any) => (
<View style={styles.sectionContainer}>
<View style={styles.sectionHeader}>
<Icon size={18} color={colors.primary} />
<Text style={styles.sectionTitle}>{title}</Text>
</View>
{children}
</View>
);
const LiveTenderDetail = () => {
const route = useRoute<any>();
const { tender } = route.params;
console.log("live tender details : ", tender)
return (
<View>
<Text>LiveTenderDetail</Text>
const route = useRoute<any>();
const navigation = useNavigation<any>();
const tender = route?.params;
console.log("object---",tender)
const { data } = useFetchLiveTenderById(tender?.tenderId);
const [downloadingId, setDownloadingId] = useState<string | number | null>(null);
const insets = useSafeAreaInsets()
if (!data?.data?.[0]) return null;
const tenderData = data.data[0];
const handleDownload = async (doc: any) => {
try {
setDownloadingId(doc?.docId);
const { dirs } = ReactNativeBlobUtil.fs;
const path = `${dirs.DownloadDir}/${doc?.fileName}.pdf`;
const downloadUrl = `${GET_DOCID}${doc?.docId}`;
const response = await ReactNativeBlobUtil.config({
fileCache: false,
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
path,
mime: 'application/pdf',
title: `${doc?.fileName}.pdf`,
description: 'Downloading document',
mediaScannable: true,
},
}).fetch('GET', downloadUrl, {
Accept: 'application/pdf',
});
const stat = await ReactNativeBlobUtil.fs.stat(path);
Alert.alert('Download complete', `File saved to: ${path}`);
setDownloadingId(null);
} catch (error: any) {
setDownloadingId(null);
Alert.alert('Error', error?.message || 'Something went wrong while downloading.');
}
};
const isLive =
tenderData?.tenderStatus?.toLowerCase() === 'live' ||
tenderData?.tenderStatus?.toLowerCase() === 'open';
const formatDateTime = (val?: string) => {
if (!val) return '-';
return moment(val, 'DD-MM-YYYY HH:mm:ss').isValid()
? moment(val, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A')
: val;
};
return (
<View style={styles.safeArea}>
<StatusBar barStyle="dark-content" />
<View style={[styles.header,{marginTop:insets.top}]}>
<TouchableOpacity
onPress={() => navigation.goBack()}
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: {tenderData?.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>
export default LiveTenderDetail
<ScrollView contentContainerStyle={styles.content}>
<Section title="Basic Details" icon={Layers}>
<View style={styles.card}>
<InfoRow label="Tender ID" value={tenderData?.tenderId} />
<InfoRow label="Tender Number" value={tenderData?.tenderNumber} />
<InfoRow label="NIT Number" value={tenderData?.nitNumber} />
<InfoRow label="NIT Date" value={tenderData?.nitDate} />
<InfoRow label="Name of Work" value={tenderData?.nameOfWork} />
<InfoRow label="Description" value={tenderData?.descriptionOfWork} />
<InfoRow label="Tender Stage" value={tenderData?.tenderStage} />
<InfoRow label="Tender Status" value={tenderData?.tenderStatus} />
</View>
</Section>
const styles = StyleSheet.create({})
<Section title="Category & Type" icon={Briefcase}>
<View style={styles.card}>
<InfoRow label="Tender Category" value={tenderData?.tenderCategory} />
<InfoRow label="Type of Tender" value={tenderData?.typeOfTender} />
<InfoRow label="Selective Tender" value={tenderData?.selectiveTender} />
<InfoRow label="Area of Work" value={tenderData?.areaOfWork} />
</View>
</Section>
<Section title="Financial Details" icon={FileText}>
<View style={styles.card}>
<InfoRow
label="Estimated Value"
value={tenderData?.estimateValueInFig ? `₹ ${tenderData.estimateValueInFig}` : '-'}
/>
<InfoRow
label="Estimated Value (Words)"
value={tenderData?.estimateValueInWord}
/>
<InfoRow
label="Tender Fee"
value={tenderData?.tenderFeeInFig ? `₹ ${tenderData.tenderFeeInFig}` : '-'}
/>
<InfoRow
label="EMD"
value={tenderData?.emdInFig ? `₹ ${tenderData.emdInFig}` : '-'}
/>
<InfoRow label="GST %" value={tenderData?.gst} />
<InfoRow label="GST Amount" value={tenderData?.gstAmmount} />
</View>
</Section>
<Section title="Work & Validity" icon={Briefcase}>
<View style={styles.card}>
<InfoRow
label="Bid Validity Type"
value={tenderData?.bidValidateType}
/>
<InfoRow
label="Bid Validity Period"
value={tenderData?.bidValidatePeriod}
/>
<InfoRow
label="Work Completion Type"
value={tenderData?.workCompletionType}
/>
<InfoRow
label="Work Completion Period"
value={tenderData?.workCompletionPeriod}
/>
<InfoRow label="Rainy Season" value={tenderData?.rainySeason} />
</View>
</Section>
<Section title="Key Dates" icon={Calendar}>
<View style={styles.card}>
<InfoRow
label="Purchase Start"
value={formatDateTime(
tenderData?.keydates?.tenderPurchaseDate?.startDateTime,
)}
/>
<InfoRow
label="Purchase End"
value={formatDateTime(
tenderData?.keydates?.tenderPurchaseDate?.endDateTime,
)}
/>
<InfoRow
label="Bid Submission Start"
value={formatDateTime(
tenderData?.keydates?.bidSubmissionDate?.startDateTime,
)}
/>
<InfoRow
label="Bid Submission End"
value={formatDateTime(
tenderData?.keydates?.bidSubmissionDate?.endDateTime,
)}
/>
<InfoRow
label="Opening Envelope A"
value={formatDateTime(tenderData?.keydates?.openingEA)}
/>
<InfoRow
label="Opening Envelope B"
value={formatDateTime(tenderData?.keydates?.openingEB)}
/>
<InfoRow
label="Opening Envelope C"
value={formatDateTime(tenderData?.keydates?.openingEC)}
/>
</View>
</Section>
<Section title="Envelope Configuration" icon={Layers}>
<View style={styles.card}>
<InfoRow
label="Envelope Type"
value={tenderData?.envelopeType}
/>
<InfoRow
label="Envelope A Enabled"
value={tenderData?.envelopeA ? 'Yes' : 'No'}
/>
<InfoRow
label="Envelope B Enabled"
value={tenderData?.envelopeB ? 'Yes' : 'No'}
/>
<InfoRow
label="Envelope C Enabled"
value={tenderData?.envelopeC ? 'Yes' : 'No'}
/>
</View>
</Section>
<Section title="Department & Location" icon={Layers}>
<View style={styles.card}>
<InfoRow label="Department" value={tenderData?.deptName} />
<InfoRow label="Circle" value={tenderData?.circle} />
<InfoRow
label="Division"
value={tenderData?.tenderSelectedDivision}
/>
<InfoRow
label="Sub Division"
value={tenderData?.tenderSelectedSubDivision}
/>
</View>
</Section>
<Section title="Officers" icon={Layers}>
<View style={styles.card}>
<InfoRow label="Tender Master" value={tenderData?.tenderMaster} />
{tenderData?.approvalOfficerNameEmail?.map((o: any, i: number) => (
<InfoRow
key={i}
label={`Approval Officer ${i + 1}`}
value={`${o.name} (${o.email})`}
/>
))}
</View>
</Section>
<Section title="Documents" icon={Download}>
<View style={styles.card}>
{tenderData?.tenderDocuments?.map((doc: any, i: number) => (
<View key={i} style={styles.documentRow}>
<View style={styles.docInfo}>
<View style={styles.fileIcon}>
<FileText size={18} color={colors.primary} />
</View>
<Text style={styles.docName}>{doc.fileName}</Text>
</View>
<TouchableOpacity
style={styles.downloadBtn}
onPress={() => handleDownload(doc)}
disabled={downloadingId === doc?.docId}
>
<Download
size={18}
color={
downloadingId === doc?.docId ? '#9ca3af' : colors.primary
}
/>
<Text style={styles.downloadText}>
{downloadingId === doc?.docId ? 'Downloading...' : 'Download'}
</Text>
</TouchableOpacity>
</View>
))}
</View>
</Section>
</ScrollView>
</View>
);
};
export default LiveTenderDetail;
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal:10,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
backgroundColor: '#fff',
},
backBtn: {
padding: 8,
marginRight: 8,
},
headerTitle: {
fontSize: 18,
fontWeight: '800',
color: '#0f172a',
},
headerSub: {
fontSize: 13,
color: '#64748b',
},
liveBox: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 20,
borderWidth: 1,
borderColor: '#e2e8f0',
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
},
liveText: {
fontSize: 11,
fontWeight: '800',
color: '#475569',
},
content: {
padding: 16,
paddingBottom: 32,
},
sectionContainer: {
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 16,
marginBottom: 16,
borderWidth: 1,
borderColor: '#e2e8f0',
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
marginBottom: 12,
paddingBottom: 6,
borderBottomWidth: 1,
borderBottomColor: '#f8fafc',
},
sectionTitle: {
fontSize: 14,
fontWeight: '800',
color: '#1e293b',
textTransform: 'uppercase',
letterSpacing: 0.4,
},
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
paddingVertical: 8,
borderBottomWidth: 0.5,
borderBottomColor: '#e5e7eb',
},
label: {
fontSize: 13,
color: '#64748b',
flex: 1,
paddingRight: 8,
fontWeight: '600',
},
value: {
fontSize: 13,
color: '#111827',
flex: 1,
textAlign: 'right',
fontWeight: '500',
},
documentRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
gap: 10,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
docInfo: {
flexDirection: 'row',
alignItems: 'center',
flex: 1.4,
},
fileIcon: {
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: '#e0f2fe',
alignItems: 'center',
justifyContent: 'center',
marginRight: 8,
},
docName: {
flex: 1,
color: '#111827',
fontSize: 13,
fontWeight: '600',
},
downloadBtn: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
downloadText: {
fontSize: 13,
fontWeight: '700',
color: colors.primary,
},
});
+16 -8
View File
@@ -9,6 +9,7 @@ import TenderFullDetail from './Modal/TenderFullDetail';
import { useNavigation } from '@react-navigation/native';
const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
contractorId,
data,
isPurchased = false,
onPurchasePress,
@@ -22,14 +23,21 @@ const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
const navigation: any = useNavigation();
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
console.log("live tender data : ", data)
const contractorIds = typeof data?.contractorId === 'string'
? data.contractorId.split(',').map((id:any) => id.trim())
: Array.isArray(data?.contractorId)
? data.contractorId
: [];
const purchased = contractorIds.includes(String(contractorId));
return (
<View style={styles.cardWrapper}>
{/* Status Tag */}
<View
style={[
styles.statusTag,
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : (purchased?'#94a3b8':'#4ade80') },
]}
/>
@@ -75,7 +83,7 @@ const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
{/* Action Bar */}
<View style={styles.actionBar}>
<TouchableOpacity style={styles.iconBtn} onPress={onDetailsPress}>
<TouchableOpacity style={styles.iconBtn} onPress={()=>navigation.navigate('LiveTenderDetail', data)}>
<Info size={18} color={colors.primary} />
<Text style={styles.iconBtnText}>Details</Text>
</TouchableOpacity>
@@ -85,12 +93,12 @@ const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
styles.mainButton,
purchaseEnded
? styles.btnDisabled
: isPurchased
:(purchased? styles.btnDisabled: isPurchased
? styles.btnPurchased
: styles.btnActive,
: styles.btnActive),
]}
onPress={onPurchasePress}
disabled={purchaseEnded}
disabled={purchaseEnded || purchased}
>
<Text
style={[
@@ -98,9 +106,9 @@ const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
]}
>
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
{purchaseEnded ? 'Expired' : (purchased?'Purchased':isPurchased ? 'Remove' : 'Add to Cart')}
</Text>
{!purchaseEnded && (
{(!purchaseEnded && !purchased) && (
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
)}
</TouchableOpacity>
+428 -145
View File
@@ -1,19 +1,19 @@
import { StyleSheet, Text, View, ScrollView, StatusBar, TouchableOpacity, RefreshControl } from 'react-native'
import React, { useEffect } from 'react'
import { StyleSheet, Text, View, ScrollView, StatusBar, TouchableOpacity, RefreshControl, ActivityIndicator } from 'react-native'
import React, { useCallback, useEffect } from 'react'
import { InsideHeader } from '../../Component/Index'
import colors from '../../Constant/Color'
import { useNavigation, useRoute } from '@react-navigation/native'
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native'
import { useFetchTenderById, useProceedToBid } from '../../Services/BBPS/Hooks'
import FontsFamily from '../../Constant/FontsFamily'
const ProceedToBidDetail = () => {
// Sample data based on your JSON
const route = useRoute<any>();
const tenderDataId = route.params?.tender;
const { data, refetch } = useProceedToBid(tenderDataId?.tenderId);
const { data: tenderResponse , refetch:tenderRefetch}: any = useFetchTenderById(tenderDataId?.tenderId);
const { data: tenderResponse, refetch: tenderRefetch }: any = useFetchTenderById(tenderDataId?.tenderId);
const tenderData = tenderResponse?.data;
const [refreshing, setRefreshing] = React.useState(false);
const [loading, setLoading] = React.useState(false);
console.log("proceed to bid tender detail : ", tenderDataId)
console.log("proceed to data detail : ", data)
@@ -22,18 +22,31 @@ const ProceedToBidDetail = () => {
// Format date from array
const handleStartBidding = () => {
refetch();
if (data) {
console.log(" ppp",tenderData ,"ll",data)
navigation.navigate('TenderBidding', { tenderData: tenderData, bidData: data.data });
try {
setLoading(true);
refetch();
if (data) {
console.log(" ppp", tenderData, "ll", data)
navigation.navigate('TenderBidding', { tenderData: tenderData, bidData: data.data });
}
} catch (error) {
console.log("Error in starting bidding: ", error);
} finally {
setLoading(false);
}
}
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 }: { status: boolean; label: string }) => (
<View style={styles.statusContainer}>
<View style={[styles.statusDot, { backgroundColor: status ? colors.success : colors.error }]} />
<Text style={[styles.statusText, { color: status ? colors.success : colors.error, fontFamily: FontsFamily.poppinsSemiBold }]}>
<Text style={[styles.statusText, { color: status ? colors.success : colors.error }]}>
{label}: {status ? "Submitted" : "Not Submitted"}
</Text>
</View>
@@ -49,53 +62,52 @@ const ProceedToBidDetail = () => {
</View>
</View>
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>Activity:</Text>
<Text style={[styles.value, { fontFamily: FontsFamily.poppinsRegular }]}>{log?.activity}</Text>
</View>
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>Date & Time:</Text>
<Text style={[styles.value, { fontFamily: FontsFamily.poppinsRegular }]}>{format.date6(log?.dateTime)}</Text>
</View>
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>System Info:</Text>
<Text style={[styles.value, { fontFamily: FontsFamily.poppinsRegular }]}>{log?.osName} / {log?.browserName}</Text>
</View>
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>IP Address:</Text>
<Text style={[styles.value, { fontFamily: FontsFamily.poppinsRegular }]}>{log?.ipAddress}</Text>
</View>
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>Status Message:</Text>
<Text style={[styles.value, styles.successText, { fontFamily: FontsFamily.poppinsSemiBold }]}>{log?.logMessage}</Text>
</View>
{log?.headingMsg && (
<View style={styles.logDetailRow}>
<Text style={[styles.label, { fontFamily: FontsFamily.poppinsMedium }]}>Heading:</Text>
<Text style={[styles.value, { fontFamily: FontsFamily.poppinsRegular }]}>{log?.headingMsg}</Text>
</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 }: { label: string; value: string; isHighlighted?: boolean }) => (
<View style={styles.infoRow}>
<Text style={[styles.infoLabel, { fontFamily: FontsFamily.poppinsMedium }]}>{label}:</Text>
<Text style={[styles.infoValue, isHighlighted && styles.highlightedValue, { fontFamily: FontsFamily.poppinsSemiBold }]}>{value}</Text>
<Text style={styles.infoLabel}>{label}:</Text>
<Text style={[styles.infoValue, isHighlighted && styles.highlightedValue]}>{value}</Text>
</View>
);
// Feature chip component
const FeatureChip = ({ label, enabled }: { label: string; enabled: boolean }) => (
<View style={[styles.chip, { backgroundColor: enabled ? colors.successLight : colors.lightGray }]}>
<Text style={[styles.chipText, { color: enabled ? colors.success : colors.darkGray, fontFamily: FontsFamily.poppinsSemiBold }]}>
<Text style={[styles.chipText, { color: enabled ? colors.success : colors.darkGray }]}>
{label} {enabled ? '✓' : '✗'}
</Text>
</View>
@@ -110,9 +122,11 @@ const ProceedToBidDetail = () => {
}
};
useEffect(()=>{
tenderRefetch()
},[tenderDataId?.tenderId])
useFocusEffect(
useCallback(() => {
tenderRefetch();
}, [tenderData.tenderId])
);
return (
<View style={{ flex: 1, backgroundColor: colors.white }}>
@@ -131,34 +145,34 @@ const ProceedToBidDetail = () => {
<View style={styles.headerCard}>
<View style={styles.tenderHeader}>
<View>
<Text style={[styles.tenderNumber, { fontFamily: FontsFamily.poppinsSemiBold }]}>{tenderData.tenderNumber}</Text>
<Text style={[styles.tenderId, { fontFamily: FontsFamily.poppinsRegular }]}>ID: {tenderData.tenderId}</Text>
<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, { fontFamily: FontsFamily.poppinsSemiBold }]}>
{tenderData.biddingStatus ? "Active" : "Inactive"}
<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, { fontFamily: FontsFamily.poppinsRegular }]} numberOfLines={1}>{tenderData.nameofWork}</Text>
<Text style={styles.workDescription}>{tenderData?.nameofWork}</Text>
<View style={styles.headerInfoRow}>
<View style={styles.headerInfoItem}>
<Text style={[styles.headerInfoLabel, { fontFamily: FontsFamily.poppinsMedium }]}>NIT No.</Text>
<Text style={[styles.headerInfoValue, { fontFamily: FontsFamily.poppinsSemiBold }]}>{tenderData.nitNo}</Text>
<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, { fontFamily: FontsFamily.poppinsMedium }]}>EMD Amount</Text>
<Text style={[styles.headerInfoValue, { fontFamily: FontsFamily.poppinsSemiBold }]}>₹{tenderData.emdInFig}</Text>
<Text style={styles.headerInfoLabel}>EMD Amount</Text>
<Text style={styles.headerInfoValue}>₹{tenderData?.emdInFig}</Text>
</View>
</View>
</View>
{/* Envelope Status */}
{/* Envelope Submission Status */}
<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>{`Envelope Submission Status`}</Text>
<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" />
@@ -166,19 +180,19 @@ const ProceedToBidDetail = () => {
</View>
</View>
{/* Logs */}
{tenderData.envelopeA_Logs && (
{/* Envelope Logs */}
{tenderData?.envelopeA_Logs && (
<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>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" />
<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, { color: colors.black }]}>Tender Details</Text>
<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} />
@@ -188,24 +202,24 @@ const ProceedToBidDetail = () => {
</View>
</View>
{/* Submission Time */}
{/* Time Period */}
<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>Submission Time Period</Text>
<Text style={styles.sectionTitle}>Submission Time Period</Text>
<View style={styles.timeCard}>
<View style={styles.timeRow}>
<Text style={[styles.timeLabel, { fontFamily: FontsFamily.poppinsSemiBold }]}>Starts On:</Text>
<Text style={[styles.timeValue, { fontFamily: FontsFamily.poppinsSemiBold }]}>{format.date6(tenderData.submittedStartDate)}</Text>
<Text style={styles.timeLabel}>Starts On:</Text>
<Text style={styles.timeValue}>{formatDate(tenderData?.submittedStartDate)}</Text>
</View>
<View style={styles.timeRow}>
<Text style={[styles.timeLabel, { fontFamily: FontsFamily.poppinsSemiBold }]}>Ends On:</Text>
<Text style={[styles.timeValue, { fontFamily: FontsFamily.poppinsSemiBold }]}>{format.date6(tenderData.submittedEndDate)}</Text>
<Text style={styles.timeLabel}>Ends On:</Text>
<Text style={styles.timeValue}>{formatDate(tenderData?.submittedEndDate)}</Text>
</View>
</View>
</View>
{/* Feature Chips */}
{/* Feature Flags */}
<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.black }]}>Available Features</Text>
<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} />
@@ -216,93 +230,362 @@ const ProceedToBidDetail = () => {
</View>
</View>
{/* Overall Status */}
{/* Final Status */}
<View style={styles.finalStatusCard}>
<Text style={[styles.finalStatusTitle, { fontFamily: FontsFamily.poppinsSemiBold }]}>Overall Status</Text>
<Text style={styles.finalStatusTitle}>Overall Status</Text>
<View style={styles.finalStatusRow}>
<Text style={[styles.finalStatusLabel, { fontFamily: FontsFamily.poppinsMedium }]}>All Envelopes Submitted:</Text>
<Text style={[styles.finalStatusValue, {
color: tenderData.envelopeASubmittStatus && tenderData.envelopeBSubmittStatus && tenderData.envelopeCSubmittStatus
? colors.success : colors.error, fontFamily: FontsFamily.poppinsSemiBold
<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"}
{tenderData?.envelopeASubmittStatus && tenderData?.envelopeBSubmittStatus && tenderData?.envelopeCSubmittStatus
? "COMPLETED" : "PENDING"}
</Text>
</View>
<View style={styles.finalStatusRow}>
<Text style={[styles.finalStatusLabel, { fontFamily: FontsFamily.poppinsMedium }]}>Bidding Status:</Text>
<Text style={[styles.finalStatusValue, { color: tenderData.biddingStatus ? colors.success : colors.error, fontFamily: FontsFamily.poppinsSemiBold }]}>
{tenderData.biddingStatus ? "ACTIVE" : "CLOSED"}
<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>
{/* Footer */}
<View style={styles.footer}>
{
tenderData?.proceedtoBid && !tenderData?.viewBid && tenderData?.biddingStatus && tenderData?.bidWithdrawStatus === false ? (
<TouchableOpacity style={styles.proceedButton} onPress={handleStartBidding}>
<Text style={[styles.proceedButtonText, { fontFamily: FontsFamily.poppinsSemiBold }]}>Start Bidding</Text>
<Text style={styles.proceedButtonText}>{loading ? <ActivityIndicator size={'small'} color={'#ffff'}/> : 'Start Bidding'}</Text>
</TouchableOpacity>
) : <Text style={[styles.footerText, { fontFamily: FontsFamily.poppinsMedium }]}>One time bidding is completed</Text>
}
</View>
) : <Text style={styles.footerText}>One time bidding is completed</Text>
} </View>
</View>
);
};
export default ProceedToBidDetail;
)
}
export default ProceedToBidDetail
// Styles remain mostly unchanged but improved shadows, spacing, and rounded corners
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
headerCard: { backgroundColor: colors.white, borderRadius: 14, padding: 20, marginBottom: 20, borderLeftWidth: 4, borderLeftColor: colors.primary, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 5, elevation: 5 },
tenderHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', 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: 13, color: colors.black, marginBottom: 16, fontFamily: FontsFamily.poppinsMedium },
headerInfoRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.lightestGray, 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', marginBottom: 12, paddingLeft: 4 },
statusGrid: { backgroundColor: colors.white, borderRadius: 14, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 5, 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: 14, padding: 16, marginBottom: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 4, elevation: 2 },
cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 },
cardTitle: { fontSize: 16, fontWeight: '600', color: colors.black },
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.black, flex: 1, fontFamily: FontsFamily.poppinsSemiBold },
value: { fontSize: 14, color: colors.darkText, flex: 2, textAlign: 'right' },
successText: { color: colors.success, fontWeight: '600' },
detailsCard: { backgroundColor: colors.white, borderRadius: 14, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 4, elevation: 3 },
infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: colors.lightestGray },
infoLabel: { fontSize: 14, color: colors.black, fontFamily: FontsFamily.poppinsSemiBold },
infoValue: { fontSize: 15, fontWeight: '500', color: colors.darkText },
highlightedValue: { color: colors.success, fontWeight: '600' },
timeCard: { backgroundColor: colors.white, borderRadius: 14, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 4, elevation: 3 },
timeRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.lightestGray },
timeLabel: { fontSize: 15, color: colors.black, },
timeValue: { fontSize: 15, color: colors.grayText, },
chipsContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
chip: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, marginBottom: 8 },
chipText: { fontSize: 14, fontWeight: '500' },
finalStatusCard: { backgroundColor: colors.white, borderRadius: 14, padding: 20, marginTop: 10, borderWidth: 2, borderColor: colors.primary, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 5, elevation: 5 },
finalStatusTitle: { fontSize: 18, fontWeight: 'bold', 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: 14, color: colors.black, fontFamily: FontsFamily.poppinsSemiBold },
finalStatusValue: { fontSize: 13, },
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 },
});
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,
},
});
+3 -2
View File
@@ -22,7 +22,6 @@ const PurchaseTenderTile = ({ item, onPressDetails }: any) => {
const tender = tenderResponse?.data;
console.log("tender for bid : ", tender)
const handlePressIn = () => {
scale.value = withTiming(0.97, { duration: 120 });
elevation.value = withTiming(3, { duration: 120 });
@@ -105,10 +104,12 @@ const PurchaseTenderTile = ({ item, onPressDetails }: any) => {
</View>
<TouchableOpacity
disabled={!isLive}
// disabled={!isLive}
onPress={() => {
if (isLive) {
navigation.navigate('ProceedToBidDetail', { tender });
}else{
navigation.navigate('LiveTenderDetail', tender)
}
}}
activeOpacity={0.85}
+4 -2
View File
@@ -26,10 +26,12 @@ const TAB_HEIGHT = 44;
const Tender = () => {
const Navigation = useNavigation() as any
const { data: liveTenderData = {} }: any = useFetchLiveTender();
const { data: purchaseTenderData = {} } : any = useFetchPurchaseTender();
const { data: liveTenderData }: any = useFetchLiveTender();
const { data: purchaseTenderData} : any = useFetchPurchaseTender();
const SelectedPurchaseList = useSelector((state: any) => state?.selectPurchaseTender);
console.log("live tender : ", liveTenderData)
console.log("purchase tender : ", purchaseTenderData)
const liveCount = liveTenderData?.totalCount || 0;
const purchaseCount = purchaseTenderData?.pages?.[0]?.totalCount || 0;
@@ -18,9 +18,10 @@ import colors from '../../../Constant/Color';
import { SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_FILE_B } from '../../../Services/BBPS/ApiUrls';
import { CheckSquare, Square } from 'lucide-react-native';
import { apiCall } from '../../../Axios/Axios';
import EditableExcel from '../component/EditableExcel';
import axios from 'axios';
import { useSelector } from 'react-redux';
import EnvelopeCExcelPreview from '../Component/EnvelopeCExcelPreview';
type EnvelopeType = 'A' | 'B' | 'C';
type EnvelopeFormState = {
@@ -47,7 +48,6 @@ const TenderBidding = () => {
const [bidData] = useState<any>(route.params?.tenderData || null);
const tenderData: any = route.params?.bidData || null;
const [activeEnvelope, setActiveEnvelope] = useState<EnvelopeType>('A');
const [isLoading, setIsLoading] = useState(false);
const [envelopForm, setEnvelopForm] = useState<EnvelopeFormState>({
A: {},
B: {},
@@ -56,6 +56,7 @@ const TenderBidding = () => {
const [loading, setLoading] = useState(false)
const [loadingUpload, setLoadingUpload] = useState(false)
const [check, setCheck] = useState(false)
const [uploadFileResponse, setUploadFileResponse] = useState<any>(null);
const InfoRow = ({ title, value }: InfoRowProps) => (
@@ -383,57 +384,54 @@ const TenderBidding = () => {
}
setLoading(true)
const payload = buildEnvelopePayload();
console.log('Envelope B Payload:', payload);
if (envelopForm?.[activeEnvelope]?.[3]) {
handleSubmitEnvelopeFile(SUBMIT_ENVELOPE_FILE_B)
return
}
try {
const res = await apiCall('post', SUBMIT_ENVELOPE_B, payload) as any
console.log("res B envelop : ", res)
const result = await handleSubmitEnvelopeFile(SUBMIT_ENVELOPE_FILE_B)
ToastAndroid.show(res.message, ToastAndroid.SHORT)
navigation.goBack()
} catch (error) {
console.log("error B envelop submit : ", error)
} finally {
setLoading(false)
if (result && result?.statusCode === 200) {
try {
const res = await apiCall('post', SUBMIT_ENVELOPE_B, payload) as any
if (res?.statusCode === 200) {
navigation.goBack()
ToastAndroid.show(res.message, ToastAndroid.SHORT)
}
} catch (error) {
console.log("error B envelop submit : ", error)
} finally {
setLoading(false)
}
} else {
setLoading(false)
Alert.alert('Error', 'File upload failed. Please try again.')
}
}
}
const handleSubmitEnvelopeFile = async (url: string) => {
console.log("url :: ", url)
setLoadingUpload(true)
const payload = buildEnvelopePayload();
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("first token : ", token?.token)
formData.append('requestDto', JSON.stringify(payload))
console.log("payload for fil doc : ", formData)
const res = await axios.post(url, formData, {
headers: {
Authentication: `Bearer ${token?.token}`,
'Content-Type': 'multipart/form-data'
},
}) as any;
console.log(`res file ${activeEnvelope} api response:`, res)
ToastAndroid.show(res.message, ToastAndroid.SHORT)
navigation.goBack()
} catch (error: any) {
console.log(`Envelope file ${activeEnvelope} error:`, error?.response || error.message || error);
ToastAndroid.show('Upload failed', ToastAndroid.SHORT);
} finally {
setLoadingUpload(false);
}
const payload = buildEnvelopePayload();
const formData = new FormData();
console.log("envelop form : ", envelopForm)
formData.append('files', {
uri: envelopForm?.[activeEnvelope]?.[3].uri,
name: envelopForm?.[activeEnvelope]?.[3].name,
type: envelopForm?.[activeEnvelope]?.[3].type,
});
// formData.append('requestDto', payload)
formData.append('requestDto', JSON.stringify(payload))
console.log("formdata : ", formData)
const res: any = await apiCall('post', url, formData, {}, 'multipart/form-data');
console.log(res, '=============================================');
return res;
}
return (
@@ -508,11 +506,11 @@ const TenderBidding = () => {
<View style={[styles.infoRow, { flexDirection: 'row', gap: 10 }]}>
<TouchableOpacity onPress={() => setCheck(!check)}>
{check ? (
<CheckSquare size={24} color={colors.primary} />
) : (
<Square size={24} color={colors.primary} />
)}</TouchableOpacity>
{check ? (
<CheckSquare size={24} color={colors.primary} />
) : (
<Square size={24} color={colors.primary} />
)}</TouchableOpacity>
<Text>Check First Terms&Condition</Text>
</View>
</View>
@@ -537,7 +535,7 @@ const TenderBidding = () => {
</>
) : (
<>
<EditableExcel tenderId={tenderData} />
<EnvelopeCExcelPreview tenderId={tenderData} />
</>
)}
</View>
+1
View File
@@ -1,4 +1,5 @@
export interface LiveTenderTileProps {
contractorId?: string;
data: any
onPurchasePress?: () => void;
onDetailsPress?: () => void;
+2 -1
View File
@@ -24,7 +24,7 @@ const CONTRACTOR_BASIC_PROFILE = `${BASE_URL}contractor/getContractorById?contId
const CONTRACTOR_PROFILE_UPDATE = `${BASE_URL}contractor/updateContBasicDetails?id=`
const GET_REGISTRATION_STATE = `${BASE_URL}contractor/getContractorOrgTypeandStatus`
const GET_MASTER_DATA = `${BASE_URL}contractor/masterdata/all`
const GET_TENDER_BY_ID = `https://procurelinc.in/EProcurementSB/eTendering/getTenderById?tenderId=`
const LOG_IN_URL = `${BASE_URL_Auction}auction/loginWithoutDecryption`
const LIVE_TENDER = `${BASE_URL}liveTender/getAllLiveTenders`
const LIVE_AUCTION = `${BASE_URL_Auction}api/auction/getLiveAuctionForMobile`
@@ -159,5 +159,6 @@ export {
SUBMIT_ENVELOPE_FILE_B,
GET_ENVELOPE_C,
SUBMIT_ENVELOPE_C_UPLOAD,
GET_TENDER_BY_ID
};
+25 -12
View File
@@ -1,5 +1,5 @@
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import { contractorStageThreeSubmit, contractorStageTwoSubmit, fetchAuctionItem, fetchCityDropdown, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchMasterData, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, fetchTenderById, getAuction, getDesignation, getRagistrationState, offlineAuctionPurchase, offlineTenderPurchase, partnerShipSubmit, proceedToBid, registerContractor, sendAadhaarOtp, submitEnvelopeA, submitEnvelopeB, uploadEnvelopeFile, userAuctionLogin, userLogin, verifyAadhaarOtp, verifyGstNumber, verifyPanHandler, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments, fetchPurchaseAuctionPage } from './apis';
import { contractorStageThreeSubmit, contractorStageTwoSubmit, fetchAuctionItem, fetchCityDropdown, fetchDashboard, fetchEmdCheck, fetchLiveAuction, fetchLiveTender, fetchMasterData, fetchPurchaseAuction, fetchPurchaseAuctionList, fetchPurchaseTender, fetchTenderById, getAuction, getDesignation, getRagistrationState, offlineAuctionPurchase, offlineTenderPurchase, partnerShipSubmit, proceedToBid, registerContractor, sendAadhaarOtp, submitEnvelopeA, submitEnvelopeB, uploadEnvelopeFile, userAuctionLogin, userLogin, verifyAadhaarOtp, verifyGstNumber, verifyPanHandler, viewAuctionDocumentDownload, viewAuctionDocuments, viewAuctionItemDocuments, fetchPurchaseAuctionPage, tenderDetails, fetchLiveTenderById } from './apis';
// User
@@ -36,18 +36,18 @@ export const useFetchPurchaseTender = () => {
getNextPageParam: (lastPage, allPages) => {
const { totalCount } = lastPage;
const totalPages = Math.ceil(totalCount / size);
const totalPages = Math.ceil(totalCount / 10);
const nextPage = allPages.length;
return nextPage < totalPages ? nextPage : undefined;
},
return page < totalPages ? page + 1 : undefined;
},
select: (data) => ({
...data,
result: data.pages.flatMap(
(page: any) => page?.data ?? []
),
}),
});
select: (data) => ({
...data,
result: data.pages.flatMap(
(page: any) => page?.data ?? []
),
}),
});
};
@@ -298,3 +298,16 @@ export const useAucitonDocDownload = () => {
export const useTenderDetails = () => {
return useMutation({
mutationFn: (tenderId) => tenderDetails(tenderId),
});
};
export const useFetchLiveTenderById = (tenderId:number) => {
return useQuery({
queryKey: ['fetchLiveTenderById', tenderId],
queryFn: () => fetchLiveTenderById(tenderId)
});
};
+20 -2
View File
@@ -1,5 +1,5 @@
import axios from "axios";
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, STAGETHREE, REJ_STAGE_TWO, GETCONTRACTORBYSTATE, GET_MASTER_DATA, ALL_DESIGNATION, GET_REGISTRATION_STATE, PARTNER_API_ENDPOINT, SAVE_CONTRACTOR_STAGE1, VERIFY_GST, VERIFY_ADHAR, SEND_ADHAROTP, SUBMIT_ENVELOPE_FILE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, VERIFY_ORGPAN, PURCHASE_AUCTION_PAGE } 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, STAGETHREE, REJ_STAGE_TWO, GETCONTRACTORBYSTATE, GET_MASTER_DATA, ALL_DESIGNATION, GET_REGISTRATION_STATE, PARTNER_API_ENDPOINT, SAVE_CONTRACTOR_STAGE1, VERIFY_GST, VERIFY_ADHAR, SEND_ADHAROTP, SUBMIT_ENVELOPE_FILE_B, SUBMIT_ENVELOPE_FILE_A, SUBMIT_ENVELOPE_A, SUBMIT_ENVELOPE_B, VERIFY_ORGPAN, PURCHASE_AUCTION_PAGE, GET_TENDER_BY_ID } from "./ApiUrls";
import { apiCall } from "../../Axios/Axios";
// User
@@ -341,4 +341,22 @@ export const uploadEnvelopeFile = async ({
})
return data
}
}
export const tenderDetails = async (tenderId:any ): Promise<any> => {
const data = await apiCall<any>('get', `${GET_TENDER_DETAILS}${tenderId}`, );
return data
}
export const fetchLiveTenderById = async (tenderId: number): Promise<any> => {
const data = await apiCall<any>(
'get',
`${GET_TENDER_BY_ID}${tenderId}`
);
return data;
};