feat(quick-261003-387): Verwaltungsseite Kategorien, Marktplatz-Reihenfolge, Formular, Texte, CHANGELOG und Handbuch

- Administrator -> Module -> Kategorien: anlegen, umbenennen, sortieren, Eintraege zuordnen und sortieren, loeschen mit Zielauswahl
- Marktplatz-Filter folgen der Kategorienreihenfolge, Kategorieseite und Modulliste zeigen gespeicherte Namen
- Formular fuer eigene Module bietet alle Kategorien der Organisation an
- Texte de/en, CHANGELOG, Handbuch Administration und Anwender

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 02:48:04 +02:00
parent af1878d5ee
commit f2c0a896e8
16 changed files with 1173 additions and 14 deletions
@@ -2,7 +2,7 @@
import { CUSTOM_MODULE_CATEGORIES, CUSTOM_MODULE_CATEGORY } from '@tessera/shared';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import {
type CustomModule,
CustomModuleRequestError,
@@ -11,6 +11,7 @@ import {
updateCustomModule,
} from '@/lib/custom-modules-api';
import { useCustomModuleCacheStore } from '@/lib/stores/custom-module-cache-store';
import { useModuleCategoryStore } from '@/lib/stores/module-category-store';
import { useCategoryLabel } from '@/lib/use-category-label';
interface CustomModuleFormModalProps {
@@ -63,6 +64,8 @@ export function CustomModuleFormModal({
const t = useTranslations('customModules.form');
const tCommon = useTranslations('common');
const categoryLabel = useCategoryLabel();
const storeCategories = useModuleCategoryStore((s) => s.categories);
const ensureCategories = useModuleCategoryStore((s) => s.ensureLoaded);
const [name, setName] = useState(mod?.name ?? '');
const [url, setUrl] = useState(mod?.url ?? '');
@@ -72,6 +75,20 @@ export function CustomModuleFormModal({
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
// Alle Kategorien der Organisation in der eingestellten Reihenfolge
// (quick-261003-387). Solange der Speicher leer ist, gilt die Standardliste;
// ein vorhandener Wert, der (noch) nicht im Speicher steht, bleibt Option.
useEffect(() => {
ensureCategories();
}, [ensureCategories]);
const categoryOptions = useMemo(() => {
const keys: string[] =
storeCategories.length > 0
? storeCategories.map((c) => c.key)
: [...CUSTOM_MODULE_CATEGORIES];
return keys.includes(category) ? keys : [...keys, category];
}, [storeCategories, category]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
@@ -167,7 +184,7 @@ export function CustomModuleFormModal({
onChange={(e) => setCategory(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
>
{CUSTOM_MODULE_CATEGORIES.map((c) => (
{categoryOptions.map((c) => (
<option key={c} value={c}>
{categoryLabel(c)}
</option>