with encrypted data on envolope submit

This commit is contained in:
Abin Toppo
2026-02-05 16:03:26 +05:30
parent 499e680404
commit c1ecc97e29
56 changed files with 4788 additions and 1482 deletions
@@ -11,13 +11,27 @@ import {
} from 'react-native';
import RNFS from 'react-native-fs';
import * as XLSX from 'xlsx';
import axios from 'axios';
import axios, { AxiosError } from 'axios';
import { useSelector } from 'react-redux';
import colors from '../../../Constant/Color';
import { GET_ENVELOPE_C, SUBMIT_ENVELOPE_C_UPLOAD } from '../../../Services/BBPS/ApiUrls';
import { GET_ENVELOPE_C, SUBMIT_ENVELOPE_C_UPLOAD } from '../../../Services/ApiCall/ApiUrls';
import { useNavigation } from '@react-navigation/native';
import { encrypt, encryptWithRSA } from '../../../Services/Encryption/encrypt';
/* -------------------- UTILS -------------------- */
interface OfficerKey {
officersId: number;
officerPublicKey: string;
officerType: string;
}
interface SaveEnvelopeOfficerDetail {
officerId: number;
officerEncryptedKey: string;
officerType: string;
}
// Convert ArrayBuffer → Base64
const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
@@ -29,37 +43,54 @@ const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
return global.btoa(binary);
};
// Find column dynamically by keywords
const findColumnKey = (columns: string[], keywords: string[]) =>
columns.find(col =>
keywords.some(k => col.toLowerCase().includes(k.toLowerCase()))
);
// Detect TOTAL row (any column contains TOTAL)
const isTotalRow = (row: any) =>
Object.values(row).some(v => String(v).toUpperCase() === 'TOTAL');
/* -------------------- COMPONENT -------------------- */
const EnvelopeCExcelPreview = (props: any) => {
const tenderData = props;
console.log("Tender Data",tenderData)
const token = useSelector((state: any) => state.token.token);
const navigation = useNavigation() as any;
const [data, setData] = useState<any[]>([]);
const [columns, setColumns] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadExcelFromApi();
}, []);
/* -------------------- LOAD EXCEL -------------------- */
const loadExcelFromApi = async () => {
try {
setLoading(true);
console.log('\n📊 LOADING EXCEL FROM API');
console.log('═══════════════════════════════════════════════════════');
console.log('🔗 API URL:', `${GET_ENVELOPE_C}${tenderData?.tenderId?.tenderId}`);
console.log('🆔 Tender ID:', tenderData?.tenderId?.tenderId);
console.log('🔑 Token:', token.token ? 'Present' : 'Missing');
const response = await axios.get(
`${GET_ENVELOPE_C}${tenderData?.tenderId?.tenderId}`,
{
@@ -70,45 +101,71 @@ const EnvelopeCExcelPreview = (props: any) => {
}
);
console.log('\n✅ API RESPONSE RECEIVED');
console.log('───────────────────────────────────────────────────────');
console.log('📡 Status:', response.status);
console.log('📡 Status Text:', response.statusText);
console.log('📦 Response Data Size (KB):', (response.data.byteLength / 1024).toFixed(2));
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: '' });
console.log('📊 PARSED DATA - Total Rows:', rows.length);
setColumns(Object.keys(rows[0] || {}));
setData(rows);
} catch (e) {
Alert.alert('Error', 'Failed to load Excel');
console.log('✅ EXCEL LOADED SUCCESSFULLY\n');
} catch (e: any) {
console.log('\n❌ ERROR LOADING EXCEL');
console.error('Error:', e.message);
Alert.alert('Error', 'Failed to load Excel: ' + e.message);
} finally {
setLoading(false);
}
};
/* -------------------- UPDATE CELL -------------------- */
const updateCell = (rowIndex: number, key: string, value: string) => {
const updated = [...data];
const row = updated[rowIndex];
if (isTotalRow(row)) return;
const qtyKey:any = findColumnKey(columns, ['qty', 'quantity']);
const rateKey:any = findColumnKey(columns, ['rate', 'price']);
const valueKey = findColumnKey(columns, ['value', 'amount', 'total']);
if (key === qtyKey || key === rateKey) {
row[key] = value;
const qty = Number(row[qtyKey]);
const rate = Number(row[rateKey]);
if (valueKey) {
row[valueKey] =
!isNaN(qty) && !isNaN(rate) ? qty * rate : 0;
}
}
// Recalculate TOTAL
const totalRowIndex = updated.findIndex(isTotalRow);
if (totalRowIndex !== -1 && valueKey) {
@@ -119,67 +176,248 @@ const EnvelopeCExcelPreview = (props: any) => {
}, 0);
}
setData(updated);
};
/* -------------------- SAVE & UPLOAD EXCEL -------------------- */
const buildOfficerEncryptedKeys = (envelopeKey: string): SaveEnvelopeOfficerDetail[] => {
const officerKeys: OfficerKey[] = tenderData?.tenderId?.envelopeKeyDetailsResDTO?.departmentOfficerPublicKeys || [];
console.log('\n🔐 ENCRYPTING ENVELOPE KEY FOR OFFICERS');
console.log('═══════════════════════════════════════════════════════');
console.log('📌 Envelope Key to encrypt:', envelopeKey);
console.log('👥 Number of officers:', officerKeys.length);
const encryptedKeys: SaveEnvelopeOfficerDetail[] = [];
for (let i = 0; i < officerKeys.length; i++) {
const officer = officerKeys[i];
console.log(`\n🔄 Officer ${i + 1}/${officerKeys.length}`);
console.log(' ID:', officer.officersId);
console.log(' Type:', officer.officerType);
try {
const encryptedKey = encryptWithRSA(envelopeKey, officer.officerPublicKey);
if (!encryptedKey) {
console.error(` ❌ FAILED to encrypt key for officer ${officer.officersId}`);
} else {
console.log(` ✅ SUCCESS - Encrypted key length: ${encryptedKey.length}`);
}
encryptedKeys.push({
officerId: officer.officersId,
officerEncryptedKey: encryptedKey || '',
officerType: officer.officerType,
});
} catch (error) {
console.error(` ❌ ERROR encrypting for officer ${officer.officersId}:`, error);
encryptedKeys.push({
officerId: officer.officersId,
officerEncryptedKey: '',
officerType: officer.officerType,
});
}
}
console.log('\n✅ ALL OFFICER KEYS ENCRYPTED');
console.log('═══════════════════════════════════════════════════════\n');
return encryptedKeys;
};
// ✅ NEW: Encrypt Excel file content (binary) after creation
const encryptExcelFile = async (filePath: string, encryptionKey: string): Promise<string> => {
console.log('\n🔐 ENCRYPTING EXCEL FILE CONTENT');
console.log('═══════════════════════════════════════════════════════');
console.log('📁 File Path:', filePath);
console.log('🔑 Encryption Key:', encryptionKey);
try {
// Read the Excel file as Base64
const fileContent = await RNFS.readFile(filePath, 'base64');
console.log('📖 File read successfully, Base64 length:', fileContent.length);
// Encrypt the entire file content
console.log('🔒 Encrypting file content...');
const encryptedContent:any = encrypt(fileContent, encryptionKey);
console.log('✅ File content encrypted, Encrypted length:', encryptedContent?.length);
// Save encrypted content to a new file
const encryptedFilePath = `${RNFS.DocumentDirectoryPath}/envelopeC_encrypted.xlsx`;
await RNFS.writeFile(encryptedFilePath, encryptedContent, 'utf8');
console.log('💾 Encrypted file saved at:', encryptedFilePath);
console.log('✅ EXCEL FILE ENCRYPTION COMPLETED');
console.log('═══════════════════════════════════════════════════════\n');
return encryptedFilePath;
} catch (error) {
console.error('❌ ERROR ENCRYPTING EXCEL FILE:', error);
throw error;
}
};
const saveUpdatedExcel = async () => {
try {
setLoading(true);
console.log('\n💾 SAVING UPDATED EXCEL');
console.log('═══════════════════════════════════════════════════════');
const ENCRYPTION_KEY = tenderData?.tenderId?.envelopeKeyDetailsResDTO?.envelopeCGenerateKey || '';
if (!ENCRYPTION_KEY) {
console.error('❌ ENCRYPTION KEY NOT FOUND');
Alert.alert('Error', 'Encryption key not available');
setLoading(false);
return;
}
console.log('🔑 Encryption Key:', ENCRYPTION_KEY);
// Step 1: Create normal Excel workbook with user data (NOT ENCRYPTED YET)
console.log('\n📝 STEP 1: Creating Excel workbook with user data');
console.log('───────────────────────────────────────────────────────');
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const binary = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'binary',
});
const buffer = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
buffer[i] = binary.charCodeAt(i) & 0xff;
}
const filePath = `${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`;
await RNFS.writeFile(filePath, buffer.toString(), 'ascii');
// Step 2: Save the plain Excel file
console.log('\n📝 STEP 2: Saving plain Excel file');
console.log('───────────────────────────────────────────────────────');
const plainFilePath = `${RNFS.DocumentDirectoryPath}/envelopeC_plain.xlsx`;
await RNFS.writeFile(plainFilePath, buffer.toString(), 'ascii');
console.log('✅ Plain Excel saved at:', plainFilePath);
// Step 3: Encrypt the entire Excel file
console.log('\n📝 STEP 3: Encrypting the Excel file');
console.log('───────────────────────────────────────────────────────');
const encryptedFilePath = await encryptExcelFile(plainFilePath, ENCRYPTION_KEY);
console.log('✅ Encrypted Excel file ready');
// Step 4: Build officer encrypted keys
console.log('\n📝 STEP 4: Building officer encrypted keys');
console.log('───────────────────────────────────────────────────────');
const officerEncryptedKeys = buildOfficerEncryptedKeys(ENCRYPTION_KEY);
// Step 5: Prepare FormData for upload
console.log('\n📝 STEP 5: Building FormData');
console.log('───────────────────────────────────────────────────────');
const formData = new FormData();
formData.append(
'requestDto',
JSON.stringify({
tenderId: tenderData?.tenderId?.tenderId,
deptId: tenderData?.tenderId?.deptId,
typesOfTender: tenderData?.tenderId?.typesOfTender,
fieldValue: '',
fieldValueInWord: 'zero',
})
);
const requestDto = {
tenderId: tenderData?.tenderId?.tenderId,
deptId: tenderData?.tenderId?.deptId,
typesOfTender: tenderData?.tenderId?.typesOfTender,
fieldValue: '',
fieldValueInWord: 'zero',
deviceType: 'MOBILE',
fileName: 'envelopeC_encrypted.xlsx',
saveEnvelopesOfficersDetailsResDTO: officerEncryptedKeys,
};
formData.append('requestDto', JSON.stringify(requestDto));
// Use the ENCRYPTED file for upload
formData.append('file', {
uri: `file://${filePath}`,
name: 'envelopeC_updated.xlsx',
uri: `file://${encryptedFilePath}`,
name: 'envelopeC_encrypted.xlsx',
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
} as any);
await axios.post(SUBMIT_ENVELOPE_C_UPLOAD, formData, {
console.log('📋 Request DTO:', JSON.stringify(requestDto, null, 2));
console.log('📎 Encrypted File Path:', encryptedFilePath);
// Step 6: Upload to server
console.log('\n📝 STEP 6: Uploading encrypted file to server');
console.log('───────────────────────────────────────────────────────');
console.log('FormData prepared:', formData);
const response = await axios.post(SUBMIT_ENVELOPE_C_UPLOAD, formData, {
headers: {
'Content-Type': 'multipart/form-data',
Authentication: `Bearer ${token.token}`,
},
});
console.log('✅ UPLOAD SUCCESSFUL');
console.log('Response:', response.data);
console.log('═══════════════════════════════════════════════════════\n');
// Clean up temporary files
await RNFS.unlink(plainFilePath).catch(e => console.log('Plain file cleanup:', e.message));
await RNFS.unlink(encryptedFilePath).catch(e => console.log('Encrypted file cleanup:', e.message));
Alert.alert('Success', 'Excel uploaded successfully');
navigation.goBack();
} catch (e) {
Alert.alert('Error', 'Save failed');
} catch (e: any) {
console.error('❌ Error saving/uploading Excel:', {
message: e?.message,
response: e?.response?.data,
status: e?.response?.status,
statusText: e?.response?.statusText,
});
Alert.alert('Error', e?.response?.data?.message || 'Save failed');
} finally {
setLoading(false);
}
};
/* -------------------- UI -------------------- */
if (loading) {
return (
<View style={styles.loader}>
@@ -188,10 +426,12 @@ const EnvelopeCExcelPreview = (props: any) => {
);
}
const qtyKey = findColumnKey(columns, ['qty', 'quantity']);
const rateKey = findColumnKey(columns, ['rate', 'price']);
const valueKey = findColumnKey(columns, ['value', 'amount', 'total']);
return (
<View style={styles.container}>
<ScrollView horizontal contentContainerStyle={{ padding: 10 }}>
@@ -203,6 +443,7 @@ const EnvelopeCExcelPreview = (props: any) => {
))}
</View>
{/* DATA */}
<ScrollView style={{ maxHeight: 420 }}>
{data.map((row, rowIndex) => (
@@ -211,6 +452,7 @@ const EnvelopeCExcelPreview = (props: any) => {
const editable =
!isTotalRow(row) && (key === qtyKey || key === rateKey);
return (
<TextInput
key={key}
@@ -234,6 +476,7 @@ const EnvelopeCExcelPreview = (props: any) => {
</View>
</ScrollView>
{tenderData?.buttonEnable && (
<TouchableOpacity style={styles.button} onPress={saveUpdatedExcel}>
<Text style={styles.buttonText}>Save Updated Excel</Text>
@@ -243,10 +486,13 @@ const EnvelopeCExcelPreview = (props: any) => {
);
};
export default EnvelopeCExcelPreview;
/* -------------------- STYLES -------------------- */
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
loader: { flex: 1, justifyContent: 'center', alignItems: 'center' },
@@ -282,4 +528,4 @@ const styles = StyleSheet.create({
textAlign: 'center',
fontWeight: '600',
},
});
});
+1 -4
View File
@@ -2,7 +2,7 @@ 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 { useFetchLiveAuction, useFetchLiveTender } from '../../Services/ApiCall/Hooks';
import { SearchInput } from '../../Component/Serach/SeacrhInput';
import { Skelton } from '../../Component/Index';
import colors from '../../Constant/Color';
@@ -14,10 +14,8 @@ 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();
console.log("to show ",data)
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
useEffect(() => {
@@ -49,7 +47,6 @@ const LiveTender = () => {
const renderItem = useCallback(
({ item, index }: any) => {
console.log("opopopopoopopopop",item.tenderNumber,item)
if(item.tenderStatus==="Cancelled") return null;
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
return <LiveTenderTile
+2 -4
View File
@@ -12,11 +12,11 @@ import {
import { useRoute, useNavigation } from '@react-navigation/native';
import {
useFetchLiveTenderById,
} from '../../Services/BBPS/Hooks';
} from '../../Services/ApiCall/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 { GET_DOCID } from '../../Services/ApiCall/ApiUrls';
import {
Layers,
Briefcase,
@@ -50,9 +50,7 @@ const LiveTenderDetail = () => {
const route = useRoute<any>();
const navigation = useNavigation<any>();
const tender = route?.params;
console.log("object---",tender)
const { data } = useFetchLiveTenderById(tender?.tenderId);
console.log("huh",data)
const [downloadingId, setDownloadingId] = useState<string | number | null>(null);
const insets = useSafeAreaInsets()
if (!data?.data?.[0]) return null;
-1
View File
@@ -30,7 +30,6 @@ const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
: [];
const purchased = contractorIds.includes(String(contractorId));
console.log("item is ",data)
if(data.tenderStatus==="Amended"){
}
@@ -23,7 +23,7 @@ import {
} from 'lucide-react-native';
import colors from '../../../Constant/Color';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useFetchTenderById } from '../../../Services/BBPS/Hooks';
import { useFetchTenderById } from '../../../Services/ApiCall/Hooks';
interface Props {
visible: boolean;
+6 -5
View File
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect } from 'react'
import { InsideHeader } from '../../Component/Index'
import colors from '../../Constant/Color'
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native'
import { useFetchTenderById, useProceedToBid } from '../../Services/BBPS/Hooks'
import { useFetchTenderById, useProceedToBid } from '../../Services/ApiCall/Hooks'
const ProceedToBidDetail = () => {
// Sample data based on your JSON
@@ -12,13 +12,10 @@ const ProceedToBidDetail = () => {
const { data, refetch, isRefetching } = useProceedToBid(tenderDataId?.tenderId);
const { data: tenderResponse, refetch: tenderRefetch }: any = useFetchTenderById(tenderDataId?.tenderId);
const tenderData = tenderResponse?.data;
console.log("dataopop",tenderData)
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)
// console.log("tender data for updated : ", tenderData)
const navigation = useNavigation<any>()
// Format date from array
@@ -176,7 +173,11 @@ const ProceedToBidDetail = () => {
<View style={styles.statusGrid}>
<StatusIndicator status={tenderData?.envelopeASubmittStatus} label="Envelope A" />
{tenderData?.showEB &&<StatusIndicator status={tenderData?.envelopeBSubmittStatus} label="Envelope B" />}
{tenderData?.showLCS &&<StatusIndicator status={tenderData?.envelopeBSubmittStatus} label="Envelope LCS" />}
{tenderData?.showQCBS &&<StatusIndicator status={tenderData?.envelopeBSubmittStatus} label="Envelope QCBS" />}
{tenderData?.showEC &&<StatusIndicator status={tenderData?.envelopeCSubmittStatus} label="Envelope C" />}
</View>
</View>
+1 -3
View File
@@ -4,7 +4,7 @@ import { InsideHeader, Loader } from '../../Component/Index';
import colors from '../../Constant/Color';
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
import { useDispatch, useSelector } from 'react-redux';
import { useOfflineTenderPurchase } from '../../Services/BBPS/Hooks';
import { useOfflineTenderPurchase } from '../../Services/ApiCall/Hooks';
import { StackActions, useNavigation } from '@react-navigation/native';
import NavigationString from '../../Constant/NavigationString';
import { showErrorAlert, showToast } from '../../Constant/ShowDailog';
@@ -50,11 +50,9 @@ const ProceedToPurTender = () => {
orderNo: orderNo,
};
console.log("tender offline body : ", body)
try {
mutate(body, {
onSuccess: (res) => {
console.log(res, '---------------------purchsase response');
if (res.statusCode === 200) {
showToast(res.message);
navigation.dispatch(StackActions.pop(2));
+1 -1
View File
@@ -2,7 +2,7 @@ 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 { useFetchPurchaseTender } from '../../Services/ApiCall/Hooks';
import PurchaseTenderTile from './PurchaseTenderTile';
import { useDispatch } from 'react-redux';
import { setTender } from '../../Redux/Slices/SelectedTender';
@@ -19,7 +19,6 @@ const PurchaseTenderList = () => {
};
useEffect(()=>{
console.log("dcj",SelectedPurchaseList)
if(SelectedPurchaseList.length === 0){
Navigation.goBack()
}
@@ -36,7 +35,6 @@ const PurchaseTenderList = () => {
{/* Cards */}
{SelectedPurchaseList.map((item: any, index: any) => {
const expanded = expandedIndex === index;
console.log("item gvsdvgfsv h ca : ", item)
return (
<Animated.View
key={item.auctionId}
+1 -2
View File
@@ -4,7 +4,7 @@ import Animated, { FadeInUp, useAnimatedStyle, useSharedValue, withTiming } from
import moment from 'moment';
import CountdownTimer from '../../Component/Counter/CountdownTimer';
import colors from '../../Constant/Color';
import { useFetchTenderById } from '../../Services/BBPS/Hooks';
import { useFetchTenderById } from '../../Services/ApiCall/Hooks';
import { useNavigation } from '@react-navigation/native';
import TenderFullDetail from './Modal/TenderFullDetail';
@@ -12,7 +12,6 @@ const PurchaseTenderTile = ({ item, onPressDetails }: any) => {
const scale = useSharedValue(1);
const elevation = useSharedValue(8);
const navigation = useNavigation<any>();
console.log("obj000ect",item)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
elevation: elevation.value,
+1 -1
View File
@@ -11,7 +11,7 @@ import { InsideHeader } from '../../Component/Index';
import {
useFetchLiveTender,
useFetchPurchaseTender,
} from '../../Services/BBPS/Hooks';
} from '../../Services/ApiCall/Hooks';
import colors from '../../Constant/Color';
import { ShoppingCart, Lock } from 'lucide-react-native';
import { useNavigation } from '@react-navigation/native';
File diff suppressed because it is too large Load Diff