update excel sheet open

This commit is contained in:
rohit-kumar-fullstack
2026-01-12 18:12:41 +05:30
parent 5391040b0b
commit ffc7e4ead0
5 changed files with 207 additions and 73 deletions
+15 -1
View File
@@ -49,6 +49,7 @@
"react-native-skeleton-placeholder": "^5.2.4", "react-native-skeleton-placeholder": "^5.2.4",
"react-native-svg": "^15.12.0", "react-native-svg": "^15.12.0",
"react-native-svg-transformer": "^1.5.1", "react-native-svg-transformer": "^1.5.1",
"react-native-webview": "^13.16.0",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",
@@ -3937,7 +3938,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz",
"integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.90.16" "@tanstack/query-core": "5.90.16"
}, },
@@ -12251,6 +12251,20 @@
"react-native-svg": ">=12.0.0" "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": { "node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.79.0", "version": "0.79.0",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz",
+1
View File
@@ -51,6 +51,7 @@
"react-native-skeleton-placeholder": "^5.2.4", "react-native-skeleton-placeholder": "^5.2.4",
"react-native-svg": "^15.12.0", "react-native-svg": "^15.12.0",
"react-native-svg-transformer": "^1.5.1", "react-native-svg-transformer": "^1.5.1",
"react-native-webview": "^13.16.0",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",
+122 -66
View File
@@ -1,73 +1,114 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import { View, Text, TextInput, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, } from 'react-native';
View,
Text,
TextInput,
ScrollView,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import axios from 'axios'; 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 EnvelopeCExcelPreview = () => {
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [columns, setColumns] = useState<string[]>([]); const [columns, setColumns] = useState<string[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const token = useSelector((state: any) => state.token.token);
useEffect(() => { useEffect(() => {
loadExcelFromApi(); 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 () => { const loadExcelFromApi = async () => {
try { try {
setLoading(true); setLoading(true);
const urlWithParams = `${EXCEL_API_URL}?tenderId=2479`; const response = await axios.get(EXCEL_API_URL, {
// ⬇️ Fetch Excel as blob
const response = await axios.get(urlWithParams, {
responseType: 'arraybuffer', responseType: 'arraybuffer',
headers: { headers: {
Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBQklOVE9QUE85NkBHTUFJTC5DT00iLCJpYXQiOjE3Njc5MzgzMDMsImV4cCI6MTc2Nzk3NDMwM30.NiT5iOIdQH0y8TutLfLVp9ApQIAI3NHbkmRjb1fl-JlGxeyX21n3oPGvU6sDw_uU9iTZHI183e2FH_IL2sZYpQ`, Authentication: `Bearer ${token.token}`,
}, },
}); });
console.log(response, '--------------------------------------');
// ⬇️ Save file locally const base64 = arrayBufferToBase64(response.data);
const filePath = `${RNFS.DocumentDirectoryPath}/envelopeC.xlsx`; const workbook = XLSX.read(base64, { type: 'base64' });
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 sheet = workbook.Sheets[workbook.SheetNames[0]]; 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(rows[0] || {}));
setColumns(Object.keys(jsonData[0] || {})); setData(rows);
} catch (error: any) {
console.log('Excel load error:', error); await RNFS.writeFile(
Alert.alert('Error', error.message || 'Unable to load Excel'); `${RNFS.DocumentDirectoryPath}/envelopeC.xlsx`,
base64,
'base64'
);
} catch (e) {
Alert.alert('Error', 'Failed to load Excel');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// ✏️ UPDATE CELL // UPDATE CELL (QTY / RATE) + AUTO VALUE + AUTO TOTAL
const updateCell = (rowIndex: number, key: string, value: string) => { const updateCell = (rowIndex: number, key: string, value: string) => {
const updated = [...data]; 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); setData(updated);
}; };
// 💾 SAVE UPDATED EXCEL (LOCAL) // SAVE UPDATED EXCEL
const saveUpdatedExcel = async () => { const saveUpdatedExcel = async () => {
try { try {
setLoading(true); setLoading(true);
@@ -76,16 +117,20 @@ const EnvelopeCExcelPreview = () => {
const workbook = XLSX.utils.book_new(); const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const base64Excel = XLSX.write(workbook, { bookType: 'xlsx', type: 'base64' }); const base64 = XLSX.write(workbook, {
const updatedPath = `${RNFS.DocumentDirectoryPath}/envelopeC_updated.xlsx`; 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'); Alert.alert('Success', 'Excel saved with auto-calculation');
console.log('Saved at:', updatedPath); } catch (e) {
} catch (error) { Alert.alert('Error', 'Save failed');
console.log('Save error:', error);
Alert.alert('Error', 'Failed to save Excel');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -101,9 +146,9 @@ const EnvelopeCExcelPreview = () => {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Envelope-C Excel Preview</Text> <InsideHeader title='Envelope-C Excel Preview' />
<ScrollView horizontal> <ScrollView horizontal contentContainerStyle={{ paddingHorizontal: 10, paddingTop: 20, backgroundColor: colors.white }}>
<View> <View>
{/* HEADER */} {/* HEADER */}
<View style={styles.row}> <View style={styles.row}>
@@ -118,14 +163,32 @@ const EnvelopeCExcelPreview = () => {
<ScrollView style={{ maxHeight: 420 }}> <ScrollView style={{ maxHeight: 420 }}>
{data.map((row, rowIndex) => ( {data.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}> <View key={rowIndex} style={styles.row}>
{columns.map(key => ( {columns.map(key => {
const isTotal = row['DEMO DEPARTEMENT'] === TOTAL_LABEL;
const isEditable =
!isTotal && (key === QTY_KEY || key === RATE_KEY);
return (
<TextInput <TextInput
key={key} key={key}
value={String(row[key])} value={String(row[key])}
style={styles.cell} editable={isEditable}
onChangeText={text => updateCell(rowIndex, key, text)} 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> </View>
))} ))}
</ScrollView> </ScrollView>
@@ -142,24 +205,10 @@ const EnvelopeCExcelPreview = () => {
export default EnvelopeCExcelPreview; export default EnvelopeCExcelPreview;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: { flex: 1, backgroundColor: '#fff' },
flex: 1, loader: { flex: 1, justifyContent: 'center', alignItems: 'center' },
padding: 16, title: { fontSize: 18, fontWeight: '700', marginBottom: 10 },
backgroundColor: '#fff', row: { flexDirection: 'row' },
},
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
fontWeight: '700',
marginBottom: 10,
},
row: {
flexDirection: 'row',
},
headerCell: { headerCell: {
minWidth: 140, minWidth: 140,
padding: 10, padding: 10,
@@ -173,6 +222,13 @@ const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderColor: '#cbd5e1', borderColor: '#cbd5e1',
}, },
valueCell: {
backgroundColor: '#f8fafc',
},
totalCell: {
backgroundColor: '#e5e7eb',
fontWeight: '700',
},
button: { button: {
marginTop: 15, marginTop: 15,
backgroundColor: '#2563eb', backgroundColor: '#2563eb',
+63
View File
@@ -0,0 +1,63 @@
export const spreadHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link
href="https://cdn.grapecity.com/spreadjs/15.2.2/css/gc.spread.sheets.excel2013white.css"
rel="stylesheet"
/>
<script src="https://cdn.grapecity.com/spreadjs/15.2.2/gc.spread.sheets.all.min.js"></script>
<script src="https://cdn.grapecity.com/spreadjs/15.2.2/plugins/gc.spread.sheets.charts.min.js"></script>
<style>
html, body {
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#spread {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="spread"></div>
<script>
let spread;
document.addEventListener('message', function (e) {
const base64 = e.data;
spread = new GC.Spread.Sheets.Workbook(
document.getElementById('spread'),
{ sheetCount: 1 }
);
GC.Spread.Excel.IO.open(base64, spread, function () {
console.log('Excel loaded');
});
});
function exportExcel() {
const excelIO = new GC.Spread.Excel.IO();
excelIO.save(spread, function (blob) {
const reader = new FileReader();
reader.onload = function () {
const base64 = reader.result.split(',')[1];
window.ReactNativeWebView.postMessage(base64);
};
reader.readAsDataURL(blob);
});
}
</script>
</body>
</html>
`;
+2 -2
View File
@@ -6,8 +6,8 @@ import { apiCall } from "../../Axios/Axios";
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => { export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
try { 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" }); const response = await axios.post(TENDER_LOGIN, { ...payload, device: "mobileApp" });
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
throw error.response?.data || error.message; throw error.response?.data || error.message;