This commit is contained in:
seb
2026-10-23 20:31:34 +02:00
parent 57347bdef9
commit b9bd0380cb
7 changed files with 533 additions and 78 deletions

View File

@@ -4,10 +4,19 @@ import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Link from '@mui/material/Link';
import { enqueueSnackbar } from 'notistack';
import { articleHistoryAutomat } from '../../../automats/articleDetailAutomat';
import sseAutomat from '../../../automats/sseAutomat';
import shipmentAutomat from '../../../automats/shipmentAutomat';
import { shipment as shipmentApi } from '../../../api';
import { ShipmentDetailDialog } from '../../manifest';
import withNavigate from '../../withNavigate';
import { notifyError } from '../../../utils/notify';
import ResultsPagination from '../ResultsPagination';
import {
SCRIM_SOFT,
} from '../../../colors';
const PAGE_SIZE = 20;
@@ -15,9 +24,15 @@ function formatDate(iso) {
return new Date(iso).toLocaleString('de-DE');
}
import {
SCRIM_SOFT,
} from '../../../colors';
function decodeMeta(meta) {
if (!meta) return null;
if (typeof meta === 'object') return meta;
try {
return JSON.parse(meta);
} catch {
return null;
}
}
const HISTORY_DATE_CELL_BORDER = `1px solid ${SCRIM_SOFT}`;
@@ -30,11 +45,17 @@ const dateCellSx = {
verticalAlign: 'middle',
};
export default class ArticleHistoryTab extends PureComponent {
class ArticleHistoryTab extends PureComponent {
constructor(props) {
super(props);
this.state = { all: [], skip: 0 };
this.state = {
all: [],
skip: 0,
selectedShipment: null,
loadingShipment: false,
};
this.handlePageChange = this.handlePageChange.bind(this);
this.handleCloseShipment = this.handleCloseShipment.bind(this);
}
componentDidMount() {
@@ -88,8 +109,122 @@ export default class ArticleHistoryTab extends PureComponent {
this.setState({ skip: (page - 1) * PAGE_SIZE });
}
handleCloseShipment() {
this.setState({ selectedShipment: null });
}
handleOpenShipment = async (shipmentNumber, shipmentId = null) => {
if (shipmentId) {
this.setState({ selectedShipment: { id: shipmentId, number: shipmentNumber } });
return;
}
if (!shipmentNumber) return;
this.setState({ loadingShipment: true });
try {
const cached = shipmentAutomat.state.items?.find(
(s) => s && String(s.number).toLowerCase() === String(shipmentNumber).toLowerCase()
);
if (cached?.id) {
this.setState({ selectedShipment: cached, loadingShipment: false });
return;
}
const res = await shipmentApi.list({ search: shipmentNumber });
const shipments = Array.isArray(res?.shipments) ? res.shipments : [];
const match = shipments.find(
(s) => String(s.number).toLowerCase() === String(shipmentNumber).toLowerCase()
) || shipments[0];
if (match?.id) {
this.setState({ selectedShipment: match, loadingShipment: false });
} else {
enqueueSnackbar(`Lieferschein „${shipmentNumber}“ nicht gefunden.`, { variant: 'warning' });
this.setState({ loadingShipment: false });
}
} catch (err) {
this.setState({ loadingShipment: false });
notifyError(err);
}
};
renderEntryText(entry) {
const meta = decodeMeta(entry.meta);
const shipmentNumber = meta?.shipmentNumber || (meta?.type === 'shipment' ? meta?.manifestNumber : null);
const regexMatch = !shipmentNumber && entry.text ? entry.text.match(/Lieferschein\s+([A-Za-z0-9\-_]+)/) : null;
const effectiveShipmentNumber = shipmentNumber || regexMatch?.[1] || null;
if (!effectiveShipmentNumber || !entry.text) {
return entry.text;
}
const shipmentId = meta?.shipmentId ?? meta?.manifestId ?? meta?.id ?? null;
const targetPattern = `Lieferschein ${effectiveShipmentNumber}`;
const idx = entry.text.indexOf(targetPattern);
const linkElement = (label) => (
<Link
component="button"
type="button"
variant="body2"
onClick={() => this.handleOpenShipment(effectiveShipmentNumber, shipmentId)}
sx={{
fontWeight: 600,
verticalAlign: 'baseline',
textAlign: 'left',
cursor: 'pointer',
color: 'primary.main',
textDecoration: 'underline',
fontFamily: 'inherit',
fontSize: 'inherit',
p: 0,
m: 0,
border: 'none',
background: 'none',
display: 'inline',
'&:hover': {
color: 'primary.dark',
},
}}
>
{label}
</Link>
);
if (idx !== -1) {
const before = entry.text.slice(0, idx);
const after = entry.text.slice(idx + targetPattern.length);
return (
<>
{before}
{linkElement(targetPattern)}
{after}
</>
);
}
const numIdx = entry.text.indexOf(effectiveShipmentNumber);
if (numIdx !== -1) {
const before = entry.text.slice(0, numIdx);
const after = entry.text.slice(numIdx + effectiveShipmentNumber.length);
return (
<>
{before}
{linkElement(effectiveShipmentNumber)}
{after}
</>
);
}
return (
<>
{linkElement(`Lieferschein ${effectiveShipmentNumber}`)} {entry.text}
</>
);
}
render() {
const { all, skip } = this.state;
const { all, skip, selectedShipment } = this.state;
const total = all.length;
const history = this.pageOf(all, skip);
@@ -98,54 +233,69 @@ export default class ArticleHistoryTab extends PureComponent {
const showPagination = total > PAGE_SIZE;
return (
<Table
size="small"
sx={{
'& td': { borderBottom: 'none', bgcolor: 'transparent' },
'& th': { bgcolor: 'transparent' },
}}
>
<TableHead>
<TableRow>
<TableCell
sx={{
...dateDividerSx,
width: '1%',
whiteSpace: 'nowrap',
verticalAlign: 'middle',
}}
>
Datum
</TableCell>
<TableCell
sx={dateCellSx}
>
{showPagination ? (
<ResultsPagination
offset={skip}
totalCount={total}
pageSize={PAGE_SIZE}
pageCount={pageCount}
currentPage={currentPage}
onPageChange={this.handlePageChange}
/>
) : null}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{history.map((entry) => (
<TableRow key={entry.id}>
<>
<Table
size="small"
sx={{
'& td': { borderBottom: 'none', bgcolor: 'transparent' },
'& th': { bgcolor: 'transparent' },
}}
>
<TableHead>
<TableRow>
<TableCell
sx={{ verticalAlign: 'top', whiteSpace: 'nowrap', width: '1%' }}
sx={{
...dateDividerSx,
width: '1%',
whiteSpace: 'nowrap',
verticalAlign: 'middle',
}}
>
{formatDate(entry.date)}
Datum
</TableCell>
<TableCell
sx={dateCellSx}
>
{showPagination ? (
<ResultsPagination
offset={skip}
totalCount={total}
pageSize={PAGE_SIZE}
pageCount={pageCount}
currentPage={currentPage}
onPageChange={this.handlePageChange}
/>
) : null}
</TableCell>
<TableCell sx={{ whiteSpace: 'pre-wrap' }}>{entry.text}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableHead>
<TableBody>
{history.map((entry) => (
<TableRow key={entry.id}>
<TableCell
sx={{ verticalAlign: 'top', whiteSpace: 'nowrap', width: '1%' }}
>
{formatDate(entry.date)}
</TableCell>
<TableCell sx={{ whiteSpace: 'pre-wrap' }}>
{this.renderEntryText(entry)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{selectedShipment && (
<ShipmentDetailDialog
open={Boolean(selectedShipment)}
shipment={selectedShipment}
onClose={this.handleCloseShipment}
onNavigate={this.props.navigate}
/>
)}
</>
);
}
}
export default withNavigate(ArticleHistoryTab);