fixed bug with spreadsheet import

This commit is contained in:
GotthardG
2024-12-10 15:18:48 +01:00
parent 996fc66d76
commit e28c8b05d4
25 changed files with 819 additions and 70 deletions

2
frontend/.env Normal file
View File

@ -0,0 +1,2 @@
VITE_OPENAPI_BASE_DEV=https://127.0.0.1:8000
VITE_OPENAPI_BASE_TEST=https://mx-aare-test.psi.ch:8000

View File

@ -1,16 +1,28 @@
// fetch-and-generate-openapi.js
import fs from 'fs';
import https from 'https'; // Use https instead of http
import https from 'https';
import { exec } from 'child_process';
import chokidar from 'chokidar';
import path from 'path';
import util from 'util';
const OPENAPI_URL = 'https://127.0.0.1:8000/openapi.json';
const SCHEMA_PATH = path.resolve('./src/openapi.json');
const OUTPUT_DIRECTORY = path.resolve('./openapi');
const SSL_KEY_PATH = path.resolve('../backend/ssl/key.pem'); // Path to SSL key
const SSL_CERT_PATH = path.resolve('../backend/ssl/cert.pem'); // Path to SSL certificate
// Determine the environment
const environment = process.env.NODE_ENV || 'development';
const configFile = `config_${environment}.json`;
// Load the appropriate configuration
let config;
try {
config = JSON.parse(fs.readFileSync(path.resolve('../', configFile), 'utf8'));
} catch (error) {
console.error(`❌ Error reading configuration file: ${error.message}`);
process.exit(1);
}
const OPENAPI_URL = config.OPENAPI_URL;
const SCHEMA_PATH = path.resolve(config.SCHEMA_PATH);
const OUTPUT_DIRECTORY = path.resolve(config.OUTPUT_DIRECTORY);
const SSL_KEY_PATH = path.resolve(config.SSL_KEY_PATH);
const SSL_CERT_PATH = path.resolve(config.SSL_CERT_PATH);
console.log(`Using SCHEMA_PATH: ${SCHEMA_PATH}`);
console.log(`Using OUTPUT_DIRECTORY: ${OUTPUT_DIRECTORY}`);
@ -70,7 +82,6 @@ async function fetchAndGenerate() {
await fs.promises.rm(OUTPUT_DIRECTORY, { recursive: true, force: true });
console.log(`✅ Output directory cleaned at ${OUTPUT_DIRECTORY}`);
// Verify directory removal
if (fs.existsSync(OUTPUT_DIRECTORY)) {
console.error(`❌ Output directory still exists: ${OUTPUT_DIRECTORY}`);
} else {
@ -81,7 +92,6 @@ async function fetchAndGenerate() {
console.log(`Executing debug command: ${command}`);
const { stdout, stderr } = await execPromisified(command);
console.log("🔍 Inside exec callback");
if (stderr) {
console.error(`⚠️ stderr while generating services: ${stderr}`);
} else {
@ -99,13 +109,13 @@ async function fetchAndGenerate() {
}
}
const backendDirectory = '/Users/gotthardg/PycharmProjects/heidi-v2/backend/app';
const backendDirectory = path.resolve('/Users/gotthardg/PycharmProjects/heidi-v2/backend/app');
console.log(`👀 Watching for changes in ${backendDirectory}`);
const watcher = chokidar.watch(backendDirectory, { persistent: true, ignored: [SCHEMA_PATH, OUTPUT_DIRECTORY] });
watcher
.on('add', debounce(fetchAndGenerate, debounceDelay))
.on('change', debounce(fetchAndGenerate, debounceDelay)) // Corrected typo here
.on('change', debounce(fetchAndGenerate, debounceDelay))
.on('unlink', debounce(fetchAndGenerate, debounceDelay));
console.log(`👀 Watching for changes in ${backendDirectory}`);

View File

@ -24,6 +24,7 @@
"axios": "^1.7.7",
"chokidar": "^4.0.1",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"openapi-typescript-codegen": "^0.29.0",
@ -41,6 +42,7 @@
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
"cross-env": "^7.0.3",
"eslint": "^9.11.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
@ -3434,6 +3436,25 @@
"node": ">= 10"
}
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
@ -3560,6 +3581,18 @@
"tslib": "^2.0.3"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/duplexer2": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",

View File

@ -4,12 +4,14 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "node_modules/.bin/vite",
"build": "node_modules/.bin/tsc -b && vite build",
"lint": "node_modules/.bin/eslint .",
"preview": "node_modules/.bin/vite preview",
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"start-dev": "cross-env NODE_ENV=dev vite",
"start-test": "cross-env NODE_ENV=test vite",
"watch:openapi": "node fetch-openapi.js"
},
},
"dependencies": {
"@aldabil/react-scheduler": "^2.9.5",
"@bitnoi.se/react-scheduler": "^0.3.1",
@ -27,6 +29,7 @@
"axios": "^1.7.7",
"chokidar": "^4.0.1",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"openapi-typescript-codegen": "^0.29.0",
@ -44,6 +47,7 @@
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
"cross-env": "^7.0.3",
"eslint": "^9.11.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",

View File

@ -39,7 +39,10 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
useEffect(() => {
OpenAPI.BASE = 'https://127.0.0.1:8000';
const isTestEnv = import.meta.env.MODE === 'test';
OpenAPI.BASE = isTestEnv
? import.meta.env.VITE_OPENAPI_BASE_TEST
: import.meta.env.VITE_OPENAPI_BASE_DEV;
const getContacts = async () => {
try {

View File

@ -1,14 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
IconButton,
Box,
CircularProgress
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';
@ -17,45 +9,39 @@ 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'; // Ensure correct import paths
import Modal from './Modal';
import * as ExcelJS from 'exceljs';
interface UploadDialogProps {
open: boolean;
onClose: () => void;
selectedShipment: any; // Adjust the type based on your implementation
selectedShipment: any;
}
const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShipment }) => {
const [uploadError, setUploadError] = useState<string | null>(null);
const [fileSummary, setFileSummary] = useState<{
data: any[];
errors: { row: number, cell: number, value: any, message: string }[];
raw_data: { row_num: number, data: any[] }[];
dewars_count: number;
dewars: string[];
pucks_count: number;
pucks: string[];
samples_count: number;
samples: string[];
headers: string[];
} | null>(null);
const [fileBlob, setFileBlob] = useState<Blob | null>(null); // New state to store the file blob
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(() => {
OpenAPI.BASE = 'https://127.0.0.1:8000';
const isTestEnv = import.meta.env.MODE === 'test';
OpenAPI.BASE = isTestEnv
? import.meta.env.VITE_OPENAPI_BASE_TEST
: import.meta.env.VITE_OPENAPI_BASE_DEV;
}, []);
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); // Store the file blob
setFileBlob(file);
setIsLoading(true);
if (!file.name.endsWith('.xlsx')) {
@ -68,8 +54,8 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShip
try {
const response = await SpreadsheetService.uploadFileUploadPost(formData);
const { headers, raw_data, errors } = response;
setFileSummary({
data: raw_data,
errors: errors,
@ -82,6 +68,7 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShip
samples_count: 23,
samples: ['Sample1', 'Sample2']
});
setIsLoading(false);
setIsModalOpen(true);
} catch (error) {
@ -115,7 +102,7 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShip
<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="http://127.0.0.1:8000/download-template" download sx={{ mt: 1 }}>
<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>
@ -165,8 +152,8 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShip
headers={fileSummary.headers}
setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))}
onCancel={handleCancel}
fileBlob={fileBlob} // Pass the original file blob
selectedShipment={selectedShipment} // Pass the selected shipment ID
fileBlob={fileBlob}
selectedShipment={selectedShipment}
/>
</Modal>
)}

View File

@ -6,12 +6,8 @@ import { Dewar, OpenAPI, Shipment } from '../../openapi';
import useShipments from '../hooks/useShipments';
import { Grid, Container } from '@mui/material';
// Define props for Shipments View
type ShipmentViewProps = React.PropsWithChildren<Record<string, never>>;
const API_BASE_URL = 'https://127.0.0.1:8000';
OpenAPI.BASE = API_BASE_URL;
const ShipmentView: React.FC<ShipmentViewProps> = () => {
const { shipments, error, defaultContactPerson, fetchAndSetShipments } = useShipments();
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
@ -19,10 +15,13 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
useEffect(() => {
console.log('Updated shipments:', shipments);
}, [shipments]);
const isTestEnv = import.meta.env.MODE === 'test';
OpenAPI.BASE = isTestEnv
? import.meta.env.VITE_OPENAPI_BASE_TEST
: import.meta.env.VITE_OPENAPI_BASE_DEV;
fetchAndSetShipments();
}, []);
// Handlers for selecting shipment and canceling form
const handleSelectShipment = (shipment: Shipment | null) => {
setSelectedShipment(shipment);
setIsCreatingShipment(false);
@ -32,7 +31,6 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
setIsCreatingShipment(false);
};
// Render the shipment content based on state
const renderShipmentContent = () => {
if (isCreatingShipment) {
return (
@ -60,7 +58,6 @@ const ShipmentView: React.FC<ShipmentViewProps> = () => {
return <div>No shipment details available.</div>;
};
// Render the main layout using Grid for layout
return (
<Container maxWidth={false} disableGutters sx={{ display: 'flex', height: '100vh' }}>
<Grid container spacing={2} sx={{ height: '100vh' }}>