Added downloading back the excel sheet with corrected values

This commit is contained in:
GotthardG 2024-11-08 13:45:23 +01:00
parent dbebfd6d5a
commit 744a365bfc
4 changed files with 1114 additions and 72 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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>
);
};

View File

@ -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>
)}
</>