profit-planet-frontend/src/app/admin/language-management/components/CategoryManagerModal.tsx
DeathKaioken 4074ea4eee Bibelbumser
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 23:48:09 +02:00

236 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useTranslation } from '../../../i18n/useTranslation';
import type { NamespaceCategory } from '../hooks/useNamespaceCategories';
import { useModalAnimation } from '../hooks/useModalAnimation';
type Props = {
isOpen: boolean;
onClose: () => void;
newCategoryLabel: string;
setNewCategoryLabel: (value: string) => void;
onCreateCategory: () => void;
uncategorizedNamespaces: string[];
categoriesWithKnownNamespaces: NamespaceCategory[];
namespaces: string[];
assignNamespaceByCategory: Record<string, string>;
setAssignNamespaceByCategory: (next: Record<string, string> | ((prev: Record<string, string>) => Record<string, string>)) => void;
expandedCategoryId: string | null;
setExpandedCategoryId: (value: string | null | ((prev: string | null) => string | null)) => void;
dragNamespace: string | null;
setDragNamespace: (value: string | null) => void;
addNamespaceToCategory: (categoryId: string, namespace: string) => void;
removeNamespaceFromCategory: (categoryId: string, namespace: string) => void;
deleteCategory: (categoryId: string) => void;
};
export default function CategoryManagerModal({
isOpen,
onClose,
newCategoryLabel,
setNewCategoryLabel,
onCreateCategory,
uncategorizedNamespaces,
categoriesWithKnownNamespaces,
namespaces,
assignNamespaceByCategory,
setAssignNamespaceByCategory,
expandedCategoryId,
setExpandedCategoryId,
dragNamespace,
setDragNamespace,
addNamespaceToCategory,
removeNamespaceFromCategory,
deleteCategory,
}: Props) {
const { t } = useTranslation();
const { isRendered, isVisible } = useModalAnimation(isOpen);
if (!isRendered) return null;
return (
<div className={`fixed inset-0 z-[150] flex items-center justify-center bg-black/30 backdrop-blur-md transition-opacity duration-200 ${
isVisible ? 'opacity-100' : 'opacity-0'
}`}>
<div className={`mx-4 w-full max-w-5xl rounded-[30px] border border-white/80 bg-white/88 backdrop-blur overflow-hidden shadow-[0_32px_80px_-40px_rgba(15,23,42,0.42)] transform transition-all duration-200 ${
isVisible ? 'opacity-100 translate-y-0 scale-100' : 'opacity-0 translate-y-2 scale-[0.98]'
}`}>
{/* Header */}
<div className="px-6 py-5 border-b border-white/60 flex items-start justify-between gap-4 bg-white/40">
<div>
<span className="inline-flex items-center rounded-full border border-slate-200 bg-white px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-500 shadow-sm mb-2">
Admin
</span>
<h2 className="text-xl font-black tracking-tight text-slate-950">{t('autofix.kef9de7f0')}</h2>
<p className="text-xs text-slate-500 mt-0.5">{t('autofix.kc4671abe')}</p>
</div>
<button
onClick={onClose}
className="rounded-xl border border-slate-200 bg-white/80 px-2.5 py-1.5 text-slate-400 hover:text-slate-700 hover:bg-white transition shadow-sm text-base leading-none shrink-0"
>
</button>
</div>
{/* Body */}
<div className="px-6 py-5 max-h-[70vh] overflow-y-auto space-y-4">
{/* Create category row */}
<div className="flex items-center gap-2 flex-wrap">
<input
value={newCategoryLabel}
onChange={(e) => setNewCategoryLabel(e.target.value)}
placeholder={t('autofix.ke52ed6e9')}
className="w-56 rounded-2xl border border-slate-200 bg-white px-4 py-2 text-sm shadow-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900/20 transition"
/>
<button
type="button"
onClick={onCreateCategory}
className="rounded-2xl bg-slate-900 text-white px-4 py-2 text-sm font-semibold shadow-[0_18px_40px_-24px_rgba(15,23,42,0.85)] hover:bg-slate-800 transition"
>
{t('autofix.k1db86f96')}
</button>
</div>
<div className="space-y-3">
{/* Uncategorized pool */}
<div className="rounded-[20px] border border-white/80 bg-white/70 backdrop-blur p-4 shadow-sm">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500 mb-2.5">{t('autofix.k505ebdae')}</p>
<div className="flex flex-wrap gap-2 min-h-10">
{uncategorizedNamespaces.map((ns) => (
<span
key={ns}
draggable
onDragStart={() => setDragNamespace(ns)}
className="cursor-grab rounded-full border border-slate-200 bg-white px-3 py-1 text-xs font-medium text-slate-700 shadow-sm hover:border-slate-300 hover:bg-slate-50 transition"
title={t('autofix.k66edf1eb')}
>
{ns}
</span>
))}
{uncategorizedNamespaces.length === 0 && (
<span className="text-xs text-slate-400">{t('autofix.k741a01f7')}</span>
)}
</div>
</div>
{/* Category list */}
<div className="space-y-2">
{categoriesWithKnownNamespaces.map((cat) => {
const availableToAssign = namespaces.filter((ns) => !cat.namespaces.includes(ns));
const selectValue = assignNamespaceByCategory[cat.id] ?? '';
const isExpanded = expandedCategoryId === cat.id;
return (
<div
key={cat.id}
onDragEnter={() => {
if (!dragNamespace) return;
if (expandedCategoryId !== cat.id) setExpandedCategoryId(cat.id);
}}
onDragOver={(e) => e.preventDefault()}
onDrop={() => {
if (dragNamespace) {
addNamespaceToCategory(cat.id, dragNamespace);
setDragNamespace(null);
}
}}
className="rounded-[20px] border border-white/80 bg-white/70 backdrop-blur overflow-hidden shadow-sm"
>
{/* Category header row */}
<div
className="px-4 py-3 flex items-center justify-between gap-2 cursor-pointer hover:bg-white/60 transition"
onClick={() => setExpandedCategoryId((prev) => (prev === cat.id ? null : cat.id))}
>
<div className="flex items-center gap-2">
<span className="text-sm font-bold text-slate-950">{cat.label}</span>
<span className="rounded-full border border-slate-200 bg-white px-2 py-0.5 text-[10px] font-semibold text-slate-500 shadow-sm">
{cat.namespaces.length}
</span>
<span className="text-xs text-slate-400">{isExpanded ? t('autofix.k5daa1471') : t('autofix.k893106ba')}</span>
</div>
{cat.isCustom && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
deleteCategory(cat.id);
}}
className="rounded-2xl border border-red-200 bg-red-50 px-3 py-1 text-xs font-medium text-red-600 hover:bg-red-100 transition"
>
Delete
</button>
)}
</div>
{isExpanded && (
<div className="border-t border-white/60 p-4 space-y-3 bg-white/40">
<div className="flex flex-col lg:flex-row items-stretch gap-2">
<select
value={selectValue}
onChange={(e) => setAssignNamespaceByCategory((prev) => ({ ...prev, [cat.id]: e.target.value }))}
className="w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-xs shadow-sm focus:outline-none focus:ring-2 focus:ring-slate-900/20 transition"
>
<option value="">{t('autofix.k0cdc3ee9')}</option>
{availableToAssign.map((ns) => (
<option key={ns} value={ns}>{ns}</option>
))}
</select>
<button
type="button"
onClick={() => {
if (!selectValue) return;
addNamespaceToCategory(cat.id, selectValue);
setAssignNamespaceByCategory((prev) => ({ ...prev, [cat.id]: '' }));
}}
className="rounded-2xl border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50 transition"
>
Add
</button>
</div>
<div className="flex flex-wrap gap-2 min-h-10 rounded-2xl border border-dashed border-slate-200 bg-white/60 p-3">
{cat.namespaces.map((ns) => (
<span
key={ns}
draggable
onDragStart={() => setDragNamespace(ns)}
className="group cursor-grab inline-flex items-center rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-medium text-indigo-700 hover:border-indigo-300 transition"
>
{ns}
<button
type="button"
onClick={() => removeNamespaceFromCategory(cat.id, ns)}
className="ml-1.5 text-indigo-400 group-hover:text-red-500 transition leading-none"
title={t('autofix.ka6791a02')}
>
×
</button>
</span>
))}
{cat.namespaces.length === 0 && (
<span className="text-xs text-slate-400">{t('autofix.kf3c3223a')}</span>
)}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-white/60 bg-white/40 flex justify-end">
<button
onClick={onClose}
className="rounded-2xl bg-slate-900 text-white px-5 py-2 text-sm font-semibold shadow-[0_18px_40px_-24px_rgba(15,23,42,0.85)] hover:bg-slate-800 transition"
>
Done
</button>
</div>
</div>
</div>
);
}