u
This commit is contained in:
@@ -32,6 +32,7 @@ import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
import { getMaxDocumentNumber } from '../../utils/documentNumber';
|
||||
import PartyFilterPanel, { PartyFilterButton, PartyFilterChip } from './PartyFilterPanel';
|
||||
import withNavigate from '../withNavigate';
|
||||
import {
|
||||
formatDate,
|
||||
@@ -333,7 +334,14 @@ class OrderSearchField extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputRef, onCreate } = this.props;
|
||||
const {
|
||||
inputRef,
|
||||
onCreate,
|
||||
customerFilterOpen,
|
||||
onToggleCustomerFilter,
|
||||
selectedCustomer,
|
||||
onClearCustomerFilter,
|
||||
} = this.props;
|
||||
const { input, onlyUnpaid, onlyUndelivered, seekMonths, seekYears } = this.state;
|
||||
const monthActive = seekMonths > 0;
|
||||
const monthLabel = !monthActive ? '-1 Month' : `-${seekMonths} Month${seekMonths > 1 ? 's' : ''}`;
|
||||
@@ -352,22 +360,34 @@ class OrderSearchField extends PureComponent {
|
||||
pb: 2,
|
||||
}}
|
||||
>
|
||||
{/* Top row: search field & create button */}
|
||||
{/* Top row: search field, customer filter button, active chip & create button */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<SingleLineTextEdit
|
||||
sx={{ flex: 1 }}
|
||||
sx={{ flex: 1, minWidth: { xs: '100%', sm: 220 } }}
|
||||
size="small"
|
||||
search
|
||||
clear
|
||||
value={input}
|
||||
onChange={(next) => orderAutomat.actions.filter(next)}
|
||||
/>
|
||||
<PartyFilterButton
|
||||
title="Kunden"
|
||||
open={customerFilterOpen}
|
||||
onClick={onToggleCustomerFilter}
|
||||
/>
|
||||
{selectedCustomer && (
|
||||
<PartyFilterChip
|
||||
label={partyDisplayName('order', selectedCustomer)}
|
||||
onDelete={onClearCustomerFilter}
|
||||
/>
|
||||
)}
|
||||
<Button variant="contained" onClick={onCreate}>
|
||||
{cfg.createButton}
|
||||
</Button>
|
||||
@@ -548,7 +568,10 @@ export class OrderList extends PureComponent {
|
||||
items: orderAutomat.getItems(),
|
||||
unfilteredTotal: orderAutomat.state.unfilteredTotal ?? 0,
|
||||
filterKey: orderAutomat.getFilterKey?.() ?? '',
|
||||
entityId: orderAutomat.state.entityId ?? null,
|
||||
customerFilterOpen: false,
|
||||
partiesById: {},
|
||||
partiesList: [],
|
||||
busyId: null,
|
||||
};
|
||||
this.searchRef = createRef();
|
||||
@@ -560,6 +583,8 @@ export class OrderList extends PureComponent {
|
||||
this.handleTogglePaid = this.handleTogglePaid.bind(this);
|
||||
this.handleToggleDelivered = this.handleToggleDelivered.bind(this);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleToggleCustomerFilter = this.handleToggleCustomerFilter.bind(this);
|
||||
this.handleSelectCustomer = this.handleSelectCustomer.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -568,6 +593,7 @@ export class OrderList extends PureComponent {
|
||||
items: state.items,
|
||||
unfilteredTotal: state.unfilteredTotal ?? 0,
|
||||
filterKey: orderAutomat.getFilterKey(state),
|
||||
entityId: state.entityId ?? null,
|
||||
}));
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestOrder();
|
||||
@@ -583,11 +609,22 @@ export class OrderList extends PureComponent {
|
||||
this.unsubNumber?.();
|
||||
}
|
||||
|
||||
handleToggleCustomerFilter() {
|
||||
this.setState((prev) => ({ customerFilterOpen: !prev.customerFilterOpen }));
|
||||
}
|
||||
|
||||
handleSelectCustomer(entityId) {
|
||||
orderAutomat.actions.setEntityId(entityId);
|
||||
}
|
||||
|
||||
async loadParties() {
|
||||
try {
|
||||
const parties = await loadParties('order');
|
||||
if (!this._cancelled) {
|
||||
this.setState({ partiesById: cfg.partiesByIdFromList(parties) });
|
||||
this.setState({
|
||||
partiesById: cfg.partiesByIdFromList(parties),
|
||||
partiesList: parties || [],
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: names fall back to ids.
|
||||
@@ -663,7 +700,16 @@ export class OrderList extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, unfilteredTotal, filterKey, partiesById, busyId } = this.state;
|
||||
const {
|
||||
items,
|
||||
unfilteredTotal,
|
||||
filterKey,
|
||||
partiesById,
|
||||
partiesList,
|
||||
entityId,
|
||||
customerFilterOpen,
|
||||
busyId,
|
||||
} = this.state;
|
||||
|
||||
rowContext.partiesById = partiesById;
|
||||
rowContext.busyId = busyId;
|
||||
@@ -673,10 +719,31 @@ export class OrderList extends PureComponent {
|
||||
rowContext.onToggleDelivered = this.handleToggleDelivered;
|
||||
rowContext.headerItem = this.headerItem;
|
||||
|
||||
const customerCounts = {};
|
||||
if (Array.isArray(items)) {
|
||||
for (const ord of items) {
|
||||
if (ord?.entityId) {
|
||||
customerCounts[ord.entityId] = (customerCounts[ord.entityId] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const selectedCustomer = entityId ? (partiesById[entityId] || { id: entityId, name: `Kunde #${entityId}` }) : null;
|
||||
|
||||
const searchField = (
|
||||
<OrderSearchField
|
||||
inputRef={this.searchRef}
|
||||
onCreate={this.handleCreate}
|
||||
customerFilterOpen={customerFilterOpen}
|
||||
onToggleCustomerFilter={this.handleToggleCustomerFilter}
|
||||
selectedCustomer={selectedCustomer}
|
||||
onClearCustomerFilter={() => this.handleSelectCustomer(null)}
|
||||
/>
|
||||
);
|
||||
|
||||
if (items === null && orderAutomat.state.status === 'failed') {
|
||||
return (
|
||||
<>
|
||||
<OrderSearchField inputRef={this.searchRef} onCreate={this.handleCreate} />
|
||||
{searchField}
|
||||
<PanelTileGrid>
|
||||
<PanelTile wide={3} minHeight={0} color={cfg.listColor} sx={{ p: 2 }}>
|
||||
<BackendError error={backendErrorFrom(orderAutomat.state.error)} onRetry={this.handleRetry} />
|
||||
@@ -688,73 +755,88 @@ export class OrderList extends PureComponent {
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrderSearchField inputRef={this.searchRef} onCreate={this.handleCreate} />
|
||||
<PanelTileGrid>
|
||||
<PanelTile wide={3} minHeight={0} color={cfg.listColor} sx={{ pb: 0, position: 'relative' }}>
|
||||
<FillSpace
|
||||
aboveRef={[this.searchRef]}
|
||||
reserveBottom={32}
|
||||
minPageSize={8}
|
||||
minHeight={0}
|
||||
resetKey={filterKey}
|
||||
items={items || []}
|
||||
totalCount={items ? items.length : 0}
|
||||
unfilteredTotal={unfilteredTotal}
|
||||
headerItem={this.headerItem}
|
||||
renderItem={OrderRow}
|
||||
virtualScrollbar
|
||||
header={
|
||||
<Box
|
||||
sx={{
|
||||
...rowGridSx,
|
||||
height: 36,
|
||||
minHeight: 36,
|
||||
px: 2,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
{renderInvisibleDummyRow(this.headerItem)}
|
||||
<Box sx={{ gridColumn: 1, gridRow: 2, ...headerCellSx }}>Nr. / Ref.</Box>
|
||||
<Box sx={{ gridColumn: 2, gridRow: 2, ...headerCellSx }}>Datum</Box>
|
||||
<Box sx={{ gridColumn: 3, gridRow: 2, ...headerCellSx }}>Kunde</Box>
|
||||
<Box sx={{ gridColumn: 4, gridRow: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', ...headerCellSx }}>
|
||||
<Tooltip title="Bezahlt">
|
||||
<PaymentsOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ gridColumn: 5, gridRow: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', ...headerCellSx }}>
|
||||
<Tooltip title="Geliefert">
|
||||
<LocalShippingOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ gridColumn: 6, gridRow: 2, display: 'flex', justifyContent: 'center', textAlign: 'center', ...headerCellSx }}>Pos.</Box>
|
||||
<Box sx={{ gridColumn: 7, gridRow: 2, textAlign: 'right', ...headerCellSx }}>Gesamt</Box>
|
||||
<Box sx={{ gridColumn: 8, gridRow: 2, ...headerCellSx }} />
|
||||
</Box>
|
||||
}
|
||||
onPageChange={this.handlePageChange}
|
||||
{searchField}
|
||||
<Box sx={{ display: 'flex', gap: 2.5, alignItems: 'stretch' }}>
|
||||
{customerFilterOpen && (
|
||||
<PartyFilterPanel
|
||||
title="Kunden"
|
||||
parties={partiesList}
|
||||
selectedId={entityId}
|
||||
onSelect={this.handleSelectCustomer}
|
||||
onClose={this.handleToggleCustomerFilter}
|
||||
counts={customerCounts}
|
||||
searchPlaceholder="Kunde suchen…"
|
||||
/>
|
||||
{items === null && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 52,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
)}
|
||||
</PanelTile>
|
||||
</PanelTileGrid>
|
||||
)}
|
||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||
<PanelTileGrid>
|
||||
<PanelTile wide={3} minHeight={0} color={cfg.listColor} sx={{ pb: 0, position: 'relative' }}>
|
||||
<FillSpace
|
||||
aboveRef={[this.searchRef]}
|
||||
reserveBottom={32}
|
||||
minPageSize={8}
|
||||
minHeight={0}
|
||||
resetKey={filterKey}
|
||||
items={items || []}
|
||||
totalCount={items ? items.length : 0}
|
||||
unfilteredTotal={unfilteredTotal}
|
||||
headerItem={this.headerItem}
|
||||
renderItem={OrderRow}
|
||||
virtualScrollbar
|
||||
header={
|
||||
<Box
|
||||
sx={{
|
||||
...rowGridSx,
|
||||
height: 36,
|
||||
minHeight: 36,
|
||||
px: 2,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
{renderInvisibleDummyRow(this.headerItem)}
|
||||
<Box sx={{ gridColumn: 1, gridRow: 2, ...headerCellSx }}>Nr. / Ref.</Box>
|
||||
<Box sx={{ gridColumn: 2, gridRow: 2, ...headerCellSx }}>Datum</Box>
|
||||
<Box sx={{ gridColumn: 3, gridRow: 2, ...headerCellSx }}>Kunde</Box>
|
||||
<Box sx={{ gridColumn: 4, gridRow: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', ...headerCellSx }}>
|
||||
<Tooltip title="Bezahlt">
|
||||
<PaymentsOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ gridColumn: 5, gridRow: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', ...headerCellSx }}>
|
||||
<Tooltip title="Geliefert">
|
||||
<LocalShippingOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ gridColumn: 6, gridRow: 2, display: 'flex', justifyContent: 'center', textAlign: 'center', ...headerCellSx }}>Pos.</Box>
|
||||
<Box sx={{ gridColumn: 7, gridRow: 2, textAlign: 'right', ...headerCellSx }}>Gesamt</Box>
|
||||
<Box sx={{ gridColumn: 8, gridRow: 2, ...headerCellSx }} />
|
||||
</Box>
|
||||
}
|
||||
onPageChange={this.handlePageChange}
|
||||
/>
|
||||
{items === null && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 52,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
)}
|
||||
</PanelTile>
|
||||
</PanelTileGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user