353 lines
13 KiB
TypeScript
353 lines
13 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Paper,
|
|
Tooltip,
|
|
TextField,
|
|
Typography,
|
|
Button,
|
|
Box
|
|
} from '@mui/material';
|
|
import { SpreadsheetService, ShipmentsService, DewarsService, ApiError } from '../../openapi';
|
|
import * as ExcelJS from 'exceljs';
|
|
import { saveAs } from 'file-saver';
|
|
|
|
const SpreadsheetTable = ({
|
|
raw_data,
|
|
errors,
|
|
headers,
|
|
setRawData,
|
|
onCancel,
|
|
fileBlob,
|
|
selectedShipment
|
|
}) => {
|
|
const [localErrors, setLocalErrors] = useState(errors || []);
|
|
const [editingCell, setEditingCell] = useState({});
|
|
const [nonEditableCells, setNonEditableCells] = useState(new Set());
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
const initialNewDewarState = {
|
|
number_of_pucks: 0,
|
|
number_of_samples: 0,
|
|
ready_date: null,
|
|
shipping_date: null,
|
|
arrival_date: null,
|
|
returning_date: null,
|
|
qrcode: 'N/A',
|
|
contact_person_id: selectedShipment?.contact_person?.id,
|
|
return_address_id: selectedShipment?.return_address?.id,
|
|
dewar_name: '',
|
|
tracking_number: 'UNKNOWN',
|
|
status: 'In preparation',
|
|
pucks: []
|
|
};
|
|
|
|
const [newDewar, setNewDewar] = useState(initialNewDewarState);
|
|
|
|
useEffect(() => {
|
|
setNewDewar((prev) => ({
|
|
...prev,
|
|
contact_person_id: selectedShipment?.contact_person?.id,
|
|
return_address_id: selectedShipment?.return_address?.id
|
|
}));
|
|
}, [selectedShipment]);
|
|
|
|
const generateErrorMap = (errorsList) => {
|
|
const errorMap = new Map();
|
|
if (Array.isArray(errorsList)) {
|
|
errorsList.forEach((error) => {
|
|
const key = `${error.row}-${headers[error.cell]}`;
|
|
errorMap.set(key, error.message);
|
|
});
|
|
}
|
|
return errorMap;
|
|
};
|
|
|
|
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];
|
|
const currentRow = updatedRawData[rowIndex];
|
|
const newValue = editingCell[`${rowIndex}-${colIndex}`];
|
|
|
|
if (newValue === undefined) return;
|
|
|
|
if (!currentRow.data) {
|
|
currentRow.data = [];
|
|
}
|
|
|
|
currentRow.data[colIndex] = newValue;
|
|
|
|
setEditingCell((prev) => {
|
|
const updated = { ...prev };
|
|
delete updated[`${rowIndex}-${colIndex}`];
|
|
return updated;
|
|
});
|
|
|
|
try {
|
|
const response = await SpreadsheetService.validateCellValidateCellPost({
|
|
row: currentRow.row_num,
|
|
column: columnName,
|
|
value: newValue
|
|
});
|
|
|
|
if (response.is_valid !== undefined) {
|
|
if (response.is_valid) {
|
|
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,
|
|
{ row: currentRow.row_num, cell: colIndex, message: response.message || 'Invalid value.' }
|
|
];
|
|
setLocalErrors(updatedErrors);
|
|
}
|
|
}
|
|
setRawData(updatedRawData);
|
|
} catch (error) {
|
|
console.error('Validation failed:', error);
|
|
}
|
|
};
|
|
|
|
const handleCellBlur = (rowIndex, colIndex) => {
|
|
handleCellEdit(rowIndex, colIndex);
|
|
};
|
|
|
|
const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;
|
|
|
|
const fieldToCol = {
|
|
'dewarname': 0,
|
|
'puckname': 1,
|
|
'pucktype': 2,
|
|
// Add other fields as needed
|
|
};
|
|
|
|
const createDewarsFromSheet = async (data, contactPerson, returnAddress) => {
|
|
if (!contactPerson?.id || !returnAddress?.id) {
|
|
console.error('contact_person_id or return_address_id is missing');
|
|
return null;
|
|
}
|
|
|
|
const dewars = new Map();
|
|
|
|
const dewarNameIdx = fieldToCol['dewarname'];
|
|
const puckNameIdx = fieldToCol['puckname'];
|
|
const puckTypeIdx = fieldToCol['pucktype'];
|
|
|
|
let puckPositionInDewar = 1;
|
|
|
|
for (const row of data) {
|
|
if (!row.data) {
|
|
console.error(`Row data is missing`);
|
|
continue;
|
|
}
|
|
|
|
const dewarName = typeof row.data[dewarNameIdx] === 'string' ? row.data[dewarNameIdx].trim() : null;
|
|
const puckName = typeof row.data[puckNameIdx] === 'string' ? row.data[puckNameIdx].trim() : null;
|
|
const puckType = typeof row.data[puckTypeIdx] === 'string' ? row.data[puckTypeIdx] : 'Unipuck';
|
|
|
|
console.log(`Processing Dewar: ${dewarName}, Puck: ${puckName}, Type: ${puckType}`);
|
|
|
|
if (dewarName) {
|
|
let dewar;
|
|
if (!dewars.has(dewarName)) {
|
|
dewar = {
|
|
...initialNewDewarState,
|
|
dewar_name: dewarName,
|
|
contact_person_id: contactPerson.id,
|
|
return_address_id: returnAddress.id,
|
|
pucks: []
|
|
};
|
|
dewars.set(dewarName, dewar);
|
|
puckPositionInDewar = 1;
|
|
console.log(`Created new dewar: ${dewarName}`);
|
|
} else {
|
|
dewar = dewars.get(dewarName);
|
|
puckPositionInDewar++;
|
|
console.log(`Found existing dewar: ${dewarName}`);
|
|
}
|
|
|
|
const puck = {
|
|
puck_name: puckName || 'test', // Fixed puck name
|
|
puck_type: puckType || 'Unipuck', // Fixed puck type
|
|
puck_position_in_dewar: puckPositionInDewar
|
|
};
|
|
dewar.pucks.push(puck);
|
|
|
|
console.log(`Added puck: ${JSON.stringify(puck)}`);
|
|
} else {
|
|
console.error('Dewar name is missing in the row');
|
|
}
|
|
}
|
|
|
|
const dewarsArray = Array.from(dewars.values());
|
|
for (const dewar of dewarsArray) {
|
|
try {
|
|
// Call to create the dewar
|
|
const createdDewar = await DewarsService.createDewarDewarsPost(dewar);
|
|
console.log(`Created dewar: ${createdDewar.id}`);
|
|
|
|
// Add dewar to the shipment if created successfully
|
|
if (createdDewar && selectedShipment) {
|
|
await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
|
|
selectedShipment.id,
|
|
createdDewar.id
|
|
);
|
|
console.log(`Added dewar to shipment: ${createdDewar.id}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error adding dewar`, error);
|
|
if (error instanceof ApiError && error.body) {
|
|
console.error('Validation errors:', error.body.detail);
|
|
} else {
|
|
console.error('Unexpected error:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dewarsArray;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (isSubmitting) return;
|
|
if (!headers || headers.length === 0) {
|
|
console.error('Cannot submit, headers are not defined or empty');
|
|
return;
|
|
}
|
|
|
|
if (allCellsValid()) {
|
|
setIsSubmitting(true);
|
|
console.log('All data is valid. Proceeding with submission...');
|
|
|
|
const processedDewars = await createDewarsFromSheet(
|
|
raw_data,
|
|
selectedShipment?.contact_person,
|
|
selectedShipment?.return_address
|
|
);
|
|
|
|
if (processedDewars && processedDewars.length > 0) {
|
|
console.log('Dewars processed successfully.');
|
|
} else {
|
|
console.error('No valid dewars were created.');
|
|
}
|
|
|
|
setIsSubmitting(false);
|
|
} 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);
|
|
console.log('Headers:', headers);
|
|
}, [raw_data, localErrors, headers]);
|
|
|
|
if (!raw_data || !headers) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
return (
|
|
<TableContainer component={Paper}>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
{headers.map((header, index) => (
|
|
<TableCell key={index} align="center">
|
|
<Typography variant="body2">{header}</Typography>
|
|
</TableCell>
|
|
))}
|
|
</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() || isSubmitting}>
|
|
Submit
|
|
</Button>
|
|
<Button variant="contained" onClick={downloadCorrectedSpreadsheet} disabled={!allCellsValid()}>
|
|
Download Corrected Spreadsheet
|
|
</Button>
|
|
</Box>
|
|
<TableBody>
|
|
{raw_data.map((row, rowIndex) => (
|
|
<TableRow key={rowIndex}>
|
|
{headers.map((header, colIndex) => {
|
|
const key = `${row.row_num}-${header}`;
|
|
const errorMessage = errorMap.get(key);
|
|
const isInvalid = !!errorMessage;
|
|
const cellValue = (row.data && row.data[colIndex]) || "";
|
|
const editingValue = editingCell[`${rowIndex}-${colIndex}`];
|
|
const isReadonly = !isInvalid;
|
|
|
|
return (
|
|
<TableCell key={colIndex} align="center">
|
|
<Tooltip title={errorMessage || ""} arrow disableHoverListener={!isInvalid}>
|
|
<TextField
|
|
value={editingValue !== undefined ? editingValue : cellValue}
|
|
onChange={(e) => setEditingCell({ ...editingCell, [`${rowIndex}-${colIndex}`]: e.target.value })}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
handleCellEdit(rowIndex, colIndex);
|
|
}
|
|
}}
|
|
onBlur={() => handleCellBlur(rowIndex, colIndex)}
|
|
error={isInvalid}
|
|
fullWidth
|
|
variant="outlined"
|
|
size="small"
|
|
disabled={isReadonly}
|
|
/>
|
|
</Tooltip>
|
|
</TableCell>
|
|
);
|
|
})}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
);
|
|
};
|
|
|
|
export default SpreadsheetTable; |