u
This commit is contained in:
@@ -1,8 +1,25 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import BackendError from '../../components/BackendError';
|
||||
import { ArticleView } from '../../components/article';
|
||||
import {
|
||||
artikelAutomat,
|
||||
createArticleSelector,
|
||||
getArticle,
|
||||
setLastArticleSku,
|
||||
} from '../../automats';
|
||||
import withSkuRoute from './withSkuRoute';
|
||||
import { setLastArticleSku } from '../../automats';
|
||||
|
||||
/** Full-screen loading state while the shared article cache resolves. */
|
||||
function ArticleLoading() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
class ArtikelSkuPage extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
@@ -10,43 +27,66 @@ class ArtikelSkuPage extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
ready:
|
||||
context.getArticle(props.sku) != null &&
|
||||
context.getCategoryState() != null,
|
||||
article: getArticle(props.sku),
|
||||
ready: context.getCategoryState() != null,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._mounted = true;
|
||||
this.installEscape();
|
||||
setLastArticleSku(this.props.sku);
|
||||
if (!this.state.ready) void this.ensureArticle();
|
||||
this.unsub = artikelAutomat.subscribe(this, (state) => ({
|
||||
article: createArticleSelector(this.props.sku)(state),
|
||||
}));
|
||||
void this.ensureArticle();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.sku !== this.props.sku) {
|
||||
setLastArticleSku(this.props.sku);
|
||||
const ready =
|
||||
this.context.getArticle(this.props.sku) != null &&
|
||||
this.context.getCategoryState() != null;
|
||||
if (ready !== this.state.ready) this.setState({ ready });
|
||||
if (!ready) void this.ensureArticle();
|
||||
this.setState({ article: getArticle(this.props.sku), error: null });
|
||||
this.unsub?.();
|
||||
this.unsub = artikelAutomat.subscribe(this, (state) => ({
|
||||
article: createArticleSelector(this.props.sku)(state),
|
||||
}));
|
||||
void this.ensureArticle();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
this.removeEscape();
|
||||
this.unsub?.();
|
||||
this.unsub = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates the article fetch to the shared artikelAutomat cache; also waits
|
||||
* for the category tree/company/session the view tiles depend on.
|
||||
*/
|
||||
async ensureArticle() {
|
||||
await Promise.all([
|
||||
this.context.loadArticle(this.props.sku),
|
||||
this.context.categoryTreePromise,
|
||||
this.context.companyPromise,
|
||||
this.context.authMePromise,
|
||||
]);
|
||||
this.setState({ ready: true });
|
||||
const sku = this.props.sku;
|
||||
if (!sku) return;
|
||||
try {
|
||||
await Promise.all([
|
||||
artikelAutomat.actions.loadArticle(sku),
|
||||
this.context.categoryTreePromise,
|
||||
this.context.companyPromise,
|
||||
this.context.authMePromise,
|
||||
]);
|
||||
if (this._mounted) this.setState({ ready: true });
|
||||
} catch (error) {
|
||||
if (this._mounted) this.setState({ error });
|
||||
}
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
this.setState({ error: null });
|
||||
void this.ensureArticle();
|
||||
};
|
||||
|
||||
installEscape() {
|
||||
this._onKeyDown = (e) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
@@ -65,7 +105,12 @@ class ArtikelSkuPage extends Component {
|
||||
|
||||
render() {
|
||||
const { sku } = this.props;
|
||||
if (!this.state.ready) return null;
|
||||
const { article, ready, error } = this.state;
|
||||
|
||||
if (error) {
|
||||
return <BackendError error={error} sx={{ mt: 2 }} onRetry={this.handleRetry} />;
|
||||
}
|
||||
if (!article || !ready) return <ArticleLoading />;
|
||||
return <ArticleView sku={sku} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
emptyLine,
|
||||
lineFromApi,
|
||||
manifestConfig,
|
||||
TextEditorDialog,
|
||||
TextEditorPanel,
|
||||
PdfViewerPanel,
|
||||
ArticleSearchDialog,
|
||||
} from '../../components/manifest';
|
||||
import ManifestPdfDialog from '../../components/manifest/ManifestPdfDialog';
|
||||
@@ -32,7 +33,7 @@ import { notifyError } from '../../utils/notify';
|
||||
import { generatePdfThumbnail } from '../../utils/pdfThumbnail';
|
||||
import { extractPdfText } from '../../utils/pdfText';
|
||||
import { getDeliveryEditorAutomat } from '../../automats/deliveryEditorAutomat';
|
||||
import { setLastDeliveryPath } from '../../automats';
|
||||
import { setLastDeliveryPath, userSettingsAutomat, setViewerPosition } from '../../automats';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
|
||||
class LieferungNeuPage extends PureComponent {
|
||||
@@ -54,6 +55,9 @@ class LieferungNeuPage extends PureComponent {
|
||||
pdfFileName: '',
|
||||
textEditorOpen: false,
|
||||
textEditorAttachment: null,
|
||||
viewerPosition: userSettingsAutomat.state.viewerPosition,
|
||||
pdfViewerOpen: false,
|
||||
pdfViewerAttachment: null,
|
||||
articleSearchDialogOpen: false,
|
||||
articleSearchLine: null,
|
||||
unmatchedSupplierName: null,
|
||||
@@ -77,6 +81,8 @@ class LieferungNeuPage extends PureComponent {
|
||||
this.handleAbortParse = this.handleAbortParse.bind(this);
|
||||
this.handleAttachmentClick = this.handleAttachmentClick.bind(this);
|
||||
this.handleCloseTextEditor = this.handleCloseTextEditor.bind(this);
|
||||
this.handleTextEditorPositionChange = this.handleTextEditorPositionChange.bind(this);
|
||||
this.handleClosePdfViewer = this.handleClosePdfViewer.bind(this);
|
||||
this.handleSaveTextEditor = this.handleSaveTextEditor.bind(this);
|
||||
this.handleTextEditorContentLoaded = this.handleTextEditorContentLoaded.bind(this);
|
||||
this.ensureAttachmentFile = this.ensureAttachmentFile.bind(this);
|
||||
@@ -90,6 +96,9 @@ class LieferungNeuPage extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubscribe = this.automat.subscribe(this);
|
||||
this._settingsUnsub = userSettingsAutomat.subscribe(this, (state) => ({
|
||||
viewerPosition: state.viewerPosition,
|
||||
}));
|
||||
this._cancelled = false;
|
||||
if (this.props.docId != null) {
|
||||
setLastDeliveryPath(`/lieferung/${this.props.docId}`);
|
||||
@@ -131,6 +140,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
this._skuDebounceTimers = null;
|
||||
}
|
||||
this.unsubscribe?.();
|
||||
this._settingsUnsub?.();
|
||||
}
|
||||
|
||||
async loadPartiesOnly() {
|
||||
@@ -862,30 +872,21 @@ class LieferungNeuPage extends PureComponent {
|
||||
this.setState({
|
||||
textEditorOpen: true,
|
||||
textEditorAttachment: attachment,
|
||||
pdfViewerOpen: false,
|
||||
pdfViewerAttachment: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPdf) {
|
||||
if (
|
||||
(attachment.file instanceof Blob || attachment.file instanceof File) &&
|
||||
attachment.file.size > 0
|
||||
) {
|
||||
const url = URL.createObjectURL(attachment.file);
|
||||
window.open(url, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = this.props.docId ?? attachment.manifestId ?? this.state.id;
|
||||
const fileId = attachment.fileId ?? attachment.id;
|
||||
if (docId != null && fileId != null) {
|
||||
window.open(
|
||||
`/api/delivery/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`,
|
||||
'_blank',
|
||||
);
|
||||
void this.ensureAttachmentFile(attachment);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
pdfViewerOpen: true,
|
||||
pdfViewerAttachment: attachment,
|
||||
textEditorOpen: false,
|
||||
textEditorAttachment: null,
|
||||
});
|
||||
void this.ensureAttachmentFile(attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachment.file instanceof Blob || attachment.file instanceof File) {
|
||||
@@ -913,6 +914,88 @@ class LieferungNeuPage extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
handleClosePdfViewer() {
|
||||
this.setState({
|
||||
pdfViewerOpen: false,
|
||||
pdfViewerAttachment: null,
|
||||
});
|
||||
}
|
||||
|
||||
handleTextEditorPositionChange(position) {
|
||||
setViewerPosition(position);
|
||||
}
|
||||
|
||||
renderViewerPanels(wide) {
|
||||
const {
|
||||
textEditorOpen,
|
||||
textEditorAttachment,
|
||||
pdfViewerOpen,
|
||||
pdfViewerAttachment,
|
||||
viewerPosition,
|
||||
} = this.state;
|
||||
const docId = this.props.docId ?? this.state.id;
|
||||
|
||||
const panels = [];
|
||||
|
||||
if (textEditorOpen && textEditorAttachment) {
|
||||
panels.push(
|
||||
<TextEditorPanel
|
||||
key="text"
|
||||
docId={docId}
|
||||
attachment={textEditorAttachment}
|
||||
position={viewerPosition}
|
||||
wide={wide}
|
||||
height={panels.length === 0 ? '60vh' : '40vh'}
|
||||
onPositionChange={this.handleTextEditorPositionChange}
|
||||
onClose={this.handleCloseTextEditor}
|
||||
onSave={this.handleSaveTextEditor}
|
||||
onContentLoad={this.handleTextEditorContentLoaded}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (pdfViewerOpen && pdfViewerAttachment) {
|
||||
panels.push(
|
||||
<PdfViewerPanel
|
||||
key="pdf"
|
||||
docId={docId}
|
||||
attachment={pdfViewerAttachment}
|
||||
position={viewerPosition}
|
||||
wide={wide}
|
||||
height={panels.length === 0 ? '60vh' : '40vh'}
|
||||
onPositionChange={this.handleTextEditorPositionChange}
|
||||
onClose={this.handleClosePdfViewer}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (panels.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
gridColumn: { xs: '1 / -1', sm: wide >= 2 ? 'span 2' : 'span 1', md: `span ${wide}` },
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
minWidth: 0,
|
||||
...(viewerPosition === 'right'
|
||||
? { position: 'sticky', top: 16, alignSelf: 'flex-start' }
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{panels}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
isViewerOpen() {
|
||||
return Boolean(
|
||||
(this.state.textEditorOpen && this.state.textEditorAttachment) ||
|
||||
(this.state.pdfViewerOpen && this.state.pdfViewerAttachment),
|
||||
);
|
||||
}
|
||||
|
||||
handleSaveTextEditor(newText) {
|
||||
const attachment = this.state.textEditorAttachment;
|
||||
if (!attachment) return;
|
||||
@@ -1067,6 +1150,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
onNumberChange={this.handleNumberChange}
|
||||
onExternalNumberChange={this.handleExternalNumberChange}
|
||||
/>
|
||||
{this.state.viewerPosition === 'top' && this.renderViewerPanels(3)}
|
||||
{partyId == null && (!lines || lines.length === 0) ? (
|
||||
<PanelTile wide={3} minHeight={160} color="#e2e8f0">
|
||||
{this.state.unmatchedSupplierName ? (
|
||||
@@ -1120,6 +1204,11 @@ class LieferungNeuPage extends PureComponent {
|
||||
columns={DELIVERY_LINE_COLUMNS}
|
||||
lines={lines}
|
||||
saving={saving}
|
||||
wide={
|
||||
this.state.viewerPosition === 'right' && this.isViewerOpen()
|
||||
? 2
|
||||
: 3
|
||||
}
|
||||
linesColor={cfg.linesColor}
|
||||
supplierArticlesMap={this.state.supplierArticlesMap}
|
||||
supplierArticlesByLineKey={this.state.supplierArticlesByLineKey}
|
||||
@@ -1137,6 +1226,8 @@ class LieferungNeuPage extends PureComponent {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{this.state.viewerPosition === 'right' && this.renderViewerPanels(2)}
|
||||
{this.state.viewerPosition === 'below' && this.renderViewerPanels(3)}
|
||||
{notes && notes.length > 0 && (
|
||||
<PanelTile wide={3} color="#fffbeb">
|
||||
<Typography variant="subtitle2" fontWeight={700} color="warning.dark" sx={{ mb: 1.5 }}>
|
||||
@@ -1162,14 +1253,6 @@ class LieferungNeuPage extends PureComponent {
|
||||
onAbort={this.handleAbortParse}
|
||||
/>
|
||||
|
||||
<TextEditorDialog
|
||||
open={this.state.textEditorOpen}
|
||||
docId={this.props.docId ?? this.state.id}
|
||||
attachment={this.state.textEditorAttachment}
|
||||
onClose={this.handleCloseTextEditor}
|
||||
onSave={this.handleSaveTextEditor}
|
||||
onContentLoad={this.handleTextEditorContentLoaded}
|
||||
/>
|
||||
<ArticleSearchDialog
|
||||
open={this.state.articleSearchDialogOpen}
|
||||
line={this.state.articleSearchLine}
|
||||
|
||||
Reference in New Issue
Block a user