u
This commit is contained in:
421
src/components/manifest/DeliveryList.jsx
Normal file
421
src/components/manifest/DeliveryList.jsx
Normal file
@@ -0,0 +1,421 @@
|
||||
import { PureComponent } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
||||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { RecordList } from 'dashgui/lists';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { delivery } from '../../api';
|
||||
import { supplierAutomat } from '../../automats';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import BookDeliveryDialog from './BookDeliveryDialog';
|
||||
import withNavigate from '../withNavigate';
|
||||
import { formatDate, numberDisplay } from './manifestConfig';
|
||||
|
||||
function sortDeliveries(docs) {
|
||||
if (!Array.isArray(docs)) return [];
|
||||
return [...docs].sort((a, b) => {
|
||||
const cmp = String(b.date ?? '').localeCompare(String(a.date ?? ''));
|
||||
if (cmp !== 0) return cmp;
|
||||
return Number(b.id ?? 0) - Number(a.id ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes == null || Number.isNaN(bytes)) return '';
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
export class DeliveryList extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
status: 'loading',
|
||||
error: null,
|
||||
docs: [],
|
||||
suppliers: supplierAutomat.state?.suppliers || [],
|
||||
busyId: null,
|
||||
bookingDoc: null,
|
||||
bookingOpen: false,
|
||||
};
|
||||
this._cancelled = false;
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handleCreate = this.handleCreate.bind(this);
|
||||
this.handleEdit = this.handleEdit.bind(this);
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
this.handleOpenBookDialog = this.handleOpenBookDialog.bind(this);
|
||||
this.handleCloseBookDialog = this.handleCloseBookDialog.bind(this);
|
||||
this.handleBookSuccess = this.handleBookSuccess.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsubSupplier = supplierAutomat.subscribe(this, (state) => ({
|
||||
suppliers: state.suppliers || [],
|
||||
}));
|
||||
|
||||
void this.load();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsubSupplier?.();
|
||||
}
|
||||
|
||||
async fetchDeliveries() {
|
||||
if (this.context?.deliveries) {
|
||||
return this.context.deliveries;
|
||||
}
|
||||
if (this.context?.deliveryPromise) {
|
||||
return await this.context.deliveryPromise;
|
||||
}
|
||||
const res = await (this.context?.delivery?.list ? this.context.delivery.list() : delivery.list());
|
||||
return res?.deliveries ?? res ?? [];
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const docs = await this.fetchDeliveries();
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
docs: sortDeliveries(docs),
|
||||
});
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
this.setState({ status: 'failed', error: backendErrorFrom(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async handleRetry() {
|
||||
if (this.context?.updateDelivery) {
|
||||
try {
|
||||
await this.context.updateDelivery();
|
||||
} catch {
|
||||
// Will be caught in load()
|
||||
}
|
||||
}
|
||||
void this.load();
|
||||
}
|
||||
|
||||
go(to) {
|
||||
const { navigate } = this.props;
|
||||
if (typeof navigate === 'function') {
|
||||
navigate(to);
|
||||
return;
|
||||
}
|
||||
window.location.assign(to);
|
||||
}
|
||||
|
||||
handleCreate() {
|
||||
this.go('/lieferung/neu');
|
||||
}
|
||||
|
||||
handleEdit(doc) {
|
||||
this.go(`/lieferung/${doc.id}`);
|
||||
}
|
||||
|
||||
handleOpenBookDialog(doc) {
|
||||
this.setState({ bookingDoc: doc, bookingOpen: true });
|
||||
}
|
||||
|
||||
handleCloseBookDialog() {
|
||||
this.setState({ bookingDoc: null, bookingOpen: false });
|
||||
}
|
||||
|
||||
async handleBookSuccess() {
|
||||
this.handleCloseBookDialog();
|
||||
if (this.context?.updateDelivery) {
|
||||
try {
|
||||
const updated = await this.context.updateDelivery();
|
||||
if (this._cancelled) return;
|
||||
this.setState({ docs: sortDeliveries(updated) });
|
||||
return;
|
||||
} catch {
|
||||
// Fall back to load()
|
||||
}
|
||||
}
|
||||
void this.load();
|
||||
}
|
||||
|
||||
async handleDelete(doc) {
|
||||
if (this.state.busyId != null) return;
|
||||
if (!window.confirm(`Lieferung „${doc.number}“ wirklich löschen?`)) return;
|
||||
|
||||
this.setState({ busyId: doc.id });
|
||||
try {
|
||||
await (this.context?.delivery?.delete ? this.context.delivery.delete(doc.id) : delivery.delete(doc.id));
|
||||
if (this.context?.updateDelivery) {
|
||||
await this.context.updateDelivery();
|
||||
}
|
||||
if (this._cancelled) return;
|
||||
await this.load();
|
||||
enqueueSnackbar('Lieferung gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
notifyError(err);
|
||||
this.setState({ busyId: null });
|
||||
}
|
||||
}
|
||||
|
||||
getSupplierName(supplierId) {
|
||||
if (supplierId == null) return null;
|
||||
const supplier = this.state.suppliers?.find((s) => s.id === supplierId);
|
||||
return supplier?.name || `Lieferant #${supplierId}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, error, docs, busyId, bookingDoc, bookingOpen, suppliers } = this.state;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'number',
|
||||
label: 'Nummer',
|
||||
render: (row) => (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontWeight: 600, color: 'text.primary', fontFamily: 'monospace' }}
|
||||
>
|
||||
{numberDisplay(row.number)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: 'Erstellt',
|
||||
render: (row) => (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{row.createdAt ? formatDate(row.createdAt) : '–'}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
label: 'Lieferung',
|
||||
render: (row) => (
|
||||
<Typography variant="body2" color="text.primary">
|
||||
{row.date ? formatDate(row.date) : '–'}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'totalPositions',
|
||||
label: 'Positionen',
|
||||
render: (row) => {
|
||||
const total = row.ledger?.totalPositions ?? row.ledger?.total ?? 0;
|
||||
return (
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
{total}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'unbooked',
|
||||
label: 'Ungebucht',
|
||||
render: (row) => {
|
||||
const total = row.ledger?.totalPositions ?? row.ledger?.total ?? 0;
|
||||
const completely = row.ledger?.completelyOnLedgerPositions ?? row.ledger?.booked ?? 0;
|
||||
const unbooked = Math.max(0, total - completely);
|
||||
|
||||
if (total === 0) {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
0
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (unbooked === 0) {
|
||||
return (
|
||||
<Tooltip title={`Alle ${total} Positionen vollständig ins Lager gebucht`}>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<CheckCircleOutlineOutlinedIcon sx={{ '&&': { fontSize: 16 } }} />}
|
||||
label="0"
|
||||
color="success"
|
||||
variant="outlined"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.handleOpenBookDialog(row);
|
||||
}}
|
||||
sx={{ fontWeight: 600, cursor: 'pointer' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'inline-flex', alignItems: 'center', gap: 1 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Tooltip title={`${unbooked} von ${total} Positionen noch ungebucht`}>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<Inventory2OutlinedIcon sx={{ '&&': { fontSize: 16 } }} />}
|
||||
label={String(unbooked)}
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{ fontWeight: 600, cursor: 'pointer' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="warning"
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{
|
||||
py: 0.2,
|
||||
px: 1,
|
||||
minHeight: 24,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Buchen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'supplierId',
|
||||
label: 'Lieferant',
|
||||
render: (row) => {
|
||||
const supplier = suppliers?.find((s) => s.id === row.supplierId);
|
||||
const name = supplier?.name || (row.supplierId != null ? `Lieferant #${row.supplierId}` : '–');
|
||||
return (
|
||||
<Typography variant="body2" color={supplier ? 'text.primary' : 'text.secondary'}>
|
||||
{name}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'externalNumber',
|
||||
label: 'Referenz',
|
||||
render: (row) => (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{numberDisplay(row.externalNumber)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'files',
|
||||
label: 'Attachments',
|
||||
render: (row) => {
|
||||
const files = Array.isArray(row.files) ? row.files : [];
|
||||
if (files.length === 0) {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
–
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{files.map((file, idx) => {
|
||||
const docId = file.manifestId ?? row.id;
|
||||
const fileId = file.fileId ?? file.id;
|
||||
const isPdf =
|
||||
file.mimetype === 'application/pdf' ||
|
||||
file.name?.toLowerCase().endsWith('.pdf');
|
||||
const isTxt =
|
||||
file.mimetype?.startsWith('text/') ||
|
||||
file.name?.toLowerCase().endsWith('.txt') ||
|
||||
file.name?.toLowerCase().endsWith('.csv');
|
||||
|
||||
const icon = isPdf ? (
|
||||
<PictureAsPdfIcon sx={{ fontSize: 18, color: 'error.main' }} />
|
||||
) : isTxt ? (
|
||||
<TextSnippetOutlinedIcon sx={{ fontSize: 18, color: 'primary.main' }} />
|
||||
) : (
|
||||
<DescriptionOutlinedIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
|
||||
);
|
||||
|
||||
const title = `${file.name}${file.size ? ` (${formatFileSize(file.size)})` : ''}`;
|
||||
const fileUrl = `/api/delivery/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`;
|
||||
|
||||
return (
|
||||
<Tooltip key={file.id ?? fileId ?? idx} title={title}>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={title}
|
||||
onClick={() => window.open(fileUrl, '_blank')}
|
||||
sx={{
|
||||
p: 0.5,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 1,
|
||||
'&:hover': {
|
||||
bgcolor: 'action.hover',
|
||||
borderColor: 'primary.main',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordList
|
||||
status={status}
|
||||
error={<BackendError error={error} onRetry={this.handleRetry} />}
|
||||
rows={docs}
|
||||
columns={columns}
|
||||
createLabel="Neue Lieferung"
|
||||
onCreate={this.handleCreate}
|
||||
onEdit={this.handleEdit}
|
||||
onDelete={this.handleDelete}
|
||||
deleteDisabled={busyId != null}
|
||||
emptyMessage="Noch keine Lieferungen."
|
||||
color="#fed7aa"
|
||||
getEditLabel={(row) => `„${row.number}“ bearbeiten`}
|
||||
getDeleteLabel={(row) => `„${row.number}“ löschen`}
|
||||
/>
|
||||
|
||||
<BookDeliveryDialog
|
||||
open={bookingOpen}
|
||||
delivery={bookingDoc}
|
||||
summary={null}
|
||||
partyName={bookingDoc ? this.getSupplierName(bookingDoc.supplierId) : null}
|
||||
onClose={this.handleCloseBookDialog}
|
||||
onBookSuccess={this.handleBookSuccess}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withNavigate(DeliveryList);
|
||||
@@ -1,22 +1,12 @@
|
||||
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 Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { RecordList } from 'dashgui/lists';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { ledger, delivery } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { fetchDeliveryLedgerSummary } from '../../utils/deliveryLedger';
|
||||
import BookDeliveryDialog from './BookDeliveryDialog';
|
||||
import withNavigate from '../withNavigate';
|
||||
import DeliveryList from './DeliveryList';
|
||||
import {
|
||||
formatDate,
|
||||
loadParties,
|
||||
@@ -38,23 +28,19 @@ class ManifestList extends PureComponent {
|
||||
docs: [],
|
||||
partiesById: {},
|
||||
busyId: null,
|
||||
ledgerSummaries: {},
|
||||
bookingDoc: null,
|
||||
bookingOpen: false,
|
||||
};
|
||||
this._cancelled = false;
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handleCreate = this.handleCreate.bind(this);
|
||||
this.handleEdit = this.handleEdit.bind(this);
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
this.handleOpenBookDialog = this.handleOpenBookDialog.bind(this);
|
||||
this.handleCloseBookDialog = this.handleCloseBookDialog.bind(this);
|
||||
this.handleBookSuccess = this.handleBookSuccess.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
void this.load();
|
||||
if (this.props.kind !== 'delivery') {
|
||||
void this.load();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -76,58 +62,16 @@ class ManifestList extends PureComponent {
|
||||
docs: sortedDocs,
|
||||
partiesById: cfg.partiesByIdFromList(parties),
|
||||
});
|
||||
|
||||
if (this.props.kind === 'delivery') {
|
||||
void this.loadLedgerSummaries(sortedDocs);
|
||||
}
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
this.setState({ status: 'failed', error: backendErrorFrom(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async loadLedgerSummaries(docs) {
|
||||
if (!Array.isArray(docs) || docs.length === 0) return;
|
||||
const ledgerApi = this.context?.ledger || ledger;
|
||||
const deliveryApi = this.context?.delivery || delivery;
|
||||
|
||||
for (const doc of docs) {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => ({
|
||||
ledgerSummaries: {
|
||||
...prev.ledgerSummaries,
|
||||
[doc.id]: prev.ledgerSummaries[doc.id] || { status: 'loading' },
|
||||
},
|
||||
}));
|
||||
|
||||
fetchDeliveryLedgerSummary(doc.id, { ledgerApi, deliveryApi })
|
||||
.then((summary) => {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => ({
|
||||
ledgerSummaries: {
|
||||
...prev.ledgerSummaries,
|
||||
[doc.id]: summary,
|
||||
},
|
||||
}));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (this._cancelled) return;
|
||||
console.error(`[ManifestList] Ledger summary failed for delivery ${doc.id}:`, err);
|
||||
this.setState((prev) => ({
|
||||
ledgerSummaries: {
|
||||
...prev.ledgerSummaries,
|
||||
[doc.id]: { status: 'error', error: err.message },
|
||||
},
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleRetry() {
|
||||
void this.load();
|
||||
}
|
||||
|
||||
/** Same path as AppShell sub-nav: live useNavigate via withNavigate. */
|
||||
go(to) {
|
||||
const { navigate } = this.props;
|
||||
if (typeof navigate === 'function') {
|
||||
@@ -145,24 +89,6 @@ class ManifestList extends PureComponent {
|
||||
this.go(this.cfg.editPath(doc.id));
|
||||
}
|
||||
|
||||
handleOpenBookDialog(doc) {
|
||||
this.setState({ bookingDoc: doc, bookingOpen: true });
|
||||
}
|
||||
|
||||
handleCloseBookDialog() {
|
||||
this.setState({ bookingDoc: null, bookingOpen: false });
|
||||
}
|
||||
|
||||
handleBookSuccess(updatedSummary) {
|
||||
if (!updatedSummary?.deliveryId) return;
|
||||
this.setState((prev) => ({
|
||||
ledgerSummaries: {
|
||||
...prev.ledgerSummaries,
|
||||
[updatedSummary.deliveryId]: updatedSummary,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
async handleDelete(doc) {
|
||||
if (this.state.busyId != null) return;
|
||||
if (!window.confirm(this.cfg.confirmDelete(doc.number))) return;
|
||||
@@ -173,14 +99,9 @@ class ManifestList extends PureComponent {
|
||||
await this.context[cfg.api].delete(doc.id);
|
||||
const list = await this.context[cfg.updateCache]();
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => {
|
||||
const nextSummaries = { ...prev.ledgerSummaries };
|
||||
delete nextSummaries[doc.id];
|
||||
return {
|
||||
docs: sortDocs(list),
|
||||
busyId: null,
|
||||
ledgerSummaries: nextSummaries,
|
||||
};
|
||||
this.setState({
|
||||
docs: sortDocs(list),
|
||||
busyId: null,
|
||||
});
|
||||
enqueueSnackbar(cfg.deletedMsg, { variant: 'success' });
|
||||
} catch (err) {
|
||||
@@ -198,141 +119,12 @@ class ManifestList extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
renderLedgerSummary(row) {
|
||||
const summary = this.state.ledgerSummaries[row.id];
|
||||
|
||||
if (!summary || summary.status === 'loading') {
|
||||
return (
|
||||
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<CircularProgress size={14} color="inherit" sx={{ opacity: 0.6 }} />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Prüfe...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (summary.status === 'error') {
|
||||
return (
|
||||
<Tooltip title={summary.error || 'Fehler beim Laden des Lagerstatus'}>
|
||||
<Chip size="small" label="Statusfehler" color="error" variant="outlined" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (summary.status === 'empty') {
|
||||
return (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Keine Positionen
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (summary.status === 'booked') {
|
||||
return (
|
||||
<Tooltip
|
||||
title={`${summary.totalPositions} Positionen (${summary.bookedQuantity} Stk.) vollständig ins Lager gebucht`}
|
||||
>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<CheckCircleOutlineOutlinedIcon sx={{ '&&': { fontSize: 16 } }} />}
|
||||
label={`Gebucht (${summary.bookedPositions}/${summary.totalPositions})`}
|
||||
color="success"
|
||||
variant="outlined"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.handleOpenBookDialog(row);
|
||||
}}
|
||||
sx={{ fontWeight: 600, cursor: 'pointer' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (summary.status === 'partial') {
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'inline-flex', alignItems: 'center', gap: 1 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Tooltip
|
||||
title={`${summary.bookedPositions} von ${summary.totalPositions} Positionen gebucht (${summary.bookedQuantity} / ${summary.totalQuantity} Stk.)`}
|
||||
>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<Inventory2OutlinedIcon sx={{ '&&': { fontSize: 16 } }} />}
|
||||
label={`Teilweise (${summary.bookedPositions}/${summary.totalPositions})`}
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{ fontWeight: 600, cursor: 'pointer' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="warning"
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{
|
||||
py: 0.2,
|
||||
px: 1,
|
||||
minHeight: 26,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Rest buchen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: 'unbooked'
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'inline-flex', alignItems: 'center', gap: 1 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Tooltip
|
||||
title={`Noch nicht ins Lager gebucht (${summary.totalPositions} Positionen, ${summary.totalQuantity} Stk.)`}
|
||||
>
|
||||
<Chip
|
||||
size="small"
|
||||
label={`Offen (${summary.totalPositions} Pos.)`}
|
||||
variant="outlined"
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{
|
||||
color: 'text.secondary',
|
||||
borderColor: 'divider',
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<Inventory2OutlinedIcon sx={{ fontSize: 16 }} />}
|
||||
onClick={() => this.handleOpenBookDialog(row)}
|
||||
sx={{
|
||||
py: 0.2,
|
||||
px: 1.2,
|
||||
minHeight: 26,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Ins Lager buchen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, error, docs, busyId, bookingDoc, bookingOpen, ledgerSummaries } = this.state;
|
||||
if (this.props.kind === 'delivery') {
|
||||
return <DeliveryList {...this.props} />;
|
||||
}
|
||||
|
||||
const { status, error, docs, busyId } = this.state;
|
||||
const cfg = this.cfg;
|
||||
const partyField = cfg.partyField;
|
||||
|
||||
@@ -357,43 +149,22 @@ class ManifestList extends PureComponent {
|
||||
},
|
||||
);
|
||||
|
||||
if (this.props.kind === 'delivery') {
|
||||
columns.push({
|
||||
key: 'ledgerStatus',
|
||||
label: 'Lagerbuchung',
|
||||
render: (row) => this.renderLedgerSummary(row),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordList
|
||||
status={status}
|
||||
error={<BackendError error={error} onRetry={this.handleRetry} />}
|
||||
rows={docs}
|
||||
columns={columns}
|
||||
createLabel={cfg.createButton}
|
||||
onCreate={this.handleCreate}
|
||||
onEdit={this.handleEdit}
|
||||
onDelete={this.handleDelete}
|
||||
deleteDisabled={busyId != null}
|
||||
emptyMessage={cfg.emptyList}
|
||||
color={cfg.listColor}
|
||||
getEditLabel={(row) => `„${row.number}“ bearbeiten`}
|
||||
getDeleteLabel={(row) => `„${row.number}“ löschen`}
|
||||
/>
|
||||
|
||||
{this.props.kind === 'delivery' && (
|
||||
<BookDeliveryDialog
|
||||
open={bookingOpen}
|
||||
delivery={bookingDoc}
|
||||
summary={bookingDoc ? ledgerSummaries[bookingDoc.id] : null}
|
||||
partyName={bookingDoc ? this.partyName(bookingDoc[partyField]) : null}
|
||||
onClose={this.handleCloseBookDialog}
|
||||
onBookSuccess={this.handleBookSuccess}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<RecordList
|
||||
status={status}
|
||||
error={<BackendError error={error} onRetry={this.handleRetry} />}
|
||||
rows={docs}
|
||||
columns={columns}
|
||||
createLabel={cfg.createButton}
|
||||
onCreate={this.handleCreate}
|
||||
onEdit={this.handleEdit}
|
||||
onDelete={this.handleDelete}
|
||||
deleteDisabled={busyId != null}
|
||||
emptyMessage={cfg.emptyList}
|
||||
color={cfg.listColor}
|
||||
getEditLabel={(row) => `„${row.number}“ bearbeiten`}
|
||||
getDeleteLabel={(row) => `„${row.number}“ löschen`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as ManifestList } from './ManifestList';
|
||||
export { default as DeliveryList } from './DeliveryList';
|
||||
export { default as ManifestForm } from './ManifestForm';
|
||||
export { default as ManifestHeader } from './ManifestHeader';
|
||||
export { default as ManifestLines } from './ManifestLines';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { ManifestList } from '../../components/manifest';
|
||||
import { DeliveryList } from '../../components/manifest';
|
||||
import { setLastDeliveryPath } from '../../automats';
|
||||
|
||||
class LieferungListePage extends PureComponent {
|
||||
@@ -8,7 +8,7 @@ class LieferungListePage extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ManifestList kind="delivery" />;
|
||||
return <DeliveryList />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user