import { PureComponent } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import CircularProgress from '@mui/material/CircularProgress';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
import MenuItem from '@mui/material/MenuItem';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import CloseIcon from '@mui/icons-material/Close';
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { enqueueSnackbar } from 'notistack';
import AppDialog from '../AppDialog';
import { shipment as shipmentApi, shipping as shippingApi } from '../../api';
import { notifyError } from '../../utils/notify';
import { formatDate } from './manifestConfig';
import orderAutomat from '../../automats/orderAutomat';
import shipmentAutomat from '../../automats/shipmentAutomat';
import carrierAutomat from '../../automats/carrierAutomat';
import { SURFACE_RAISED, WHITE } from '../../colors';
// MARK:
class AddPackageDialog extends PureComponent {
constructor(props) {
super(props);
const carriers = carrierAutomat.state.carriers || [];
this.state = {
carriers,
carrierId: carriers[0]?.id ?? '',
trackingCode: '',
type: 'parcel',
state: 'draft',
saving: false,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.unsub = carrierAutomat.subscribe(this, (state) => {
const carriers = state.carriers || [];
const currentId = this.state.carrierId;
const valid = carriers.some((c) => c.id === currentId);
return {
carriers,
carrierId: valid ? currentId : (carriers[0]?.id ?? ''),
};
});
if (carrierAutomat.state.status !== 'ready') {
void carrierAutomat.actions.load().catch(() => {});
}
}
componentWillUnmount() {
this.unsub?.();
}
async handleSubmit() {
const { shipment, onClose, onSuccess } = this.props;
const { carrierId, trackingCode, type, state } = this.state;
if (!shipment?.id || !carrierId) return;
this.setState({ saving: true });
try {
await shippingApi.create(shipment.id, {
carrierId: Number(carrierId),
trackingCode: trackingCode.trim() || undefined,
type,
state,
});
enqueueSnackbar('Paket angelegt.', { variant: 'success' });
await Promise.all([
orderAutomat.actions.reload(),
shipmentAutomat.actions.reload(),
]);
onSuccess?.();
onClose();
} catch (err) {
notifyError(err);
this.setState({ saving: false });
}
}
render() {
const { open, onClose, shipment } = this.props;
const { carriers, carrierId, trackingCode, type, state, saving } = this.state;
const canSubmit = Boolean(carrierId) && !saving;
return (
Paket zu Lieferschein hinzufügen
Lieferschein: {shipment?.number}
this.setState({ carrierId: e.target.value })}
error={carriers.length === 0}
helperText={
carriers.length === 0
? 'Keine Lieferarten eingerichtet (unter Auftrag → Lieferarten anlegen)'
: undefined
}
>
{carriers.map((c) => (
))}
this.setState({ trackingCode: e.target.value })}
placeholder="z.B. 00340434234234234"
/>
this.setState({ type: e.target.value })}
>
this.setState({ state: e.target.value })}
helperText={state === 'shipped' ? 'Bucht bei Auswahl direkt das Lager ab.' : undefined}
>
);
}
}
/**
* Modal to view and manage an existing Lieferschein (Shipment) directly from the Order page.
*/
export default class ShipmentDetailDialog extends PureComponent {
constructor(props) {
super(props);
this.state = {
shipment: props.shipment || null,
loading: false,
error: null,
addPackageDialogOpen: false,
busyAction: false,
};
this.handleRefresh = this.handleRefresh.bind(this);
this.handleDeletePackage = this.handleDeletePackage.bind(this);
this.handleMarkPackageShipped = this.handleMarkPackageShipped.bind(this);
this.handleStateChange = this.handleStateChange.bind(this);
}
componentDidMount() {
if (this.props.open && this.props.shipment?.id) {
void this.handleRefresh();
}
}
componentDidUpdate(prevProps) {
if (!prevProps.open && this.props.open) {
this.setState({ shipment: this.props.shipment, error: null });
if (this.props.shipment?.id) {
void this.handleRefresh();
}
} else if (prevProps.shipment?.id !== this.props.shipment?.id && this.props.shipment?.id) {
this.setState({ shipment: this.props.shipment });
void this.handleRefresh();
}
}
// MARK: calls shipmentApi.get from ./api/index.js
// #region GET /api/shipment/:id — refresh details
async handleRefresh() {
const shipmentId = this.props.shipment?.id;
if (!shipmentId) return;
this.setState({ loading: true, error: null });
try {
const res = await shipmentApi.get(shipmentId);
const enriched = res?.shipment || res;
this.setState({ shipment: enriched, loading: false });
} catch (err) {
notifyError(err);
this.setState({ error: err?.message || String(err), loading: false });
}
}
// #endregion
async handleDeletePackage(pkg) {
if (!window.confirm(`Paket „#${pkg.number || pkg.id}“ wirklich löschen?`)) return;
this.setState({ busyAction: true });
try {
await shippingApi.delete(pkg.id);
enqueueSnackbar('Paket gelöscht.', { variant: 'success' });
await Promise.all([
orderAutomat.actions.reload(),
shipmentAutomat.actions.reload(),
]);
this.props.onChanged?.();
await this.handleRefresh();
} catch (err) {
notifyError(err);
} finally {
this.setState({ busyAction: false });
}
}
async handleMarkPackageShipped(pkg) {
this.setState({ busyAction: true });
try {
await shippingApi.update(pkg.id, { state: 'shipped' });
enqueueSnackbar('Paket als versendet markiert (Lagerbestand abgebucht).', { variant: 'success' });
await Promise.all([
orderAutomat.actions.reload(),
shipmentAutomat.actions.reload(),
]);
this.props.onChanged?.();
await this.handleRefresh();
} catch (err) {
notifyError(err);
} finally {
this.setState({ busyAction: false });
}
}
async handleStateChange(newState) {
const shipmentId = this.state.shipment?.id;
if (!shipmentId) return;
this.setState({ busyAction: true });
try {
await shipmentApi.update(shipmentId, { state: newState });
this.setState((prev) => ({
shipment: { ...prev.shipment, state: newState },
}));
const label = newState === 'delivered' ? 'Versendet' : newState === 'returned' ? 'Retoure' : 'Neu';
enqueueSnackbar(`Lieferstatus geändert: ${label}`, { variant: 'success' });
await Promise.all([
orderAutomat.actions.reload(),
shipmentAutomat.actions.reload(),
]);
this.props.onChanged?.();
} catch (err) {
notifyError(err);
} finally {
this.setState({ busyAction: false });
}
}
render() {
const { open, onClose } = this.props;
const { shipment, loading, error, addPackageDialogOpen, busyAction } = this.state;
if (!shipment) return null;
const shLines = Array.isArray(shipment.lines) ? shipment.lines : [];
const shPackages = Array.isArray(shipment.shippings) ? shipment.shippings : [];
return (
{shipment.number || 'Lieferschein'}
{shipment.date ? `Datum: ${formatDate(shipment.date)}` : ''}
{shipment.externalNumber ? ` • Ref: ${shipment.externalNumber}` : ''}
theme.palette.grey[500] }}
>
{error && (
this.setState({ error: null })}>
{error}
)}
{/* Shipped Article Positions */}
SKU
Bezeichnung
Menge
{shLines.length === 0 ? (
{loading ? 'Lädt Positionen…' : 'Keine Positionen auf diesem Lieferschein.'}
) : (
shLines.map((line, idx) => (
{line.sku}
{line.name || '–'}
{line.amount}
))
)}
{/* Shipping Packages / Tracking */}
0 ? 1 : 0 }}>
}
onClick={() => this.setState({ addPackageDialogOpen: true })}
disabled={busyAction}
sx={{ textTransform: 'none', fontWeight: 600, height: 28, fontSize: '0.75rem' }}
>
Paket hinzufügen
{shPackages.length > 0 ? (
{shPackages.map((pkg) => {
const isShipped = pkg.state === 'shipped';
return (
{pkg.carrier || 'DHL'} ({pkg.type === 'pallet' ? 'Palette' : 'Paket'})
{pkg.trackingCode ? (
{pkg.trackingCode}
{
navigator.clipboard.writeText(pkg.trackingCode);
enqueueSnackbar('Tracking-Code kopiert!', { variant: 'info' });
}}
sx={{ p: 0.25 }}
>
) : (
Ohne Tracking
)}
{isShipped ? (
}
label="Versendet (Lager abgebucht)"
sx={{ fontWeight: 600 }}
/>
) : (
<>
this.handleDeletePackage(pkg)}
>
>
)}
);
})}
) : null}
{(!shipment.state || shipment.state === 'new') && (
)}
{shipment.state === 'delivered' && (
)}
{shipment.state === 'returned' && (
)}
{addPackageDialogOpen && (
this.setState({ addPackageDialogOpen: false })}
onSuccess={async () => {
this.props.onChanged?.();
await this.handleRefresh();
}}
/>
)}
);
}
}