
Updated Dewar API methods to use protected endpoints for enhanced security and consistency. Added `pgroups` handling in various frontend components and modified the LogisticsView contact field for clarity. Simplified backend router imports for better readability.
193 lines
8.7 KiB
TypeScript
193 lines
8.7 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import {
|
|
Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, IconButton, Box, CircularProgress
|
|
} from '@mui/material';
|
|
import CloseIcon from '@mui/icons-material/Close';
|
|
import DownloadIcon from '@mui/icons-material/Download';
|
|
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
|
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';
|
|
|
|
interface UploadDialogProps {
|
|
activePgroup: string;
|
|
open: boolean;
|
|
onClose: () => void;
|
|
selectedShipment: any;
|
|
}
|
|
|
|
const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShipment, activePgroup }) => {
|
|
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
const [fileSummary, setFileSummary] = useState<any>(null);
|
|
const [fileBlob, setFileBlob] = useState<Blob | null>(null);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
// Detect the current environment
|
|
const mode = import.meta.env.MODE;
|
|
|
|
// Dynamically set `OpenAPI.BASE` based on the mode
|
|
OpenAPI.BASE =
|
|
mode === 'test'
|
|
? import.meta.env.VITE_OPENAPI_BASE_TEST
|
|
: mode === 'prod'
|
|
? import.meta.env.VITE_OPENAPI_BASE_PROD
|
|
: import.meta.env.VITE_OPENAPI_BASE_DEV;
|
|
|
|
// Log warning if `OpenAPI.BASE` is unresolved
|
|
if (!OpenAPI.BASE) {
|
|
console.error('OpenAPI.BASE is not set. Falling back to a default value.');
|
|
OpenAPI.BASE = 'https://default-url.com'; // Use a consistent fallback
|
|
}
|
|
|
|
// Debug for mode and resolved `BASE`
|
|
console.log('Environment Mode:', mode);
|
|
console.log('Resolved OpenAPI.BASE:', OpenAPI.BASE);
|
|
}, []);
|
|
|
|
const downloadUrl = `${OpenAPI.BASE}/download-template`;
|
|
|
|
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
setUploadError(null);
|
|
setFileSummary(null);
|
|
setFileBlob(file);
|
|
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;
|
|
|
|
try {
|
|
const response = await SpreadsheetService.uploadFileUploadPost(formData);
|
|
|
|
setFileSummary({
|
|
...response,
|
|
});
|
|
setIsModalOpen(true);
|
|
} catch (error: any) {
|
|
if (error.response?.status === 400 && error.response.data?.errors) {
|
|
// Backend provided detailed error messages
|
|
const detailedErrors = error.response.data.errors;
|
|
setUploadError(
|
|
"Validation errors detected in the file. Please review and correct the following issues:\n" +
|
|
detailedErrors
|
|
.map((err: any) => `Row ${err.row}: ${err.errors.map((e: any) => e.msg).join(", ")}`)
|
|
.join("\n")
|
|
);
|
|
} else {
|
|
// Fallback to generic error message
|
|
setUploadError('Failed to upload file. Please try again.');
|
|
}
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setIsModalOpen(false);
|
|
setFileSummary(null);
|
|
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = "";
|
|
}
|
|
};
|
|
|
|
// Count rows with directory defaulted
|
|
const defaultDirectoryCount = fileSummary?.raw_data
|
|
? fileSummary.raw_data.filter((row) => row.default_set).length
|
|
: 0;
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
|
|
<DialogTitle>
|
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|
<Typography variant="h6">Upload Sample Data Sheet</Typography>
|
|
<IconButton onClick={onClose}>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</Box>
|
|
</DialogTitle>
|
|
<DialogContent dividers>
|
|
<Box display="flex" flexDirection="column" alignItems="center" mb={2}>
|
|
<img src={logo} alt="Logo" style={{ width: 200, marginBottom: 16 }} />
|
|
<Typography variant="subtitle1">Latest Spreadsheet Template Version 7</Typography>
|
|
<Typography variant="body2" color="textSecondary">Last update: November 7, 2024</Typography>
|
|
<Button variant="outlined" startIcon={<DownloadIcon />} href={downloadUrl} download sx={{ mt: 1 }}>
|
|
Download XLSX
|
|
</Button>
|
|
<Typography variant="subtitle1" sx={{ mt: 3 }}>Latest Spreadsheet Instructions Version 2.3</Typography>
|
|
<Typography variant="body2" color="textSecondary">Last updated: October 18, 2024</Typography>
|
|
<Button variant="outlined" startIcon={<DownloadIcon />} href="/path/to/instructions.pdf" download sx={{ mt: 1 }}>
|
|
Download PDF
|
|
</Button>
|
|
</Box>
|
|
<Box mt={3}>
|
|
<Button variant="contained" component="label" startIcon={<UploadFileIcon />}>
|
|
Choose a File
|
|
<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>
|
|
|
|
{fileSummary && fileBlob && (
|
|
<Modal open={isModalOpen} onClose={() => setIsModalOpen(false)} title="File Summary">
|
|
<Typography color="success.main">File uploaded successfully!</Typography>
|
|
{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 color="success.main" sx={{ mt: 2 }}>
|
|
{defaultDirectoryCount > 0
|
|
? `${defaultDirectoryCount} rows had the "directory" field auto-assigned to the default value "{sgPuck}/{sgPosition}". These rows are highlighted in green.`
|
|
: "No rows had default values assigned to the 'directory' field."}
|
|
</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}
|
|
selectedShipment={selectedShipment}
|
|
activePgroup={activePgroup}
|
|
addinfo={fileSummary.addinfo}
|
|
/>
|
|
</Modal>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default UploadDialog; |