Show article history in a table via GET /article/{sku}/history.
This commit is contained in:
@@ -1,20 +1,16 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
import CategoryIcon from '@mui/icons-material/Category';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import LabelIcon from '@mui/icons-material/Label';
|
||||
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
||||
import ManageSearchIcon from '@mui/icons-material/ManageSearch';
|
||||
import PushPinIcon from '@mui/icons-material/PushPin';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import {
|
||||
ARTICLE_TAB,
|
||||
DEFAULT_ARTICLE_TAB,
|
||||
@@ -22,9 +18,15 @@ import {
|
||||
readPreferredArticleTab,
|
||||
writePreferredArticleTab,
|
||||
} from '../../pages/artikel/articleRoute';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { PanelTile } from '../panels';
|
||||
import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from '../panels/panelTileLayout';
|
||||
import ArticleAttributesTab from './tabs/ArticleAttributesTab';
|
||||
import ArticleCompoundTab from './tabs/ArticleCompoundTab';
|
||||
import ArticleHistoryTab from './tabs/ArticleHistoryTab';
|
||||
import ArticleLabelTab from './tabs/ArticleLabelTab';
|
||||
import ArticleSeoTab from './tabs/ArticleSeoTab';
|
||||
import ArticleSettingsTab from './tabs/ArticleSettingsTab';
|
||||
import ArticleSupplierTab from './tabs/ArticleSupplierTab';
|
||||
|
||||
const ALWAYS_TABS = [
|
||||
{ id: ARTICLE_TAB.history, label: 'History', color: '#fed7aa', Icon: HistoryIcon },
|
||||
@@ -67,42 +69,14 @@ function visibleTabs(article) {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
function PanelValue({ label, children }) {
|
||||
if (children == null || children === '') return null;
|
||||
return (
|
||||
<Box sx={{ mb: 1 }}>
|
||||
{label ? (
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
{label}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography variant="body2" component="div">
|
||||
{children}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Placeholder({ label }) {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{label}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
class ArticleTileTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
busy: false,
|
||||
preferredTab: readPreferredArticleTab(),
|
||||
};
|
||||
this.handleTabChange = this.handleTabChange.bind(this);
|
||||
this.handlePinDefault = this.handlePinDefault.bind(this);
|
||||
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
|
||||
}
|
||||
|
||||
handleTabChange(_event, tab) {
|
||||
@@ -116,69 +90,28 @@ class ArticleTileTab extends Component {
|
||||
this.setState({ preferredTab: tab });
|
||||
}
|
||||
|
||||
async handleDeleteArticle() {
|
||||
const { article: articleData, onArticleDeleted } = this.props;
|
||||
if (this.state.busy) return;
|
||||
if (!window.confirm(`Artikel „${articleData.name ?? articleData.sku}“ wirklich löschen?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.article.delete(articleData.sku);
|
||||
onArticleDeleted?.(articleData);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
renderSeo() {
|
||||
const { article: articleData } = this.props;
|
||||
return (
|
||||
<>
|
||||
<PanelValue label="Slug">{articleData.slug}</PanelValue>
|
||||
<PanelValue label="SEO-Titel">{articleData.seoTitle}</PanelValue>
|
||||
<PanelValue label="SEO-Beschreibung">{articleData.seoDescription}</PanelValue>
|
||||
<PanelValue label="Beschreibung">{articleData.description}</PanelValue>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderSettings() {
|
||||
const { busy } = this.state;
|
||||
return (
|
||||
<Box>
|
||||
<Button
|
||||
type="button"
|
||||
color="error"
|
||||
variant="contained"
|
||||
startIcon={<DeleteIcon />}
|
||||
disabled={busy}
|
||||
onClick={() => void this.handleDeleteArticle()}
|
||||
>
|
||||
Artikel löschen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
renderPanel() {
|
||||
const { article: articleData, onArticleDeleted } = this.props;
|
||||
switch (this.props.tab) {
|
||||
case ARTICLE_TAB.history:
|
||||
return <Placeholder label="History" />;
|
||||
return <ArticleHistoryTab sku={articleData.sku} />;
|
||||
case ARTICLE_TAB.supplier:
|
||||
return <Placeholder label="Supplier" />;
|
||||
return <ArticleSupplierTab />;
|
||||
case ARTICLE_TAB.seo:
|
||||
return this.renderSeo();
|
||||
return <ArticleSeoTab article={articleData} />;
|
||||
case ARTICLE_TAB.label:
|
||||
return <Placeholder label="Label" />;
|
||||
return <ArticleLabelTab />;
|
||||
case ARTICLE_TAB.attributes:
|
||||
return <Placeholder label="Attribute und Merkmale" />;
|
||||
return <ArticleAttributesTab />;
|
||||
case ARTICLE_TAB.compound:
|
||||
return <Placeholder label="Compound Editor" />;
|
||||
return <ArticleCompoundTab />;
|
||||
case ARTICLE_TAB.settings:
|
||||
return this.renderSettings();
|
||||
return (
|
||||
<ArticleSettingsTab
|
||||
article={articleData}
|
||||
onArticleDeleted={onArticleDeleted}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
12
src/components/article/tabs/ArticleAttributesTab.jsx
Normal file
12
src/components/article/tabs/ArticleAttributesTab.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default class ArticleAttributesTab extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Attribute und Merkmale
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/components/article/tabs/ArticleCompoundTab.jsx
Normal file
12
src/components/article/tabs/ArticleCompoundTab.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default class ArticleCompoundTab extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Compound Editor
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
71
src/components/article/tabs/ArticleHistoryTab.jsx
Normal file
71
src/components/article/tabs/ArticleHistoryTab.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Component } from 'react';
|
||||
import Table from '@mui/material/Table';
|
||||
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 { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
|
||||
function formatDate(iso) {
|
||||
return new Date(iso).toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
export default class ArticleHistoryTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { history: null };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
void this.context.article
|
||||
.history(this.props.sku)
|
||||
.then((res) => this.setState({ history: res.history }))
|
||||
.catch(notifyError);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { history } = this.state;
|
||||
if (history == null) return null;
|
||||
return (
|
||||
<Table
|
||||
size="small"
|
||||
sx={{
|
||||
'& td': { borderBottom: 'none', bgcolor: 'transparent' },
|
||||
'& th': { bgcolor: 'transparent' },
|
||||
}}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{
|
||||
borderBottom: '1px solid rgba(0, 0, 0, 0.35)',
|
||||
width: '1%',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
Datum
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{ borderBottom: '1px solid rgba(0, 0, 0, 0.35)' }}
|
||||
/>
|
||||
</TableRow>
|
||||
</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' }}>{entry.text}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/components/article/tabs/ArticleLabelTab.jsx
Normal file
12
src/components/article/tabs/ArticleLabelTab.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default class ArticleLabelTab extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Label
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/components/article/tabs/ArticleSeoTab.jsx
Normal file
33
src/components/article/tabs/ArticleSeoTab.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
function PanelValue({ label, children }) {
|
||||
if (children == null || children === '') return null;
|
||||
return (
|
||||
<Box sx={{ mb: 1 }}>
|
||||
{label ? (
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
{label}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography variant="body2" component="div">
|
||||
{children}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default class ArticleSeoTab extends Component {
|
||||
render() {
|
||||
const { article: articleData } = this.props;
|
||||
return (
|
||||
<>
|
||||
<PanelValue label="Slug">{articleData.slug}</PanelValue>
|
||||
<PanelValue label="SEO-Titel">{articleData.seoTitle}</PanelValue>
|
||||
<PanelValue label="SEO-Beschreibung">{articleData.seoDescription}</PanelValue>
|
||||
<PanelValue label="Beschreibung">{articleData.description}</PanelValue>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
51
src/components/article/tabs/ArticleSettingsTab.jsx
Normal file
51
src/components/article/tabs/ArticleSettingsTab.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
|
||||
export default class ArticleSettingsTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { busy: false };
|
||||
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
|
||||
}
|
||||
|
||||
async handleDeleteArticle() {
|
||||
const { article: articleData, onArticleDeleted } = this.props;
|
||||
if (this.state.busy) return;
|
||||
if (!window.confirm(`Artikel „${articleData.name ?? articleData.sku}“ wirklich löschen?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.article.delete(articleData.sku);
|
||||
onArticleDeleted?.(articleData);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { busy } = this.state;
|
||||
return (
|
||||
<Box>
|
||||
<Button
|
||||
type="button"
|
||||
color="error"
|
||||
variant="contained"
|
||||
startIcon={<DeleteIcon />}
|
||||
disabled={busy}
|
||||
onClick={() => void this.handleDeleteArticle()}
|
||||
>
|
||||
Artikel löschen
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/components/article/tabs/ArticleSupplierTab.jsx
Normal file
12
src/components/article/tabs/ArticleSupplierTab.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default class ArticleSupplierTab extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Supplier
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user