This commit is contained in:
seb
2026-07-31 17:51:57 +02:00
parent 797ea59bed
commit a6a9777f31
33 changed files with 1432 additions and 422 deletions

View File

@@ -0,0 +1,42 @@
import { Component } from 'react';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Link from '@mui/material/Link';
class CategoryBreadcrumbs extends Component {
handleNavigate = (categoryId) => {
this.props.onNavigate(categoryId);
};
render() {
const { crumbs } = this.props;
const visible = (crumbs ?? []).filter((crumb) => crumb.name);
if (visible.length === 0) return null;
return (
<Breadcrumbs sx={{ mb: 2 }} aria-label="Kategoriepfad">
{visible.map((crumb, index) => (
<Link
key={crumb.id}
component="button"
type="button"
variant="body2"
underline="always"
color="inherit"
aria-current={index === visible.length - 1 ? 'page' : undefined}
onClick={() => this.handleNavigate(crumb.id)}
sx={{
fontWeight: 600,
lineHeight: 1.43,
p: 0,
minWidth: 0,
}}
>
{crumb.name}
</Link>
))}
</Breadcrumbs>
);
}
}
export default CategoryBreadcrumbs;

View File

@@ -0,0 +1,30 @@
import NameDialog from '../NameDialog';
const TITLES = {
create: 'Neue Kategorie',
edit: 'Kategorie bearbeiten',
};
const SUBMIT_LABELS = {
create: 'Anlegen',
edit: 'Speichern',
};
export default function CategoryNameDialog({
open,
mode = 'create',
initialName,
onClose,
onSubmit,
}) {
return (
<NameDialog
open={open}
title={TITLES[mode]}
submitLabel={SUBMIT_LABELS[mode]}
initialName={initialName}
onClose={onClose}
onSubmit={onSubmit}
/>
);
}

View File

@@ -0,0 +1,21 @@
import { Component } from 'react';
import CategoryNameDialog from './CategoryNameDialog';
/** Edit flow — wire onSubmit when PUT /api/category/:id exists. */
class EditCategoryDialog extends Component {
render() {
const { open, onClose, initialName, onSubmit } = this.props;
return (
<CategoryNameDialog
open={open}
mode="edit"
initialName={initialName}
onClose={onClose}
onSubmit={onSubmit}
/>
);
}
}
export default EditCategoryDialog;

View File

@@ -0,0 +1,56 @@
import { Component } from 'react';
import { connect } from 'react-redux';
import { enqueueSnackbar } from 'notistack';
import { ActionTile } from '../tiles';
import { createCategory as createCategoryThunk } from '../../store/categorySlice';
import CategoryNameDialog from './CategoryNameDialog';
class NewCategoryTile extends Component {
constructor(props) {
super(props);
this.state = { open: false };
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleOpen() {
this.setState({ open: true });
}
handleClose() {
this.setState({ open: false });
}
async handleSubmit({ name }) {
const { parentId } = this.props;
await this.props.createCategory({ parentId, name }).unwrap();
enqueueSnackbar('Kategorie angelegt.', { variant: 'success' });
}
render() {
const { parentId, disabled } = this.props;
return (
<>
<ActionTile
label="Neue Kategorie"
disabled={disabled || parentId == null}
onClick={this.handleOpen}
/>
<CategoryNameDialog
open={this.state.open}
mode="create"
onClose={this.handleClose}
onSubmit={this.handleSubmit}
/>
</>
);
}
}
const mapDispatchToProps = {
createCategory: createCategoryThunk,
};
export default connect(null, mapDispatchToProps)(NewCategoryTile);

View File

@@ -0,0 +1,4 @@
export { default as CategoryNameDialog } from './CategoryNameDialog';
export { default as CategoryBreadcrumbs } from './CategoryBreadcrumbs';
export { default as NewCategoryTile } from './NewCategoryTile';
export { default as EditCategoryDialog } from './EditCategoryDialog';