'use client'; 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'; import { CustomModuleFormModal } from './custom-module-form-modal'; import { DeleteCustomModuleDialog } from './delete-custom-module-dialog'; /** Seitenspezifische, bereits uebersetzte Texte der beiden Aufrufer. */ export interface CustomModuleManagerTexts { title: string; description: string; create: string; noModules: string; noModulesBody: string; } interface CustomModuleManagerProps { /** * `shared`: Verwaltung — zeigt und pflegt nur die gemeinsamen Eintraege (fuer * alle Benutzer sichtbar). `personal`: Einstellungen — zeigt und pflegt nur * die eigenen persoenlichen Eintraege des angemeldeten Benutzers. */ scope: 'shared' | 'personal'; texts: CustomModuleManagerTexts; } /** * Liste mit Anlegen, Bearbeiten und Loeschen eigener Module (quick-260929-dzu). * Eine Oberflaeche fuer zwei Aufrufer: `admin/custom-modules` (`scope="shared"`) * und `settings/custom-modules` (`scope="personal"`). Die API liefert gemeinsame * PLUS eigene Eintraege; die Seite filtert ueber `personal` auf ihren Bereich. * Nach jedem erfolgreichen Anlegen, Aendern oder Loeschen zieht die Seitenleiste * ueber `bumpSidebarRefresh` ohne Neuladen nach. */ export function CustomModuleManager({ scope, texts }: CustomModuleManagerProps) { const t = useTranslations('customModules.form'); const tManage = useTranslations('customModules.manage'); const tCommon = useTranslations('common'); const categoryLabel = useCategoryLabel(); const bumpSidebarRefresh = useMarketplaceStore((s) => s.bumpSidebarRefresh); const shared = scope === 'shared'; const [modules, setModules] = useState([]); const [loading, setLoading] = useState(true); const [loadFailed, setLoadFailed] = useState(false); const [showFormModal, setShowFormModal] = useState(false); const [editing, setEditing] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const fetchModules = useCallback(async () => { try { const all = await listCustomModules(); setModules(all.filter((m) => m.personal !== shared)); setLoadFailed(false); } catch { setLoadFailed(true); } finally { setLoading(false); } }, [shared]); useEffect(() => { fetchModules(); }, [fetchModules]); const handleChanged = () => { fetchModules(); bumpSidebarRefresh(); }; const openCreate = () => { setEditing(null); setShowFormModal(true); }; const openEdit = (mod: CustomModule) => { setEditing(mod); setShowFormModal(true); }; return (

{texts.title}

{texts.description}

{loadFailed && (
{tManage('loadError')}
)} {/* Bei einem Ladefehler ohne Eintraege steht nur die Fehlermeldung da — „keine Eintraege“ waere dann falsch. */} {loading ? (

{tCommon('loading')}

) : modules.length === 0 && loadFailed ? null : modules.length === 0 ? (

{texts.noModules}

{texts.noModulesBody}

) : (
{modules.map((mod) => ( ))}
{t('name')} {t('url')} {t('category')} {tManage('actions')}
{mod.name} {mod.url} {categoryLabel(mod.category)}
)} {showFormModal && ( setShowFormModal(false)} onSaved={handleChanged} /> )} {deleteTarget && ( setDeleteTarget(null)} onDeleted={handleChanged} /> )}
); }