Connected frontend shipments to backend
This commit is contained in:
@ -1,11 +1,22 @@
|
||||
import React from 'react';
|
||||
import ResponsiveAppBar from './components/ResponsiveAppBar.tsx';
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
import ResponsiveAppBar from './components/ResponsiveAppBar';
|
||||
import ShipmentView from './pages/ShipmentView';
|
||||
import HomePage from './pages/HomeView'; // Assuming this is a default export
|
||||
import ResultsView from './pages/ResultsView';
|
||||
import PlanningView from './pages/PlanningView';
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<Router>
|
||||
<ResponsiveAppBar />
|
||||
</div>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/shipments" element={<ShipmentView />} />
|
||||
<Route path="/planning" element={<PlanningView />} />
|
||||
<Route path="/results" element={<ResultsView />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -217,7 +217,7 @@ const Calendar: React.FC = () => {
|
||||
eventContent={eventContent}
|
||||
height={700}
|
||||
headerToolbar={{
|
||||
left: 'prev,next today',
|
||||
left: 'prev,next',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth',
|
||||
}}
|
||||
|
@ -11,36 +11,21 @@ import Container from '@mui/material/Container';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { Button } from '@mui/material';
|
||||
import { Link } from 'react-router-dom'; // Import Link for navigation
|
||||
import logo from '../assets/icons/psi_01_sn.svg';
|
||||
import '../App.css';
|
||||
import { Shipment, Dewar, ContactPerson, Address, Proposal } from '../types.ts'; // Import types from a single statement
|
||||
import ShipmentView from './ShipmentView.tsx';
|
||||
import PlanningView from './PlanningView.tsx';
|
||||
|
||||
const pages = ['Validator', 'Shipments', 'Samples', 'Planning', 'Experiments', 'Results', 'Docs'];
|
||||
// Page definitions for navigation
|
||||
const pages = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Shipments', path: '/shipments' },
|
||||
{ name: 'Planning', path: '/planning' },
|
||||
{ name: 'Results', path: '/results' }
|
||||
];
|
||||
|
||||
const ResponsiveAppBar: React.FC = () => {
|
||||
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
|
||||
const [selectedPage, setSelectedPage] = useState<string>('Shipments');
|
||||
const [newShipment, setNewShipment] = useState<Shipment>({
|
||||
shipment_id: '',
|
||||
shipment_name: '',
|
||||
shipment_status: '',
|
||||
number_of_dewars: 0,
|
||||
shipment_date: '',
|
||||
return_address: [], // Correctly initialize return_address
|
||||
contact_person: null, // Use null
|
||||
dewars: [],
|
||||
});
|
||||
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
||||
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
||||
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
|
||||
|
||||
// Define missing state variables for contacts, addresses, and proposals
|
||||
const [contactPersons, setContactPersons] = useState<ContactPerson[]>([]);
|
||||
const [returnAddresses, setReturnAddresses] = useState<Address[]>([]);
|
||||
const [proposals, setProposals] = useState<Proposal[]>([]);
|
||||
|
||||
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElNav(event.currentTarget);
|
||||
@ -50,11 +35,6 @@ const ResponsiveAppBar: React.FC = () => {
|
||||
setAnchorElNav(null);
|
||||
};
|
||||
|
||||
const handlePageClick = (page: string) => {
|
||||
setSelectedPage(page);
|
||||
handleCloseNavMenu();
|
||||
};
|
||||
|
||||
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
@ -63,36 +43,15 @@ const ResponsiveAppBar: React.FC = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
// Updated selectShipment to accept Shipment | null
|
||||
const selectShipment = (shipment: Shipment | null) => {
|
||||
setSelectedShipment(shipment);
|
||||
setIsCreatingShipment(false);
|
||||
setSelectedDewar(null);
|
||||
};
|
||||
|
||||
const handleSaveShipment = () => {
|
||||
console.log('Saving shipment:', newShipment);
|
||||
setIsCreatingShipment(false);
|
||||
setNewShipment({
|
||||
shipment_id: '',
|
||||
shipment_name: '',
|
||||
shipment_status: '',
|
||||
number_of_dewars: 0,
|
||||
shipment_date: '',
|
||||
return_address: [], // Add return_address to the reset state
|
||||
contact_person: null, // Use null
|
||||
dewars: [],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="static" sx={{ backgroundColor: '#2F4858' }}>
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
<a className="nav" href="">
|
||||
{/* Logo and Title */}
|
||||
<Link to="/" style={{ textDecoration: 'none' }}>
|
||||
<img src={logo} height="50px" alt="PSI logo" />
|
||||
</a>
|
||||
</Link>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
@ -109,6 +68,8 @@ const ResponsiveAppBar: React.FC = () => {
|
||||
>
|
||||
Heidi v2
|
||||
</Typography>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||
<IconButton
|
||||
size="large"
|
||||
@ -134,23 +95,30 @@ const ResponsiveAppBar: React.FC = () => {
|
||||
onClose={handleCloseNavMenu}
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<MenuItem key={page} onClick={() => handlePageClick(page)}>
|
||||
{page}
|
||||
<MenuItem key={page.name} onClick={handleCloseNavMenu}>
|
||||
<Link to={page.path} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||
{page.name}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
{pages.map((page) => (
|
||||
<Button
|
||||
key={page}
|
||||
onClick={() => handlePageClick(page)}
|
||||
key={page.name}
|
||||
component={Link} // Make button a Link
|
||||
to={page.path}
|
||||
sx={{ my: 2, color: 'white', display: 'block', fontSize: '1rem', padding: '12px 24px' }}
|
||||
>
|
||||
{page}
|
||||
{page.name}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* User Settings Menu */}
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title="Open settings">
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
@ -180,25 +148,6 @@ const ResponsiveAppBar: React.FC = () => {
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Box sx={{ width: '100%', borderRight: '1px solid #ccc', padding: 2 }}>
|
||||
{selectedPage === 'Shipments' && (
|
||||
<ShipmentView
|
||||
newShipment={newShipment}
|
||||
setNewShipment={setNewShipment}
|
||||
isCreatingShipment={isCreatingShipment}
|
||||
setIsCreatingShipment={setIsCreatingShipment}
|
||||
selectedShipment={selectedShipment}
|
||||
selectShipment={selectShipment} // Now accepts Shipment | null
|
||||
selectedDewar={selectedDewar}
|
||||
setSelectedDewar={setSelectedDewar}
|
||||
handleSaveShipment={handleSaveShipment}
|
||||
contactPersons={contactPersons}
|
||||
returnAddresses={returnAddresses}
|
||||
proposals={proposals}
|
||||
/>
|
||||
)}
|
||||
{selectedPage === 'Planning' && <PlanningView />}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -5,12 +5,13 @@ import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete'; // Import delete icon
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile'; // Import the upload icon
|
||||
import UploadDialog from './UploadDialog.tsx'; // Import the UploadDialog component
|
||||
import { Shipment } from '../types.ts'; // Ensure Shipment type is correctly imported
|
||||
import {Shipment} from '../types.ts'; // Ensure Shipment type is correctly imported
|
||||
import { SxProps } from '@mui/material';
|
||||
import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg';
|
||||
import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg';
|
||||
import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg';
|
||||
import bottleRed from '../assets/icons/bottle-svgrepo-com-red.svg';
|
||||
import {Shipment_Input, DefaultService, OpenAPI} from "../../openapi";
|
||||
|
||||
interface ShipmentPanelProps {
|
||||
selectedPage: string;
|
||||
@ -22,17 +23,16 @@ interface ShipmentPanelProps {
|
||||
}
|
||||
|
||||
const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
setIsCreatingShipment,
|
||||
newShipment,
|
||||
setNewShipment,
|
||||
selectedPage,
|
||||
//setIsCreatingShipment,
|
||||
//newShipment,
|
||||
//setNewShipment,
|
||||
//selectedPage,
|
||||
selectShipment,
|
||||
sx,
|
||||
}) => {
|
||||
const [shipments, setShipments] = useState<Shipment[]>([]);
|
||||
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
||||
|
||||
const handleOpenUploadDialog = () => {
|
||||
@ -43,6 +43,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
setUploadDialogOpen(false);
|
||||
};
|
||||
|
||||
|
||||
// Status icon mapping
|
||||
const statusIconMap: Record<string, string> = {
|
||||
"In Transit": bottleYellow,
|
||||
@ -52,9 +53,13 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
OpenAPI.BASE='http://127.0.0.1:8000'
|
||||
const fetchShipments = async () => {
|
||||
console.log('trying to fetch some shipments');
|
||||
try {
|
||||
const response = await fetch('/shipmentsdb.json');
|
||||
DefaultService.getShipmentsShipmentsGet().then((s : Shipment_Input[]) => {
|
||||
setShipments(s);
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
@ -95,21 +100,21 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setNewShipment({
|
||||
shipment_id: '', // Ensure this matches the Shipment type
|
||||
shipment_name: '',
|
||||
shipment_status: '',
|
||||
number_of_dewars: 0,
|
||||
shipment_date: '',
|
||||
contact_person: null, // Keep this as null to match Shipment type
|
||||
dewars: [],
|
||||
return_address: [], // Make sure return_address is initialized as an array
|
||||
proposal_number: undefined, // Optional property
|
||||
comments: '', // Optional property
|
||||
});
|
||||
setIsCreatingShipment(true);
|
||||
}}
|
||||
//onClick={() => {
|
||||
// setNewShipment({
|
||||
// shipment_id: '', // Ensure this matches the Shipment type
|
||||
// shipment_name: '',
|
||||
// shipment_status: '',
|
||||
// number_of_dewars: 0,
|
||||
// shipment_date: '',
|
||||
// contact_person: null, // Keep this as null to match Shipment type
|
||||
// dewars: [],
|
||||
// return_address: [], // Make sure return_address is initialized as an array
|
||||
// proposal_number: undefined, // Optional property
|
||||
// comments: '', // Optional property
|
||||
// });
|
||||
// setIsCreatingShipment(true);
|
||||
//}}
|
||||
sx={{ marginBottom: 2, padding: '10px 16px' }}
|
||||
>
|
||||
Create Shipment
|
||||
|
@ -130,6 +130,9 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
|
||||
)}
|
||||
{fileSummary && (
|
||||
<Box mt={2}>
|
||||
<Typography color="success.main">
|
||||
File uploaded successfully!
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>File Summary:</strong>
|
||||
</Typography>
|
||||
|
@ -1,18 +1,25 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import DewarDetails from './DewarDetails.tsx';
|
||||
import { Shipment, ContactPerson, Address, Dewar } from '../types.ts';
|
||||
import shipmentData from '../../public/shipmentsdb.json'; // Adjust the path to where your JSON file is stored
|
||||
import shipmentData from '../../public/shipmentsdb.json';
|
||||
import {Contact, DefaultService, OpenAPI} from "../../openapi";
|
||||
|
||||
const ParentComponent = () => {
|
||||
const [dewars, setDewars] = useState<Dewar[]>([]);
|
||||
const [contactPersons, setContactPersons] = useState<ContactPerson[]>([]);
|
||||
const [contactPersons, setContactPersons] = useState<Contact[]>([]);
|
||||
const [returnAddresses, setReturnAddresses] = useState<Address[]>([]);
|
||||
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
OpenAPI.BASE='http://127.0.0.1:8000'
|
||||
|
||||
const firstShipment = shipmentData.shipments[0] as Shipment; // Ensure proper typing
|
||||
setSelectedShipment(firstShipment);
|
||||
setContactPersons(firstShipment.contact_person || []); // Set to array directly
|
||||
console.log('this is meeee');
|
||||
DefaultService.getContactsContactsGet().then((c : Contact[]) => {
|
||||
setContactPersons(c);
|
||||
});
|
||||
|
||||
if (firstShipment.return_address) {
|
||||
setReturnAddresses(firstShipment.return_address); // Set to array directly
|
||||
}
|
157
frontend/src/keep/ResponsiveAppBar.tsx
Normal file
157
frontend/src/keep/ResponsiveAppBar.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import React, { useState } from 'react';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import Container from '@mui/material/Container';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { Button } from '@mui/material';
|
||||
import logo from '../assets/icons/psi_01_sn.svg';
|
||||
import '../App.css';
|
||||
|
||||
// Page definitions for navigation
|
||||
const pages = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Shipments', path: '/shipments' },
|
||||
{ name: 'Planning', path: '/planning' },
|
||||
{ name: 'Results', path: '/results' }
|
||||
];
|
||||
|
||||
const ResponsiveAppBar: React.FC = () => {
|
||||
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
|
||||
const [selectedPage, setSelectedPage] = useState<string>('Home'); // Default to 'Home' page
|
||||
|
||||
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElNav(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseNavMenu = () => {
|
||||
setAnchorElNav(null);
|
||||
};
|
||||
|
||||
const handlePageClick = (page: string) => {
|
||||
setSelectedPage(page);
|
||||
handleCloseNavMenu();
|
||||
};
|
||||
|
||||
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="static" sx={{ backgroundColor: '#2F4858' }}>
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
{/* Logo and Title */}
|
||||
<a className="nav" href="">
|
||||
<img src={logo} height="50px" alt="PSI logo" />
|
||||
</a>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="a"
|
||||
href="#app-bar-with-responsive-menu"
|
||||
sx={{
|
||||
mr: 2,
|
||||
display: { xs: 'none', md: 'flex' },
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 300,
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
Heidi v2
|
||||
</Typography>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||
<IconButton
|
||||
size="large"
|
||||
aria-label="menu"
|
||||
onClick={handleOpenNavMenu}
|
||||
color="inherit"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElNav}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={Boolean(anchorElNav)}
|
||||
onClose={handleCloseNavMenu}
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<MenuItem key={page.name} onClick={() => handlePageClick(page.name)}>
|
||||
{page.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
{pages.map((page) => (
|
||||
<Button
|
||||
key={page.name}
|
||||
onClick={() => handlePageClick(page.name)}
|
||||
sx={{ my: 2, color: 'white', display: 'block', fontSize: '1rem', padding: '12px 24px' }}
|
||||
>
|
||||
{page.name}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* User Settings Menu */}
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title="Open settings">
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
sx={{ mt: '45px' }}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
<MenuItem onClick={handleCloseUserMenu}>DUO</MenuItem>
|
||||
<MenuItem onClick={handleCloseUserMenu}>Logout</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponsiveAppBar;
|
@ -1,17 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Button, Stack, TextField } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
Stack,
|
||||
TextField,
|
||||
Stepper,
|
||||
Step,
|
||||
StepLabel,
|
||||
} from '@mui/material';
|
||||
import ShipmentForm from './ShipmentForm.tsx';
|
||||
import DewarDetails from './DewarDetails.tsx';
|
||||
import { Shipment, Dewar, ContactPerson, Proposal, Address } from '../types.ts';
|
||||
import { SxProps } from '@mui/system';
|
||||
import QRCode from 'react-qr-code';
|
||||
import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg';
|
||||
import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg';
|
||||
import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg';
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg';
|
||||
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
|
||||
import StoreIcon from "@mui/icons-material/Store";
|
||||
import DeleteIcon from "@mui/icons-material/Delete"; // Import delete icon
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {Contact} from "../../openapi"; // Import delete icon
|
||||
|
||||
interface ShipmentDetailsProps {
|
||||
selectedShipment: Shipment | null;
|
||||
@ -20,7 +27,7 @@ interface ShipmentDetailsProps {
|
||||
newShipment: Shipment;
|
||||
setNewShipment: React.Dispatch<React.SetStateAction<Shipment>>;
|
||||
handleSaveShipment: () => void;
|
||||
contactPersons: ContactPerson[];
|
||||
contactPersons: Contact[];
|
||||
proposals: Proposal[];
|
||||
returnAddresses: Address[];
|
||||
sx?: SxProps;
|
||||
@ -46,16 +53,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
tracking_number: '',
|
||||
});
|
||||
|
||||
const shippingStatusMap: { [key: string]: string } = {
|
||||
"not shipped": "grey",
|
||||
"shipped": "yellow",
|
||||
"arrived": "green",
|
||||
};
|
||||
|
||||
const arrivalStatusMap: { [key: string]: string } = {
|
||||
"not arrived": "grey",
|
||||
"arrived": "green",
|
||||
};
|
||||
// Step titles based on your status
|
||||
const steps = ['Ready for Shipping', 'Shipped', 'Arrived'];
|
||||
|
||||
React.useEffect(() => {
|
||||
if (localSelectedDewar) {
|
||||
@ -143,6 +142,17 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
console.log('Add new return address:', address);
|
||||
};
|
||||
|
||||
// Function to determine the color of the step icon
|
||||
const getStepIconColor = (dewar: Dewar) => {
|
||||
const { status, shippingStatus, arrivalStatus } = dewar;
|
||||
if (status === 'Ready for Shipping') return 'green'; // Bottle Icon
|
||||
if (shippingStatus === 'shipped') return 'green'; // Plane Icon
|
||||
if (shippingStatus === 'not shipped') return 'yellow'; // Plane Icon
|
||||
if (arrivalStatus === 'arrived') return 'green'; // Store Icon
|
||||
if (arrivalStatus === 'not arrived') return 'yellow'; // Store Icon
|
||||
return 'grey'; // Default color
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ ...sx, padding: 2, textAlign: 'left' }}>
|
||||
{/* Add Dewar Button - only visible if no dewar is selected */}
|
||||
@ -177,7 +187,6 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
<Typography variant="h5">{selectedShipment.shipment_name}</Typography>
|
||||
<Typography variant="body1">Number of Pucks: {totalPucks}</Typography>
|
||||
<Typography variant="body1">Number of Samples: {totalSamples}</Typography>
|
||||
@ -195,28 +204,26 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
returnAddresses={returnAddresses} // Pass return addresses
|
||||
addNewContactPerson={addNewContactPerson} // Pass function to add a new contact person
|
||||
addNewReturnAddress={addNewReturnAddress} // Pass function to add a new return address
|
||||
shipping_date={localSelectedDewar?.shipping_date} // Ensure these are passed
|
||||
arrival_date={localSelectedDewar?.arrival_date}
|
||||
/>
|
||||
)}
|
||||
{selectedShipment.dewars.map((dewar: Dewar) => (
|
||||
|
||||
{selectedShipment.dewars.map((dewar) => (
|
||||
<Button
|
||||
key={dewar.tracking_number}
|
||||
onClick={() => handleDewarSelection(dewar)}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
backgroundColor: localSelectedDewar?.tracking_number === dewar.tracking_number ? '#d0f0c0' : '#f0f0f0', // Highlight if selected
|
||||
padding: 2,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 1,
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: 1,
|
||||
backgroundColor: localSelectedDewar?.tracking_number === dewar.tracking_number ? '#f0f0f0' : '#fff', // Color when selected
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
backgroundColor: '#e0e0e0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@ -226,7 +233,20 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
{dewar.qrcode ? (
|
||||
<QRCode value={dewar.qrcode} size={70} />
|
||||
) : (
|
||||
<Typography variant="body2" color="textSecondary">No QR code available</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: 70,
|
||||
height: 70,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px dashed #ccc', // Dashed border for placeholder
|
||||
borderRadius: 1,
|
||||
color: 'text.secondary'
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">No QR Code</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@ -237,48 +257,37 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
<Typography variant="body2">Tracking Number: {dewar.tracking_number}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', flexDirection: 'row', justifyContent: 'space-evenly' }}>
|
||||
{/* Status icons and date information */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<img
|
||||
src={dewar.status === "in preparation" ? bottleYellow : (dewar.status === "ready for shipping" ? bottleGreen : bottleGrey)}
|
||||
alt={`Status: ${dewar.status}`}
|
||||
style={{ width: '40px', height: '40px', marginBottom: '4px' }}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ fontSize: '12px' }} color="textSecondary">
|
||||
{dewar.ready_date ? `Ready: ${new Date(dewar.ready_date).toLocaleDateString()}` : 'N/A'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<ArrowForwardIcon sx={{ margin: '0 8px', fontSize: '40px', alignSelf: 'center' }} />
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<AirplanemodeActiveIcon
|
||||
sx={{
|
||||
color: shippingStatusMap[dewar.shippingStatus || ""] || "grey",
|
||||
fontSize: '40px',
|
||||
marginBottom: '4px'
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ fontSize: '12px' }} color="textSecondary">
|
||||
{dewar.shipping_date ? `Shipped: ${new Date(dewar.shipping_date).toLocaleDateString()}` : 'N/A'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<ArrowForwardIcon sx={{ margin: '0 8px', fontSize: '40px', alignSelf: 'center' }} />
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<StoreIcon
|
||||
sx={{
|
||||
color: arrivalStatusMap[dewar.arrivalStatus || ""] || "grey",
|
||||
fontSize: '40px',
|
||||
marginBottom: '4px'
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ fontSize: '12px' }} color="textSecondary">
|
||||
{dewar.arrival_date ? `Arrived: ${new Date(dewar.arrival_date).toLocaleDateString()}` : 'N/A'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
{/* Horizontal Stepper for status */}
|
||||
<Stepper alternativeLabel activeStep={steps.indexOf(dewar.status) !== -1 ? steps.indexOf(dewar.status) : 0}>
|
||||
{steps.map((label, index) => (
|
||||
<Step key={label}>
|
||||
<StepLabel StepIconComponent={({ active, completed }) => {
|
||||
const color = getStepIconColor(dewar); // Use status for color
|
||||
return (
|
||||
<span style={{ color }}>
|
||||
{index === 0 ? (
|
||||
<img src={bottleIcon} alt="Bottle Icon" style={{ width: 24, height: 24 }} />
|
||||
) : index === 1 ? (
|
||||
<AirplanemodeActiveIcon style={{ color }} />
|
||||
) : index === 2 ? (
|
||||
<StoreIcon style={{ color , width: 24, height: 24}} />
|
||||
) : (
|
||||
<StoreIcon style={{ color }} // Use store icon for arrival status
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}}>{label}</StepLabel>
|
||||
{/* Display associated date */}
|
||||
<Typography variant="body2">
|
||||
{index === 0 ? dewar.ready_date :
|
||||
index === 1 ? dewar.shipping_date :
|
||||
index === 2 ? dewar.arrival_date : ''}
|
||||
</Typography>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
|
||||
{/* Delete button if the dewar is selected */}
|
||||
{localSelectedDewar?.tracking_number === dewar.tracking_number && (
|
||||
@ -290,9 +299,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
height: '40px',
|
||||
marginLeft: 2,
|
||||
padding: 0,
|
||||
alignSelf: 'center'
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
title="Delete Dewar"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
@ -3,6 +3,8 @@ import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormContro
|
||||
import { SelectChangeEvent } from '@mui/material';
|
||||
import { Shipment, ContactPerson, Proposal, Address } from '../types.ts'; // Adjust import paths as necessary
|
||||
import { SxProps } from '@mui/material';
|
||||
import {useEffect} from "react";
|
||||
import {DefaultService} from "../../openapi";
|
||||
|
||||
interface ShipmentFormProps {
|
||||
newShipment: Shipment;
|
||||
@ -33,21 +35,21 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
});
|
||||
const [newReturnAddress, setNewReturnAddress] = React.useState('');
|
||||
|
||||
const handleContactPersonChange = (event: SelectChangeEvent<string>) => {
|
||||
const handleContactPersonChange = (event: SelectChangeEvent) => {
|
||||
const value = event.target.value;
|
||||
if (value === 'new') {
|
||||
setIsCreatingContactPerson(true);
|
||||
setNewShipment({ ...newShipment, contact_person: [] }); // Set to empty array for new person
|
||||
} else {
|
||||
setIsCreatingContactPerson(false);
|
||||
const selectedPerson = contactPersons.find((person) => person.name === value) || null;
|
||||
const selectedPerson = contactPersons.find((person) => person.lastname === value) || null;
|
||||
if (selectedPerson) {
|
||||
setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); // Wrap in array
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleReturnAddressChange = (event: SelectChangeEvent<string>) => {
|
||||
const handleReturnAddressChange = (event: SelectChangeEvent) => {
|
||||
const value = event.target.value;
|
||||
if (value === 'new') {
|
||||
setIsCreatingReturnAddress(true);
|
||||
@ -105,7 +107,7 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Contact Person</InputLabel>
|
||||
<Select
|
||||
value={newShipment.contact_person?.[0]?.name || ''} // Access first contact person
|
||||
value={newShipment.contact_person?.[0]?.lastname || ''}
|
||||
onChange={handleContactPersonChange}
|
||||
displayEmpty
|
||||
>
|
||||
@ -113,8 +115,8 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
|
||||
<em>Select a Contact Person</em>
|
||||
</MenuItem>
|
||||
{contactPersons.map((person) => (
|
||||
<MenuItem key={person.id} value={person.name}>
|
||||
{person.name}
|
||||
<MenuItem key={person.id} value={person.lastname}>
|
||||
{person.lastname}
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem value="new">
|
@ -3,25 +3,12 @@ import { Grid } from '@mui/material';
|
||||
import ShipmentPanel from './ShipmentPanel.tsx';
|
||||
import ShipmentDetails from './ShipmentDetails.tsx';
|
||||
import ShipmentForm from './ShipmentForm.tsx';
|
||||
import ParentComponent from "./ParentComponent.tsx";
|
||||
import { Shipment, Dewar } from '../types.ts';
|
||||
import { ContactPerson, Address, Proposal } from '../types.ts';
|
||||
|
||||
interface ShipmentProps {
|
||||
newShipment: Shipment;
|
||||
setNewShipment: React.Dispatch<React.SetStateAction<Shipment>>;
|
||||
isCreatingShipment: boolean;
|
||||
setIsCreatingShipment: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
selectedShipment: Shipment | null;
|
||||
selectShipment: (shipment: Shipment | null) => void; // Allow null for deselection
|
||||
selectedDewar: Dewar | null;
|
||||
setSelectedDewar: React.Dispatch<React.SetStateAction<Dewar | null>>;
|
||||
handleSaveShipment: () => void;
|
||||
contactPersons: ContactPerson[];
|
||||
returnAddresses: Address[];
|
||||
proposals: Proposal[];
|
||||
}
|
||||
|
||||
const ShipmentView: React.FC<ShipmentProps> = ({
|
||||
const ShipmentView: React.FC<ParentComponent> = ({
|
||||
newShipment,
|
||||
setNewShipment,
|
||||
isCreatingShipment,
|
||||
@ -41,11 +28,11 @@ const ShipmentView: React.FC<ShipmentProps> = ({
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={3} // Adjust width for left column
|
||||
md={3}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 0, // Do not allow the panel to grow
|
||||
flexGrow: 0,
|
||||
}}
|
||||
>
|
||||
<ShipmentPanel
|
||||
@ -53,7 +40,7 @@ const ShipmentView: React.FC<ShipmentProps> = ({
|
||||
setIsCreatingShipment={setIsCreatingShipment}
|
||||
newShipment={newShipment}
|
||||
setNewShipment={setNewShipment}
|
||||
selectShipment={selectShipment} // This now accepts Shipment | null
|
||||
selectShipment={selectShipment}
|
||||
/>
|
||||
</Grid>
|
||||
|
8
frontend/src/pages/HomeView.tsx
Normal file
8
frontend/src/pages/HomeView.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
// components/HomePage.tsx
|
||||
import React from 'react';
|
||||
|
||||
const HomeView: React.FC = () => {
|
||||
return <div>Welcome to the Home Page</div>;
|
||||
};
|
||||
|
||||
export default HomeView;
|
@ -1,9 +1,10 @@
|
||||
// Planning.tsx
|
||||
import React from 'react';
|
||||
import CustomCalendar from './Calendar.tsx';
|
||||
import CustomCalendar from '../components/Calendar.tsx';
|
||||
|
||||
const PlanningView: React.FC = () => {
|
||||
return <CustomCalendar />;
|
||||
//return <div>Welcome to the Planning Page</div>;
|
||||
};
|
||||
|
||||
export default PlanningView;
|
8
frontend/src/pages/ResultsView.tsx
Normal file
8
frontend/src/pages/ResultsView.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
// components/ResultView.tsx
|
||||
import React from 'react';
|
||||
|
||||
const ResultsView: React.FC = () => {
|
||||
return <div>Results page</div>;
|
||||
};
|
||||
|
||||
export default ResultsView;
|
44
frontend/src/pages/ShipmentView.tsx
Normal file
44
frontend/src/pages/ShipmentView.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useState } from 'react';
|
||||
import ShipmentPanel from '../components/ShipmentPanel';
|
||||
import { Shipment } from '../types';
|
||||
|
||||
const ShipmentView: React.FC = () => {
|
||||
//const [isCreatingShipment, setIsCreatingShipment] = useState(false);
|
||||
//const [newShipment, setNewShipment] = useState<Shipment>({
|
||||
// shipment_id: '',
|
||||
// shipment_name: '',
|
||||
// shipment_status: '',
|
||||
// number_of_dewars: 0,
|
||||
// shipment_date: '',
|
||||
// contact_person: null,
|
||||
// dewars: [],
|
||||
// return_address: [],
|
||||
// proposal_number: undefined,
|
||||
// comments: '',
|
||||
//});
|
||||
|
||||
const selectShipment = (shipment: Shipment | null) => {
|
||||
console.log('Selected Shipment:', shipment);
|
||||
// Additional logic for selected shipment
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', padding: '16px' }}>
|
||||
{/* Shipment Panel on the left side */}
|
||||
<ShipmentPanel
|
||||
selectedPage="shipments"
|
||||
//setIsCreatingShipment={setIsCreatingShipment}
|
||||
//newShipment={newShipment}
|
||||
//setNewShipment={setNewShipment}
|
||||
selectShipment={selectShipment}
|
||||
sx={{ width: '300px' }} // Adjust width as needed
|
||||
/>
|
||||
{/* Additional content can go here */}
|
||||
<div style={{ marginLeft: '16px', flexGrow: 1 }}>
|
||||
{/* Other components or information related to selected shipment can go here */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipmentView;
|
@ -1,6 +1,9 @@
|
||||
export interface ContactPerson {
|
||||
id: string; // ID is a string
|
||||
name: string; // Name of the contact person
|
||||
id: string;
|
||||
lastname: string;
|
||||
firstname: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
@ -19,14 +22,6 @@ export interface Proposal {
|
||||
number: string; // Proposal number
|
||||
}
|
||||
|
||||
export interface NewShipment {
|
||||
shipment_name: string;
|
||||
contact_person: string; // Could also be a ContactPerson type if you want to relate it
|
||||
proposal_number: string;
|
||||
return_address: string; // Could be changed to Address
|
||||
comments: string;
|
||||
}
|
||||
|
||||
export interface Dewar {
|
||||
id: string; // Add this line
|
||||
dewar_name: string;
|
||||
@ -46,8 +41,8 @@ export interface Dewar {
|
||||
|
||||
export interface Shipment {
|
||||
shipment_id: string;
|
||||
shipment_date: string;
|
||||
shipment_name: string;
|
||||
shipment_date: string;
|
||||
number_of_dewars: number;
|
||||
shipment_status: string;
|
||||
contact_person: ContactPerson[] | null; // Change to an array to accommodate multiple contacts
|
||||
@ -55,25 +50,4 @@ export interface Shipment {
|
||||
return_address: Address[]; // Change to an array of Address
|
||||
comments?: string;
|
||||
dewars: Dewar[];
|
||||
}
|
||||
|
||||
import { DateLocalizer, Event as BigCalendarEvent } from 'react-big-calendar'; // Adjust according to your import structure
|
||||
|
||||
export interface CustomEvent extends BigCalendarEvent {
|
||||
id: number; // Custom property
|
||||
title: string; // Ensure title is included
|
||||
start: Date;
|
||||
end: Date;
|
||||
beamline?: string; // Optional property for beamline
|
||||
}
|
||||
|
||||
export interface Shift {
|
||||
beamline: string;
|
||||
local_contact: string;
|
||||
beamtime_shift: 'morning' | 'evening' | 'night';
|
||||
}
|
||||
|
||||
export interface Beamtime {
|
||||
date: string;
|
||||
shifts: Shift[];
|
||||
}
|
Reference in New Issue
Block a user