Added downloading back the excel sheet with corrected values
This commit is contained in:
parent
dbebfd6d5a
commit
744a365bfc
1009
frontend/package-lock.json
generated
1009
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,8 @@
|
||||
"axios": "^1.7.7",
|
||||
"chokidar": "^4.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.15.0",
|
||||
|
@ -10,13 +10,17 @@ import {
|
||||
Tooltip,
|
||||
TextField,
|
||||
Typography,
|
||||
Button
|
||||
Button,
|
||||
Box
|
||||
} from '@mui/material';
|
||||
import { SpreadsheetService } from '../../openapi';
|
||||
import * as ExcelJS from 'exceljs';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fileBlob }) => {
|
||||
const [localErrors, setLocalErrors] = useState(errors || []);
|
||||
const [editingCell, setEditingCell] = useState({});
|
||||
const [nonEditableCells, setNonEditableCells] = useState(new Set());
|
||||
|
||||
const generateErrorMap = (errorsList) => {
|
||||
const errorMap = new Map();
|
||||
@ -31,6 +35,21 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
|
||||
const errorMap = generateErrorMap(localErrors);
|
||||
|
||||
useEffect(() => {
|
||||
const initialNonEditableCells = new Set();
|
||||
|
||||
raw_data.forEach((row, rowIndex) => {
|
||||
headers.forEach((_, colIndex) => {
|
||||
const key = `${row.row_num}-${headers[colIndex]}`;
|
||||
if (!errorMap.has(key)) {
|
||||
initialNonEditableCells.add(`${rowIndex}-${colIndex}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setNonEditableCells(initialNonEditableCells);
|
||||
}, [raw_data, headers, errorMap]);
|
||||
|
||||
const handleCellEdit = async (rowIndex, colIndex) => {
|
||||
const updatedRawData = [...raw_data];
|
||||
const columnName = headers[colIndex];
|
||||
@ -39,14 +58,10 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
|
||||
if (newValue === undefined) return;
|
||||
|
||||
console.log(`Editing cell at row ${rowIndex}, column ${colIndex}: new value: ${newValue}`);
|
||||
|
||||
// Ensure currentRow.data exists before accessing it
|
||||
if (!currentRow.data) {
|
||||
currentRow.data = [];
|
||||
}
|
||||
|
||||
// Update the relevant cell value
|
||||
currentRow.data[colIndex] = newValue;
|
||||
|
||||
setEditingCell(prev => {
|
||||
@ -62,15 +77,13 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
value: newValue
|
||||
});
|
||||
|
||||
console.log('Validation response:', response);
|
||||
|
||||
if (response.is_valid !== undefined) {
|
||||
if (response.is_valid) {
|
||||
// Remove error if it passes validation
|
||||
const updatedErrors = localErrors.filter(
|
||||
error => !(error.row === currentRow.row_num && error.cell === colIndex)
|
||||
);
|
||||
setLocalErrors(updatedErrors);
|
||||
setNonEditableCells(prev => new Set([...prev, `${rowIndex}-${colIndex}`]));
|
||||
} else {
|
||||
const updatedErrors = [
|
||||
...localErrors,
|
||||
@ -78,8 +91,6 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
];
|
||||
setLocalErrors(updatedErrors);
|
||||
}
|
||||
} else {
|
||||
console.error('Unexpected response structure:', response);
|
||||
}
|
||||
setRawData(updatedRawData);
|
||||
} catch (error) {
|
||||
@ -91,39 +102,33 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
handleCellEdit(rowIndex, colIndex);
|
||||
};
|
||||
|
||||
const validateBeforeSubmit = () => {
|
||||
let isValid = true;
|
||||
raw_data.forEach((row, rowIndex) => {
|
||||
headers.forEach((header, colIndex) => {
|
||||
const key = `${row.row_num}-${header}`;
|
||||
const newValue = editingCell[`${rowIndex}-${colIndex}`];
|
||||
|
||||
if (newValue !== undefined) {
|
||||
// Perform cell validation on each edit
|
||||
handleCellEdit(rowIndex, colIndex);
|
||||
}
|
||||
|
||||
const errorMessage = errorMap.get(key);
|
||||
if (errorMessage) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
return isValid;
|
||||
};
|
||||
const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const isValid = validateBeforeSubmit();
|
||||
|
||||
if (isValid) {
|
||||
if (allCellsValid()) {
|
||||
console.log('All data is valid. Proceeding with submission...');
|
||||
// Use validated data to populate the database
|
||||
// You can call a dedicated API endpoint to process the entire dataset as needed
|
||||
} else {
|
||||
console.log('There are validation errors in the dataset. Please correct them before submission.');
|
||||
}
|
||||
};
|
||||
|
||||
const downloadCorrectedSpreadsheet = async () => {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(fileBlob);
|
||||
const worksheet = workbook.getWorksheet(1);
|
||||
|
||||
raw_data.forEach((row, rowIndex) => {
|
||||
row.data.forEach((value, colIndex) => {
|
||||
worksheet.getRow(row.row_num).getCell(colIndex + 1).value = value;
|
||||
});
|
||||
});
|
||||
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
|
||||
saveAs(blob, 'corrected_data.xlsx');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Raw data:', raw_data);
|
||||
console.log('Errors:', localErrors);
|
||||
@ -146,6 +151,17 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<Box display="flex" justifyContent="space-between" mt={2}>
|
||||
<Button variant="contained" color="secondary" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit} disabled={!allCellsValid()}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button variant="contained" onClick={downloadCorrectedSpreadsheet} disabled={!allCellsValid()}>
|
||||
Download Corrected Spreadsheet
|
||||
</Button>
|
||||
</Box>
|
||||
<TableBody>
|
||||
{raw_data.map((row, rowIndex) => (
|
||||
<TableRow key={rowIndex}>
|
||||
@ -155,6 +171,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
const isInvalid = !!errorMessage;
|
||||
const cellValue = (row.data && row.data[colIndex]) || "";
|
||||
const editingValue = editingCell[`${rowIndex}-${colIndex}`];
|
||||
const isReadonly = !isInvalid;
|
||||
|
||||
return (
|
||||
<TableCell key={colIndex} align="center">
|
||||
@ -172,6 +189,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled={isReadonly}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
@ -181,9 +199,6 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData }) => {
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
@ -7,7 +7,8 @@ import {
|
||||
Button,
|
||||
Typography,
|
||||
IconButton,
|
||||
Box
|
||||
Box,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
@ -16,7 +17,8 @@ import logo from '../assets/Heidi-logo.png';
|
||||
import { OpenAPI, SpreadsheetService } from '../../openapi';
|
||||
import type { Body_upload_file_upload_post } from '../../openapi/models/Body_upload_file_upload_post';
|
||||
import SpreadsheetTable from './SpreadsheetTable';
|
||||
import Modal from './Modal'; // Import the custom Modal component
|
||||
import Modal from './Modal'; // Ensure correct import paths
|
||||
import * as ExcelJS from 'exceljs';
|
||||
|
||||
interface UploadDialogProps {
|
||||
open: boolean;
|
||||
@ -37,7 +39,10 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
|
||||
samples: string[];
|
||||
headers: string[]; // Headers must be part of this object
|
||||
} | null>(null);
|
||||
const [fileBlob, setFileBlob] = useState<Blob | null>(null); // New state to store the file blob
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
OpenAPI.BASE = 'http://127.0.0.1:8000';
|
||||
@ -49,44 +54,48 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
|
||||
|
||||
setUploadError(null);
|
||||
setFileSummary(null);
|
||||
setFileBlob(file); // Store the file blob
|
||||
setIsLoading(true);
|
||||
|
||||
if (!file.name.endsWith('.xlsx')) {
|
||||
setUploadError('Invalid file format. Please upload an .xlsx file.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const formData: Body_upload_file_upload_post = {
|
||||
file: file,
|
||||
} as Body_upload_file_upload_post;
|
||||
const formData: Body_upload_file_upload_post = { file: file } as Body_upload_file_upload_post;
|
||||
|
||||
try {
|
||||
const response = await SpreadsheetService.uploadFileUploadPost(formData);
|
||||
|
||||
// Ensure headers are included in the response
|
||||
const { headers, raw_data, errors } = response;
|
||||
setFileSummary({
|
||||
data: raw_data,
|
||||
errors: errors,
|
||||
raw_data: raw_data, // keep raw data as is
|
||||
headers: headers, // Set headers correctly here
|
||||
dewars_count: 2, // You might need to adjust according to actual response
|
||||
raw_data: raw_data,
|
||||
headers: headers,
|
||||
dewars_count: 2,
|
||||
dewars: ['Dewar1', 'Dewar2'],
|
||||
pucks_count: 2, // Adjust accordingly
|
||||
pucks_count: 2,
|
||||
pucks: ['Puck1', 'Puck2'],
|
||||
samples_count: 23, // Adjust accordingly
|
||||
samples_count: 23,
|
||||
samples: ['Sample1', 'Sample2']
|
||||
});
|
||||
setIsModalOpen(true); // Open modal once data is available
|
||||
setIsLoading(false);
|
||||
setIsModalOpen(true);
|
||||
} catch (error) {
|
||||
setUploadError('Failed to upload file. Please try again.');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRawDataChange = (updatedRawData) => {
|
||||
setFileSummary((prevSummary) => ({
|
||||
...prevSummary,
|
||||
raw_data: updatedRawData
|
||||
}));
|
||||
const handleCancel = () => {
|
||||
setIsModalOpen(false);
|
||||
setFileSummary(null);
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -117,29 +126,46 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
|
||||
<Box mt={3}>
|
||||
<Button variant="contained" component="label" startIcon={<UploadFileIcon />}>
|
||||
Choose a File
|
||||
<input type="file" hidden onChange={handleFileUpload} />
|
||||
<input type="file" hidden ref={fileInputRef} onChange={handleFileUpload} />
|
||||
</Button>
|
||||
{uploadError && <Typography color="error">{uploadError}</Typography>}
|
||||
</Box>
|
||||
|
||||
{isLoading && (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" mt={2}>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" ml={2}>Processing the file, please wait...</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="primary">Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Open modal to display spreadsheet table if file uploaded successfully */}
|
||||
{fileSummary && (
|
||||
{fileSummary && fileBlob && (
|
||||
<Modal open={isModalOpen} onClose={() => setIsModalOpen(false)} title="File Summary">
|
||||
<Typography color="success.main">File uploaded successfully!</Typography>
|
||||
<SpreadsheetTable
|
||||
raw_data={fileSummary.raw_data}
|
||||
errors={fileSummary.errors}
|
||||
headers={fileSummary.headers} // Ensure headers are passed here
|
||||
setRawData={handleRawDataChange}
|
||||
/>
|
||||
{fileSummary.errors.length > 0 ? (
|
||||
<Typography color="error">
|
||||
The file contains errors that need to be corrected before submission. Please review the highlighted cells below.
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography color="success.main">
|
||||
The file is validated successfully with no errors.
|
||||
</Typography>
|
||||
)}
|
||||
<Typography>Dewars: {fileSummary.dewars_count}</Typography>
|
||||
<Typography>Pucks: {fileSummary.pucks_count}</Typography>
|
||||
<Typography>Samples: {fileSummary.samples_count}</Typography>
|
||||
<SpreadsheetTable
|
||||
raw_data={fileSummary.raw_data}
|
||||
errors={fileSummary.errors}
|
||||
headers={fileSummary.headers}
|
||||
setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))}
|
||||
onCancel={handleCancel}
|
||||
fileBlob={fileBlob} // Pass the original file blob
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
Loading…
x
Reference in New Issue
Block a user