u
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -13,6 +13,7 @@
|
|||||||
| Brand setzen / entfernen | `PUT /api/article/:sku/brand` | `{ brandId: number \| null }` | `{ article }` (Detail) |
|
| Brand setzen / entfernen | `PUT /api/article/:sku/brand` | `{ brandId: number \| null }` | `{ article }` (Detail) |
|
||||||
| Gewicht setzen | `PUT /api/article/:sku/weight` | `{ weight: number }` | `{ article }` (Detail) |
|
| Gewicht setzen | `PUT /api/article/:sku/weight` | `{ weight: number }` | `{ article }` (Detail) |
|
||||||
| Kategorien setzen (replace-all) | `PUT /api/article/:sku/categories` | `{ categoryIds: number[] }` | `{ article }` (Detail) |
|
| Kategorien setzen (replace-all) | `PUT /api/article/:sku/categories` | `{ categoryIds: number[] }` | `{ article }` (Detail) |
|
||||||
|
| Kategorie umbenennen | `PATCH /api/category/:id` | `{ name }` | `{ category: { id, name, slug } }` |
|
||||||
| Detail liefert neue Felder | `GET /api/article/:sku` | – | `{ article }` mit `eans`, `categoryIds`, `weight`, `brand` |
|
| Detail liefert neue Felder | `GET /api/article/:sku` | – | `{ article }` mit `eans`, `categoryIds`, `weight`, `brand` |
|
||||||
|
|
||||||
`Brand` = `{ id, name, gsprpinfo \| null }`.
|
`Brand` = `{ id, name, gsprpinfo \| null }`.
|
||||||
@@ -23,7 +24,7 @@ Alle PUT/PATCH-Endpoints der Artikel liefern dasselbe Shape wie `GET /api/articl
|
|||||||
|
|
||||||
### API-Client (`src/api/index.js`)
|
### API-Client (`src/api/index.js`)
|
||||||
- Alle Calls über den bestehenden Client (`credentials: 'include'`) — Frontend spricht nur HTTP, nie direkt den Store.
|
- Alle Calls über den bestehenden Client (`credentials: 'include'`) — Frontend spricht nur HTTP, nie direkt den Store.
|
||||||
- Neuen Methoden für: `brand.list` / `brand.upsert` / `brand.delete` und `article.setEansBySku` / `article.setBrandIdBySku` / `article.setWeightBySku` / `article.setCategoryIdsBySku`.
|
- Neuen Methoden für: `brand.list` / `brand.upsert` / `brand.delete` und `article.setEansBySku` / `article.setBrandIdBySku` / `article.setWeightBySku` / `article.setCategoryIdsBySku` sowie `category.rename`.
|
||||||
- **Kein `companyId` in Request/Pfad/Query senden** — die Company kommt aus der Session.
|
- **Kein `companyId` in Request/Pfad/Query senden** — die Company kommt aus der Session.
|
||||||
|
|
||||||
### Brand-UI
|
### Brand-UI
|
||||||
@@ -37,12 +38,14 @@ Alle PUT/PATCH-Endpoints der Artikel liefern dasselbe Shape wie `GET /api/articl
|
|||||||
- **Brand**: Dropdown aus `GET /api/brand`; „Keine Brand“ ⇒ `brandId: null`. Deal nur mit Brands derselben Company (sonst `404 Brand not found`).
|
- **Brand**: Dropdown aus `GET /api/brand`; „Keine Brand“ ⇒ `brandId: null`. Deal nur mit Brands derselben Company (sonst `404 Brand not found`).
|
||||||
- **Gewicht**: Zahlenfeld, `>= 0`. `400 Invalid weight` sonst.
|
- **Gewicht**: Zahlenfeld, `>= 0`. `400 Invalid weight` sonst.
|
||||||
- **Kategorien**: Mehrfachauswahl aus der Category-Tree; nur Kategorien derselben Company (sonst `400 Invalid categoryIds`). Leere Liste entfernt alle.
|
- **Kategorien**: Mehrfachauswahl aus der Category-Tree; nur Kategorien derselben Company (sonst `400 Invalid categoryIds`). Leere Liste entfernt alle.
|
||||||
|
- **Kategorie umbenennen**: Inline/Formular im Category-Tree → `PATCH /api/category/:id` mit `{ name }`. Leeres `name` ⇒ `400 Name required`. Nach Save das zurückgegebene `{ id, name, slug }` im Baum aktualisieren.
|
||||||
- Nach jedem Save das zurückgegebene Detail-Objekt in den Store übernehmen.
|
- Nach jedem Save das zurückgegebene Detail-Objekt in den Store übernehmen.
|
||||||
|
|
||||||
### Fehlerbehandlung
|
### Fehlerbehandlung
|
||||||
- Fehler als Toast/Formular-Fehler abbilden:
|
- Fehler als Toast/Formular-Fehler abbilden:
|
||||||
- `Brand in use` (409), `Invalid eans` / `Invalid weight` / `Invalid categoryIds` / `Invalid brand` (400)
|
- `Brand in use` (409), `Invalid eans` / `Invalid weight` / `Invalid categoryIds` / `Invalid brand` (400)
|
||||||
- `Brand not found` / `Article not found` (404)
|
- `Brand not found` / `Article not found` / `Category not found` (404)
|
||||||
|
- `Name required` (400) bei leerem Kategorienamen
|
||||||
- `No company selected` (400) → Company-Wechsel-Flow
|
- `No company selected` (400) → Company-Wechsel-Flow
|
||||||
- `Not authorized` (403)
|
- `Not authorized` (403)
|
||||||
|
|
||||||
@@ -50,4 +53,5 @@ Alle PUT/PATCH-Endpoints der Artikel liefern dasselbe Shape wie `GET /api/articl
|
|||||||
- API-Doku: `../dashback/docs/api/README.md` und `../dashback/docs/api/routes/`
|
- API-Doku: `../dashback/docs/api/README.md` und `../dashback/docs/api/routes/`
|
||||||
- `brand/list.md`, `brand/upsert.md`, `brand/delete.md`
|
- `brand/list.md`, `brand/upsert.md`, `brand/delete.md`
|
||||||
- `article/set-eans.md`, `article/set-brand.md`, `article/set-weight.md`, `article/set-categories.md`
|
- `article/set-eans.md`, `article/set-brand.md`, `article/set-weight.md`, `article/set-categories.md`
|
||||||
|
- `category/rename.md`
|
||||||
- Company-Scope: `google_auth.setSelectedCompany` (Store) — siehe `../dashdbschema/README.md`.
|
- Company-Scope: `google_auth.setSelectedCompany` (Store) — siehe `../dashdbschema/README.md`.
|
||||||
|
|||||||
@@ -332,6 +332,19 @@ export const category = {
|
|||||||
delete(categoryId) {
|
delete(categoryId) {
|
||||||
return http(`/api/category/${categoryId}`, { method: 'DELETE' });
|
return http(`/api/category/${categoryId}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PATCH /api/category/:id
|
||||||
|
* Body: { name } — renames and re-slugifies in the company default language.
|
||||||
|
* Empty name → 400 name is required.
|
||||||
|
* → 200 { category: { id, name, slug } }
|
||||||
|
*/
|
||||||
|
updateName(categoryId, name) {
|
||||||
|
return http(`/api/category/${categoryId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: { name },
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function isSubNavActive(pathname, basePath, segment) {
|
|||||||
return pathname === subPath || pathname === `${subPath}/`;
|
return pathname === subPath || pathname === `${subPath}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PageLayout({ title, showTitle = true, subNav, basePath, children }) {
|
export default function PageLayout({ title, showTitle = true, titleAction, subNav, basePath, children }) {
|
||||||
const hasSubNav = subNav?.length > 0;
|
const hasSubNav = subNav?.length > 0;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
@@ -81,9 +81,12 @@ export default function PageLayout({ title, showTitle = true, subNav, basePath,
|
|||||||
|
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
{showTitle ? (
|
{showTitle ? (
|
||||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{titleAction}
|
||||||
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
import PageLayout from './PageLayout';
|
import PageLayout from './PageLayout';
|
||||||
import { NAV_ITEMS, SUB_NAV } from '../nav';
|
import { NAV_ITEMS, SUB_NAV } from '../nav';
|
||||||
import { isKategorienPath } from '../pages/artikel/categoryRoute';
|
import { isKategorienPath } from '../pages/artikel/categoryRoute';
|
||||||
|
import { selectRenameMode, setRenameMode } from '../store/categorySlice';
|
||||||
|
|
||||||
export default function SectionLayout({ sectionPath }) {
|
export default function SectionLayout({ sectionPath }) {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const renameMode = useSelector(selectRenameMode);
|
||||||
const title = NAV_ITEMS.find((item) => item.path === sectionPath)?.label ?? '';
|
const title = NAV_ITEMS.find((item) => item.path === sectionPath)?.label ?? '';
|
||||||
const subNav = SUB_NAV[sectionPath];
|
const subNav = SUB_NAV[sectionPath];
|
||||||
const activeSub = subNav?.find((item) => {
|
const activeSub = subNav?.find((item) => {
|
||||||
@@ -27,8 +33,31 @@ export default function SectionLayout({ sectionPath }) {
|
|||||||
? title
|
? title
|
||||||
: (activeSub?.label ?? title);
|
: (activeSub?.label ?? title);
|
||||||
|
|
||||||
|
const showsRenameSwitch = activeSub?.segment === 'kategorien';
|
||||||
|
|
||||||
|
const titleAction = showsRenameSwitch ? (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={renameMode}
|
||||||
|
onChange={(event) => dispatch(setRenameMode(event.target.checked))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Editieren"
|
||||||
|
labelPlacement="end"
|
||||||
|
sx={{ ml: 0 }}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout title={pageTitle} showTitle={!isArticleDetail} subNav={subNav} basePath={sectionPath}>
|
<PageLayout
|
||||||
|
title={pageTitle}
|
||||||
|
showTitle={!isArticleDetail}
|
||||||
|
subNav={subNav}
|
||||||
|
basePath={sectionPath}
|
||||||
|
titleAction={titleAction}
|
||||||
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ import { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import BackendError from '../../components/BackendError';
|
import BackendError from '../../components/BackendError';
|
||||||
import { NewCategoryTile, CategoryBreadcrumbs } from '../../components/category';
|
import { NewCategoryTile, CategoryBreadcrumbs } from '../../components/category';
|
||||||
import { NewArticleTile } from '../../components/article';
|
import { NewArticleTile } from '../../components/article';
|
||||||
|
import { notifyError } from '../../utils/notify';
|
||||||
import {
|
import {
|
||||||
ArticleTile,
|
ArticleTile,
|
||||||
BackTile,
|
BackTile,
|
||||||
CategoryTile,
|
CategoryTile,
|
||||||
TILE_SIZE,
|
TILE_SIZE,
|
||||||
|
tileBaseSx,
|
||||||
} from '../../components/tiles';
|
} from '../../components/tiles';
|
||||||
import withCategoryRoute from './withCategoryRoute';
|
import withCategoryRoute from './withCategoryRoute';
|
||||||
import { artikelDetailPath, articleSku } from './articleRoute';
|
import { artikelDetailPath, articleSku } from './articleRoute';
|
||||||
@@ -26,6 +30,7 @@ import {
|
|||||||
loadCategoryArticles,
|
loadCategoryArticles,
|
||||||
loadCategoryTree,
|
loadCategoryTree,
|
||||||
navigateToCategory,
|
navigateToCategory,
|
||||||
|
renameCategory,
|
||||||
selectCanNavigateBack,
|
selectCanNavigateBack,
|
||||||
selectCategoryArticles,
|
selectCategoryArticles,
|
||||||
selectCategoryArticlesStatus,
|
selectCategoryArticlesStatus,
|
||||||
@@ -34,6 +39,7 @@ import {
|
|||||||
selectCategoryError,
|
selectCategoryError,
|
||||||
selectCategoryTreeStatus,
|
selectCategoryTreeStatus,
|
||||||
selectChildCategories,
|
selectChildCategories,
|
||||||
|
selectRenameMode,
|
||||||
} from '../../store/categorySlice';
|
} from '../../store/categorySlice';
|
||||||
|
|
||||||
const gridSx = {
|
const gridSx = {
|
||||||
@@ -43,6 +49,13 @@ const gridSx = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ArtikelKategorienPage extends Component {
|
class ArtikelKategorienPage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { drafts: {} };
|
||||||
|
this.handleRenameSubmit = this.handleRenameSubmit.bind(this);
|
||||||
|
this.handleDraftChange = this.handleDraftChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.ensureTreeLoaded();
|
this.ensureTreeLoaded();
|
||||||
this.syncCategoryFromUrl();
|
this.syncCategoryFromUrl();
|
||||||
@@ -127,6 +140,29 @@ class ArtikelKategorienPage extends Component {
|
|||||||
navigate(buildCategoryPath(categories, rootCategoryId, categoryId));
|
navigate(buildCategoryPath(categories, rootCategoryId, categoryId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleDraftChange(categoryId, value) {
|
||||||
|
this.setState((state) => ({
|
||||||
|
drafts: { ...state.drafts, [categoryId]: value },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRenameSubmit = async (categoryId, name) => {
|
||||||
|
const trimmed = String(name ?? '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
enqueueSnackbar('Name ist erforderlich.', { variant: 'error' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.props.renameCategory({
|
||||||
|
categoryId,
|
||||||
|
name: trimmed,
|
||||||
|
}).unwrap();
|
||||||
|
enqueueSnackbar('Kategorie umbenannt.', { variant: 'success' });
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
handleRetryTree = () => {
|
handleRetryTree = () => {
|
||||||
if (this.props.companyId) this.props.loadCategoryTree();
|
if (this.props.companyId) this.props.loadCategoryTree();
|
||||||
};
|
};
|
||||||
@@ -161,6 +197,7 @@ class ArtikelKategorienPage extends Component {
|
|||||||
currentCategoryId,
|
currentCategoryId,
|
||||||
childCategories,
|
childCategories,
|
||||||
articles,
|
articles,
|
||||||
|
renameMode,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!companyId) {
|
if (!companyId) {
|
||||||
@@ -218,15 +255,62 @@ class ArtikelKategorienPage extends Component {
|
|||||||
<BackTile onClick={this.handleNavigateBack} />
|
<BackTile onClick={this.handleNavigateBack} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{childCategories.map((item) => (
|
{childCategories.map((item) => {
|
||||||
|
const label = categoryDisplayName(item.name, item.id, rootCategoryId);
|
||||||
|
if (renameMode) {
|
||||||
|
const draftName =
|
||||||
|
this.state.drafts[item.id] !== undefined
|
||||||
|
? this.state.drafts[item.id]
|
||||||
|
: label;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={item.id}
|
||||||
|
component="form"
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
void this.handleRenameSubmit(item.id, this.state.drafts[item.id] ?? label);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
...tileBaseSx,
|
||||||
|
cursor: 'default',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
my: 0.5,
|
||||||
|
bgcolor: 'action.hover',
|
||||||
|
'&:hover': { bgcolor: 'action.hover' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
variant="standard"
|
||||||
|
value={draftName}
|
||||||
|
onChange={(e) => this.handleDraftChange(item.id, e.target.value)}
|
||||||
|
slotProps={{ inputLabel: { shrink: true } }}
|
||||||
|
sx={{
|
||||||
|
mx: 0.5,
|
||||||
|
'& .MuiInput-underline:before, & .MuiInput-underline:after': {
|
||||||
|
borderBottom: 'none',
|
||||||
|
},
|
||||||
|
'& .MuiInputBase-input': {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
<CategoryTile
|
<CategoryTile
|
||||||
key={item.id}
|
key={item.id}
|
||||||
label={categoryDisplayName(item.name, item.id, rootCategoryId)}
|
label={label}
|
||||||
onClick={() => this.navigateToCategoryById(item.id)}
|
onClick={() => this.navigateToCategoryById(item.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{articles.map((item) => (
|
{!renameMode && articles.map((item) => (
|
||||||
<ArticleTile
|
<ArticleTile
|
||||||
key={item.id}
|
key={item.id}
|
||||||
label={item.name != null ? item.name.trim() : undefined}
|
label={item.name != null ? item.name.trim() : undefined}
|
||||||
@@ -237,10 +321,12 @@ class ArtikelKategorienPage extends Component {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<NewCategoryTile parentId={currentCategoryId} />
|
<NewCategoryTile parentId={currentCategoryId} />
|
||||||
|
{!renameMode && (
|
||||||
<NewArticleTile
|
<NewArticleTile
|
||||||
parentId={currentCategoryId}
|
parentId={currentCategoryId}
|
||||||
onOpenArticle={this.handleArticleClick}
|
onOpenArticle={this.handleArticleClick}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{showArticlesLoading && (
|
{showArticlesLoading && (
|
||||||
@@ -265,12 +351,14 @@ const mapStateToProps = (state) => ({
|
|||||||
childCategories: selectChildCategories(state),
|
childCategories: selectChildCategories(state),
|
||||||
articles: selectCategoryArticles(state),
|
articles: selectCategoryArticles(state),
|
||||||
canGoBack: selectCanNavigateBack(state),
|
canGoBack: selectCanNavigateBack(state),
|
||||||
|
renameMode: selectRenameMode(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
loadCategoryTree,
|
loadCategoryTree,
|
||||||
loadCategoryArticles,
|
loadCategoryArticles,
|
||||||
navigateToCategory,
|
navigateToCategory,
|
||||||
|
renameCategory,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withCategoryRoute(connect(mapStateToProps, mapDispatchToProps)(ArtikelKategorienPage));
|
export default withCategoryRoute(connect(mapStateToProps, mapDispatchToProps)(ArtikelKategorienPage));
|
||||||
|
|||||||
@@ -153,6 +153,21 @@ export const createArticle = createAsyncThunk(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const renameCategory = createAsyncThunk(
|
||||||
|
'category/rename',
|
||||||
|
async ({ categoryId, name }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const trimmed = String(name ?? '').trim();
|
||||||
|
if (!trimmed) throw new Error('Name required');
|
||||||
|
const data = await category.updateName(categoryId, trimmed);
|
||||||
|
requireObject(data.category, 'PATCH /api/category/:id.category');
|
||||||
|
return normalizeCategoryNode(data.category);
|
||||||
|
} catch (err) {
|
||||||
|
return catchBackendError(rejectWithValue)(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const deleteCategory = createAsyncThunk(
|
export const deleteCategory = createAsyncThunk(
|
||||||
'category/delete',
|
'category/delete',
|
||||||
async (categoryId, { rejectWithValue }) => {
|
async (categoryId, { rejectWithValue }) => {
|
||||||
@@ -174,6 +189,7 @@ const initialState = {
|
|||||||
currentCategoryId: null,
|
currentCategoryId: null,
|
||||||
articles: [],
|
articles: [],
|
||||||
articlesTotal: 0,
|
articlesTotal: 0,
|
||||||
|
renameMode: false,
|
||||||
treeStatus: 'idle',
|
treeStatus: 'idle',
|
||||||
articlesStatus: 'idle',
|
articlesStatus: 'idle',
|
||||||
error: null,
|
error: null,
|
||||||
@@ -217,6 +233,9 @@ const categorySlice = createSlice({
|
|||||||
resetCategoryState() {
|
resetCategoryState() {
|
||||||
return initialState;
|
return initialState;
|
||||||
},
|
},
|
||||||
|
setRenameMode(state, action) {
|
||||||
|
state.renameMode = Boolean(action.payload);
|
||||||
|
},
|
||||||
removeArticleBySku(state, action) {
|
removeArticleBySku(state, action) {
|
||||||
const sku = action.payload;
|
const sku = action.payload;
|
||||||
state.articles = state.articles.filter((row) => row.sku !== sku);
|
state.articles = state.articles.filter((row) => row.sku !== sku);
|
||||||
@@ -274,6 +293,22 @@ const categorySlice = createSlice({
|
|||||||
if (!state.categories) state.categories = {};
|
if (!state.categories) state.categories = {};
|
||||||
state.categories[String(action.payload.id)] = action.payload;
|
state.categories[String(action.payload.id)] = action.payload;
|
||||||
})
|
})
|
||||||
|
.addCase(renameCategory.fulfilled, (state, action) => {
|
||||||
|
const { id, name } = action.payload;
|
||||||
|
if (id == null || name == null) return;
|
||||||
|
const key = String(id);
|
||||||
|
const existing = state.categories?.[key];
|
||||||
|
if (existing) {
|
||||||
|
state.categories[key] = { ...existing, name };
|
||||||
|
} else if (state.categories) {
|
||||||
|
state.categories[key] = action.payload;
|
||||||
|
}
|
||||||
|
state.rootArticles = state.rootArticles.map((row) =>
|
||||||
|
row?.categoryId != null && String(row.categoryId) === key
|
||||||
|
? { ...row, name }
|
||||||
|
: row,
|
||||||
|
);
|
||||||
|
})
|
||||||
.addCase(createArticle.fulfilled, (state, action) => {
|
.addCase(createArticle.fulfilled, (state, action) => {
|
||||||
const created = action.payload;
|
const created = action.payload;
|
||||||
if (created == null) return;
|
if (created == null) return;
|
||||||
@@ -313,10 +348,13 @@ export const {
|
|||||||
navigateToCategory,
|
navigateToCategory,
|
||||||
navigateBack,
|
navigateBack,
|
||||||
resetCategoryState,
|
resetCategoryState,
|
||||||
|
setRenameMode,
|
||||||
removeArticleBySku,
|
removeArticleBySku,
|
||||||
patchArticleInLists,
|
patchArticleInLists,
|
||||||
} = categorySlice.actions;
|
} = categorySlice.actions;
|
||||||
|
|
||||||
|
export const selectRenameMode = (state) => state.category.renameMode;
|
||||||
|
|
||||||
export const selectCategoryMap = (state) => state.category.categories;
|
export const selectCategoryMap = (state) => state.category.categories;
|
||||||
export const selectCurrentCategoryId = (state) => state.category.currentCategoryId;
|
export const selectCurrentCategoryId = (state) => state.category.currentCategoryId;
|
||||||
export const selectCategoryRootId = (state) => state.category.rootCategoryId;
|
export const selectCategoryRootId = (state) => state.category.rootCategoryId;
|
||||||
|
|||||||
Reference in New Issue
Block a user