fix(custom-modules): null beim Aendern ablehnen, Ladefehler statt 404, Fehlertexte, Seitenleiste eingeklappt
- PATCH mit null fuer name/url/category ergibt 400 statt 500 - Modulansicht unterscheidet Ladefehler von "nicht gefunden" - Formular/Loeschdialog nennen 403 und 400 eigens - eingeklappte Seitenleiste folgt der Gruppenreihenfolge der ausgeklappten - neue Eintraege sind mit "Eigene Module" vorbelegt - Verwaltung zeigt bei Ladefehler nicht zusaetzlich "keine Eintraege" Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { CUSTOM_MODULE_CATEGORIES, CUSTOM_MODULE_CATEGORY } from '@tessera/shared';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { CUSTOM_MODULE_CATEGORIES } from '@tessera/shared';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
type CustomModule,
|
||||
CustomModuleRequestError,
|
||||
checkCustomModuleUrl,
|
||||
createCustomModule,
|
||||
updateCustomModule,
|
||||
@@ -27,6 +28,23 @@ interface CustomModuleFormModalProps {
|
||||
const NAME_MAX = 100;
|
||||
const URL_MAX = 2048;
|
||||
|
||||
/**
|
||||
* Fehlertext fuer Speichern und Loeschen: 403 = keine Berechtigung, 400 = die
|
||||
* API hat die Angaben abgelehnt (mit ihrer Meldung), alles andere (500,
|
||||
* Netzwerk) = der allgemeine „bitte erneut versuchen“-Text `fallbackKey`.
|
||||
*/
|
||||
export function customModuleErrorText(
|
||||
err: unknown,
|
||||
t: (key: string, values?: Record<string, string>) => string,
|
||||
fallbackKey: 'saveError' | 'deleteError',
|
||||
): string {
|
||||
if (err instanceof CustomModuleRequestError) {
|
||||
if (err.status === 403) return t('forbidden');
|
||||
if (err.status === 400) return t('invalidInput');
|
||||
}
|
||||
return t(fallbackKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formular „Eigenes Modul“ (quick-260929-9wc, Vorbild `GroupFormModal`;
|
||||
* quick-260929-dzu: ein Formular, zwei Aufrufer — Verwaltung legt gemeinsame,
|
||||
@@ -47,7 +65,9 @@ export function CustomModuleFormModal({
|
||||
|
||||
const [name, setName] = useState(mod?.name ?? '');
|
||||
const [url, setUrl] = useState(mod?.url ?? '');
|
||||
const [category, setCategory] = useState<string>(mod?.category ?? 'infrastructure');
|
||||
// Neue Eintraege landen vorbelegt in der Gruppe „Eigene Module“; beim
|
||||
// Bearbeiten bleibt die gespeicherte Kategorie.
|
||||
const [category, setCategory] = useState<string>(mod?.category ?? CUSTOM_MODULE_CATEGORY);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -81,8 +101,8 @@ export function CustomModuleFormModal({
|
||||
}
|
||||
onSaved();
|
||||
onClose();
|
||||
} catch {
|
||||
setError(t('saveError'));
|
||||
} catch (err) {
|
||||
setError(customModuleErrorText(err, t, 'saveError'));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { type CustomModule, listCustomModules } from '@/lib/custom-modules-api';
|
||||
import { useMarketplaceStore } from '@/lib/stores/marketplace-store';
|
||||
import { useCategoryLabel } from '@/lib/use-category-label';
|
||||
@@ -86,9 +86,7 @@ export function CustomModuleManager({ scope, texts }: CustomModuleManagerProps)
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1
|
||||
className={`${shared ? 'text-2xl' : 'text-lg'} font-semibold text-foreground`}
|
||||
>
|
||||
<h1 className={`${shared ? 'text-2xl' : 'text-lg'} font-semibold text-foreground`}>
|
||||
{texts.title}
|
||||
</h1>
|
||||
<button type="button" onClick={openCreate} className="btn btn-primary">
|
||||
@@ -104,9 +102,11 @@ export function CustomModuleManager({ scope, texts }: CustomModuleManagerProps)
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bei einem Ladefehler ohne Eintraege steht nur die Fehlermeldung da —
|
||||
„keine Eintraege“ waere dann falsch. */}
|
||||
{loading ? (
|
||||
<p className="text-muted-foreground">{tCommon('loading')}</p>
|
||||
) : modules.length === 0 ? (
|
||||
) : modules.length === 0 && loadFailed ? null : modules.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<h2 className="mb-2 text-lg font-semibold text-foreground">{texts.noModules}</h2>
|
||||
<p className="mb-6 text-sm text-muted-foreground">{texts.noModulesBody}</p>
|
||||
@@ -119,9 +119,15 @@ export function CustomModuleManager({ scope, texts }: CustomModuleManagerProps)
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">{t('name')}</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">{t('url')}</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">{t('category')}</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">
|
||||
{t('name')}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">
|
||||
{t('url')}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-muted-foreground">
|
||||
{t('category')}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-muted-foreground">
|
||||
{tManage('actions')}
|
||||
</th>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { type CustomModule, deleteCustomModule } from '@/lib/custom-modules-api';
|
||||
import { customModuleErrorText } from './custom-module-form-modal';
|
||||
|
||||
interface DeleteCustomModuleDialogProps {
|
||||
mod: CustomModule;
|
||||
@@ -37,8 +38,8 @@ export function DeleteCustomModuleDialog({
|
||||
await deleteCustomModule(mod.id);
|
||||
onDeleted();
|
||||
onClose();
|
||||
} catch {
|
||||
setError(t('deleteError'));
|
||||
} catch (err) {
|
||||
setError(customModuleErrorText(err, t, 'deleteError'));
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
@@ -55,7 +56,11 @@ export function DeleteCustomModuleDialog({
|
||||
<h2 id="delete-custom-module-title" className="mb-4 text-lg font-semibold text-foreground">
|
||||
{t('deleteConfirm.title')}
|
||||
</h2>
|
||||
<p className="mb-4 text-sm text-foreground">{t(shared ? 'deleteConfirm.bodyShared' : 'deleteConfirm.bodyPersonal', { name: mod.name })}</p>
|
||||
<p className="mb-4 text-sm text-foreground">
|
||||
{t(shared ? 'deleteConfirm.bodyShared' : 'deleteConfirm.bodyPersonal', {
|
||||
name: mod.name,
|
||||
})}
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user