aaredb/frontend/src/components/UploadDialog.tsx
GotthardG c2215860bf Refactor Dewar service methods and improve field handling
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.
2025-01-30 13:39:49 +01:00

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;