u
This commit is contained in:
294
src/components/article/ArticleTileTab.jsx
Normal file
294
src/components/article/ArticleTileTab.jsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
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 SettingsIcon from '@mui/icons-material/Settings';
|
||||
import { article } from '../../api';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { PanelTile } from '../panels';
|
||||
import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from '../panels/panelTileLayout';
|
||||
|
||||
const TAB = {
|
||||
history: 'history',
|
||||
supplier: 'supplier',
|
||||
seo: 'seo',
|
||||
label: 'label',
|
||||
attributes: 'attributes',
|
||||
compound: 'compound',
|
||||
settings: 'settings',
|
||||
};
|
||||
|
||||
const ALWAYS_TABS = [
|
||||
{ id: TAB.history, label: 'History', color: '#fed7aa', Icon: HistoryIcon },
|
||||
{ id: TAB.supplier, label: 'Supplier', color: '#bbf7d0', Icon: LocalShippingIcon },
|
||||
{ id: TAB.seo, label: 'SEO & Beschreibung', color: '#99f6e4', Icon: ManageSearchIcon },
|
||||
{ id: TAB.label, label: 'Label', color: '#fde68a', Icon: LabelIcon },
|
||||
{ id: TAB.attributes, label: 'Attribute und Merkmale', color: '#bfdbfe', Icon: CategoryIcon },
|
||||
];
|
||||
|
||||
const COMPOUND_TAB = {
|
||||
id: TAB.compound,
|
||||
label: 'Compound Editor',
|
||||
color: '#e9d5ff',
|
||||
Icon: AccountTreeIcon,
|
||||
};
|
||||
|
||||
const SETTINGS_TAB = {
|
||||
id: TAB.settings,
|
||||
label: 'Einstellungen',
|
||||
color: '#fca5a5',
|
||||
Icon: SettingsIcon,
|
||||
};
|
||||
|
||||
function isCompoundArticle(article) {
|
||||
return (
|
||||
article?.articleListCount > 0 ||
|
||||
article?.optionsCount > 0 ||
|
||||
article?.childCount > 0 ||
|
||||
article?.fatherArticleId != null
|
||||
);
|
||||
}
|
||||
|
||||
function visibleTabs(article) {
|
||||
const tabs = [...ALWAYS_TABS];
|
||||
if (isCompoundArticle(article)) {
|
||||
tabs.push(COMPOUND_TAB);
|
||||
}
|
||||
tabs.push(SETTINGS_TAB);
|
||||
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} — noch nicht verfügbar.
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
class ArticleTileTab extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { tab: TAB.seo, busy: false };
|
||||
this.handleTabChange = this.handleTabChange.bind(this);
|
||||
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.article === this.props.article) return;
|
||||
const tabs = visibleTabs(this.props.article);
|
||||
if (!tabs.some((row) => row.id === this.state.tab)) {
|
||||
this.setState({ tab: TAB.seo });
|
||||
}
|
||||
}
|
||||
|
||||
handleTabChange(_event, tab) {
|
||||
this.setState({ 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 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() {
|
||||
switch (this.state.tab) {
|
||||
case TAB.history:
|
||||
return <Placeholder label="History" />;
|
||||
case TAB.supplier:
|
||||
return <Placeholder label="Supplier" />;
|
||||
case TAB.seo:
|
||||
return this.renderSeo();
|
||||
case TAB.label:
|
||||
return <Placeholder label="Label" />;
|
||||
case TAB.attributes:
|
||||
return <Placeholder label="Attribute und Merkmale" />;
|
||||
case TAB.compound:
|
||||
return <Placeholder label="Compound Editor" />;
|
||||
case TAB.settings:
|
||||
return this.renderSettings();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { article: articleData, wide = 3, minHeight = 200, sx, tileKey } = this.props;
|
||||
const tabs = visibleTabs(articleData);
|
||||
const { tab } = this.state;
|
||||
const active = tabs.find((row) => row.id === tab) ?? ALWAYS_TABS.find((row) => row.id === TAB.seo);
|
||||
const activeColor = active?.color;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
gridColumn: responsiveColumnSpan(wide),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minWidth: 0,
|
||||
filter:
|
||||
'drop-shadow(0px 2px 4px rgba(0,0,0,0.2)) drop-shadow(0px 4px 5px rgba(0,0,0,0.14)) drop-shadow(0px 1px 10px rgba(0,0,0,0.12))',
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tab}
|
||||
onChange={this.handleTabChange}
|
||||
variant="standard"
|
||||
TabIndicatorProps={{ sx: { display: 'none', height: 0 } }}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
minHeight: 36,
|
||||
px: 0,
|
||||
mb: 0,
|
||||
borderBottom: 'none',
|
||||
overflow: 'visible',
|
||||
'& .MuiTabs-scroller': {
|
||||
px: 0,
|
||||
borderBottom: 'none',
|
||||
overflow: 'visible !important',
|
||||
},
|
||||
'& .MuiTabs-list, & .MuiTabs-flexContainer': {
|
||||
flexWrap: 'wrap-reverse',
|
||||
columnGap: 0.75,
|
||||
rowGap: 0,
|
||||
alignItems: 'stretch',
|
||||
alignContent: 'flex-end',
|
||||
},
|
||||
'& .MuiTabs-indicator': { display: 'none' },
|
||||
'& .MuiTab-root': {
|
||||
flexShrink: 0,
|
||||
maxWidth: 'none',
|
||||
minHeight: 40,
|
||||
marginBottom: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{tabs.map((row) => {
|
||||
const selected = tab === row.id;
|
||||
const { Icon } = row;
|
||||
return (
|
||||
<Tab
|
||||
key={row.id}
|
||||
value={row.id}
|
||||
aria-label={row.label}
|
||||
title={row.label}
|
||||
icon={<Icon fontSize="small" />}
|
||||
sx={{
|
||||
minHeight: 40,
|
||||
minWidth: 48,
|
||||
py: 0.5,
|
||||
px: 1.25,
|
||||
border: 'none',
|
||||
borderBottom: 'none',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
textTransform: 'none',
|
||||
bgcolor: row.color,
|
||||
opacity: selected ? 1 : 0.72,
|
||||
color: 'text.primary',
|
||||
zIndex: selected ? 2 : 1,
|
||||
boxShadow: 'none',
|
||||
'&.Mui-selected': {
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
<PanelTile
|
||||
wide={wide}
|
||||
minHeight={minHeight}
|
||||
tileKey={tileKey}
|
||||
elevation={0}
|
||||
sx={{
|
||||
gridColumn: 'auto',
|
||||
width: '100%',
|
||||
bgcolor: activeColor,
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: (theme) =>
|
||||
`${(theme.shape.borderRadius || 4) * PANEL_TILE_BORDER_RADIUS}px`,
|
||||
borderBottomLeftRadius: (theme) =>
|
||||
`${(theme.shape.borderRadius || 4) * PANEL_TILE_BORDER_RADIUS}px`,
|
||||
borderBottomRightRadius: (theme) =>
|
||||
`${(theme.shape.borderRadius || 4) * PANEL_TILE_BORDER_RADIUS}px`,
|
||||
boxShadow: 'none',
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{this.renderPanel()}
|
||||
</PanelTile>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ArticleTileTab;
|
||||
Reference in New Issue
Block a user