Web: Unterkategorien umsortieren (uebergeordnete Kategorie aenderbar)
In der Kategorien-Verwaltung gibt es je Zeile eine Spalte "Uebergeordnet" mit Auswahl - so laesst sich eine (Unter-)Kategorie unter eine andere haengen oder auf die oberste Ebene holen. Zielauswahl auf denselben Typ beschraenkt und um die eigenen Unterkategorien bereinigt; das Backend verhindert Ringe zusaetzlich. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,19 @@ export default function Categories() {
|
||||
const nameById = Object.fromEntries(categories.map((c) => [c.id, c.name]));
|
||||
const parentOf = Object.fromEntries(categories.map((c) => [c.id, c.parent_id]));
|
||||
const childCount = (id) => categories.filter((c) => c.parent_id === id).length;
|
||||
// Eigene Unterkategorien (rekursiv) – als Ziel beim Verschieben ausgeschlossen,
|
||||
// sonst entstünde ein Ring.
|
||||
function descendantIds(id) {
|
||||
const result = new Set();
|
||||
const stack = [id];
|
||||
while (stack.length) {
|
||||
const cur = stack.pop();
|
||||
for (const kid of categories.filter((c) => c.parent_id === cur)) {
|
||||
if (!result.has(kid.id)) { result.add(kid.id); stack.push(kid.id); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function versteckt(id) {
|
||||
let p = parentOf[id];
|
||||
while (p != null) {
|
||||
@@ -135,6 +148,7 @@ export default function Categories() {
|
||||
<tr>
|
||||
<th>Kategorie</th>
|
||||
<th>Art</th>
|
||||
<th>Übergeordnet</th>
|
||||
<th className="num">Artikel</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -143,6 +157,12 @@ export default function Categories() {
|
||||
{sichtbar.map((c) => {
|
||||
const kinder = childCount(c.id);
|
||||
const zu = collapsed.has(c.id);
|
||||
// Mögliche neue Elternkategorien: gleicher Typ, nicht sie selbst
|
||||
// und keine ihrer Unterkategorien.
|
||||
const desc = descendantIds(c.id);
|
||||
const parentOptions = tree.filter(
|
||||
(o) => o.tracking === c.tracking && o.id !== c.id && !desc.has(o.id),
|
||||
);
|
||||
return (
|
||||
<tr key={c.id} className={feldKat && feldKat.id === c.id ? "row-active" : ""}>
|
||||
<td data-label="Kategorie">
|
||||
@@ -164,9 +184,6 @@ export default function Categories() {
|
||||
{zu && kinder > 0 && (
|
||||
<span className="badge nowrap">{kinder} ausgeblendet</span>
|
||||
)}
|
||||
{c.depth > 0 && c.parent_id && nameById[c.parent_id] && (
|
||||
<span className="badge nowrap">in {nameById[c.parent_id]}</span>
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
<td data-label="Art">
|
||||
@@ -180,6 +197,21 @@ export default function Categories() {
|
||||
<span className="badge">{MODUS[c.tracking] || c.tracking}</span>
|
||||
)}
|
||||
</td>
|
||||
<td data-label="Übergeordnet">
|
||||
{isAdmin ? (
|
||||
<select value={c.parent_id ?? ""} style={{ marginTop: 0, minWidth: 150 }}
|
||||
onChange={(e) => patch(c, {
|
||||
parent_id: e.target.value === "" ? null : Number(e.target.value),
|
||||
})}>
|
||||
<option value="">– oberste Ebene –</option>
|
||||
{parentOptions.map((o) => (
|
||||
<option key={o.id} value={o.id}>{"— ".repeat(o.depth)}{o.name}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<span className="muted">{c.parent_id ? nameById[c.parent_id] : "–"}</span>
|
||||
)}
|
||||
</td>
|
||||
<td data-label="Artikel" className="num muted">{c.product_count}</td>
|
||||
<td className="num">
|
||||
<button className="btn-icon" title="Felder verwalten"
|
||||
@@ -196,7 +228,7 @@ export default function Categories() {
|
||||
);
|
||||
})}
|
||||
{categories.length === 0 && (
|
||||
<tr><td colSpan={4} className="empty">Noch keine Kategorien.</td></tr>
|
||||
<tr><td colSpan={5} className="empty">Noch keine Kategorien.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user