From ffc7e4ead03c57b83c0b4534a9f30c0da818a1ea Mon Sep 17 00:00:00 2001 From: rohit-kumar-fullstack Date: Mon, 12 Jan 2026 18:12:41 +0530 Subject: [PATCH] update excel sheet open --- package-lock.json | 16 +- package.json | 1 + .../Tender/Component/EditableExcel.tsx | 196 +++++++++++------- src/Screens/Tender/utils/excelHtml.ts | 63 ++++++ src/Services/BBPS/apis.ts | 4 +- 5 files changed, 207 insertions(+), 73 deletions(-) create mode 100644 src/Screens/Tender/utils/excelHtml.ts diff --git a/package-lock.json b/package-lock.json index 33c425c..c637dbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-native-skeleton-placeholder": "^5.2.4", "react-native-svg": "^15.12.0", "react-native-svg-transformer": "^1.5.1", + "react-native-webview": "^13.16.0", "react-redux": "^9.2.0", "redux-persist": "^6.0.0", "sockjs-client": "^1.6.1", @@ -3937,7 +3938,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.16" }, @@ -12251,6 +12251,20 @@ "react-native-svg": ">=12.0.0" } }, + "node_modules/react-native-webview": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", + "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", diff --git a/package.json b/package.json index bfa183d..e166b03 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react-native-skeleton-placeholder": "^5.2.4", "react-native-svg": "^15.12.0", "react-native-svg-transformer": "^1.5.1", + "react-native-webview": "^13.16.0", "react-redux": "^9.2.0", "redux-persist": "^6.0.0", "sockjs-client": "^1.6.1", diff --git a/src/Screens/Tender/Component/EditableExcel.tsx b/src/Screens/Tender/Component/EditableExcel.tsx index fd01fb3..db1dd6d 100644 --- a/src/Screens/Tender/Component/EditableExcel.tsx +++ b/src/Screens/Tender/Component/EditableExcel.tsx @@ -1,73 +1,114 @@ import React, { useEffect, useState } from 'react'; -import { - View, - Text, - TextInput, - ScrollView, - TouchableOpacity, - StyleSheet, - ActivityIndicator, - Alert, -} from 'react-native'; +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'; +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([]); const [columns, setColumns] = useState([]); const [loading, setLoading] = useState(false); + const token = useSelector((state: any) => state.token.token); + useEffect(() => { loadExcelFromApi(); }, []); - // 📥 FETCH EXCEL BLOB + READ + // 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 urlWithParams = `${EXCEL_API_URL}?tenderId=2479`; - - // ⬇️ Fetch Excel as blob - const response = await axios.get(urlWithParams, { + const response = await axios.get(EXCEL_API_URL, { responseType: 'arraybuffer', headers: { - Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBQklOVE9QUE85NkBHTUFJTC5DT00iLCJpYXQiOjE3Njc5MzgzMDMsImV4cCI6MTc2Nzk3NDMwM30.NiT5iOIdQH0y8TutLfLVp9ApQIAI3NHbkmRjb1fl-JlGxeyX21n3oPGvU6sDw_uU9iTZHI183e2FH_IL2sZYpQ`, + Authentication: `Bearer ${token.token}`, }, }); - console.log(response, '--------------------------------------'); - // ⬇️ Save file locally - const filePath = `${RNFS.DocumentDirectoryPath}/envelopeC.xlsx`; - const buffer = Buffer.from(response.data, 'binary'); // convert arraybuffer to buffer - await RNFS.writeFile(filePath, buffer.toString('base64'), 'base64'); - - // 📖 Read Excel - const workbook = XLSX.readFile(filePath); + const base64 = arrayBufferToBase64(response.data); + const workbook = XLSX.read(base64, { type: 'base64' }); const sheet = workbook.Sheets[workbook.SheetNames[0]]; - const jsonData: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' }); + const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' }); - setData(jsonData); - setColumns(Object.keys(jsonData[0] || {})); - } catch (error: any) { - console.log('Excel load error:', error); - Alert.alert('Error', error.message || 'Unable to load Excel'); + 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 + // UPDATE CELL (QTY / RATE) + AUTO VALUE + AUTO TOTAL const updateCell = (rowIndex: number, key: string, value: string) => { const updated = [...data]; - updated[rowIndex][key] = value; + 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 (LOCAL) + // SAVE UPDATED EXCEL const saveUpdatedExcel = async () => { try { setLoading(true); @@ -76,16 +117,20 @@ const EnvelopeCExcelPreview = () => { const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); - const base64Excel = XLSX.write(workbook, { bookType: 'xlsx', type: 'base64' }); - const updatedPath = `${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`; + const base64 = XLSX.write(workbook, { + bookType: 'xlsx', + type: 'base64', + }); - await RNFS.writeFile(updatedPath, base64Excel, 'base64'); + await RNFS.writeFile( + `${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`, + base64, + 'base64' + ); - Alert.alert('Success', 'Updated Excel saved successfully'); - console.log('Saved at:', updatedPath); - } catch (error) { - console.log('Save error:', error); - Alert.alert('Error', 'Failed to save Excel'); + Alert.alert('Success', 'Excel saved with auto-calculation'); + } catch (e) { + Alert.alert('Error', 'Save failed'); } finally { setLoading(false); } @@ -101,9 +146,9 @@ const EnvelopeCExcelPreview = () => { return ( - Envelope-C Excel Preview + - + {/* HEADER */} @@ -118,14 +163,32 @@ const EnvelopeCExcelPreview = () => { {data.map((row, rowIndex) => ( - {columns.map(key => ( - updateCell(rowIndex, key, text)} - /> - ))} + {columns.map(key => { + const isTotal = row['DEMO DEPARTEMENT'] === TOTAL_LABEL; + const isEditable = + !isTotal && (key === QTY_KEY || key === RATE_KEY); + + return ( + + updateCell(rowIndex, key, text) + } + /> + ); + })} ))} @@ -142,24 +205,10 @@ const EnvelopeCExcelPreview = () => { export default EnvelopeCExcelPreview; const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 16, - backgroundColor: '#fff', - }, - loader: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - title: { - fontSize: 18, - fontWeight: '700', - marginBottom: 10, - }, - row: { - flexDirection: 'row', - }, + 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, @@ -173,6 +222,13 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: '#cbd5e1', }, + valueCell: { + backgroundColor: '#f8fafc', + }, + totalCell: { + backgroundColor: '#e5e7eb', + fontWeight: '700', + }, button: { marginTop: 15, backgroundColor: '#2563eb', diff --git a/src/Screens/Tender/utils/excelHtml.ts b/src/Screens/Tender/utils/excelHtml.ts new file mode 100644 index 0000000..b5cc7c1 --- /dev/null +++ b/src/Screens/Tender/utils/excelHtml.ts @@ -0,0 +1,63 @@ +export const spreadHtml = ` + + + + + + + + + + + + + + + +
+ + + + +`; diff --git a/src/Services/BBPS/apis.ts b/src/Services/BBPS/apis.ts index 580e747..c82c2f8 100644 --- a/src/Services/BBPS/apis.ts +++ b/src/Services/BBPS/apis.ts @@ -6,8 +6,8 @@ import { apiCall } from "../../Axios/Axios"; 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(TENDER_LOGIN, { ...payload, device: "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;