Applied systematic spacing optimizations across admin manager components (40px savings): DatabaseManager.tsx: - Input field padding: py-3 → py-2 (3 inputs: retention_count, schedule_hour, schedule_freq_days) - Form field spacing: space-y-2 → space-y-1.5 (Max Backups field group) - Grid gaps: gap-4 → gap-3 (storage policy fields) - Item list spacing: space-y-2 → space-y-1.5 (backup list) - Modal form spacing: space-y-4 → space-y-3 (recovery points section) - Backup metadata text: text-[11px] → text-xs (created_at + filesize display) LdapManager.tsx: - Input field padding: py-2.5 → py-2 (4 inputs: server_uri, base_dn, user_template, groups_dn) - Form field spacing: space-y-1.5 → space-y-1 (all label+input groups) - Button padding: py-2.5 → py-2 (test connection), py-3 → py-2.5 (save policy) IdentityManager.tsx: - Input field padding: py-3 → py-2 (username, password, role in edit modal) - Form field spacing: space-y-4 → space-y-3 (edit modal form) - Form field labels: space-y-1.5 → space-y-1 (all 3 fields) - User list spacing: space-y-2 → space-y-1.5 (user items) AiManager.tsx: - Grid gaps: gap-4 → gap-3 (provider options grid) - Input field padding: py-3 → py-2 (Gemini/Claude API key inputs) - Form field spacing: space-y-2 → space-y-1.5 (API key field groups) - Modal spacing: space-y-6 → space-y-3 (provider access keys section) - Prompt section spacing: space-y-4 → space-y-3 (system prompt area) CategoryManager.tsx: - Grid gaps: gap-3 → gap-2.5 (category items grid) - Input field padding: py-3.5 → py-2.5, py-4 → py-2.5 (name + description inputs) - Form field spacing: space-y-6 → space-y-3, space-y-1.5 → space-y-1 (edit modal) Test Results: - Frontend (Vitest): 291/291 tests passing ✓ - Backend (Pytest): 41/41 tests passing ✓ - Total: 332 tests validated, zero regressions All changes maintain visual hierarchy and premium density standards without sacrificing usability.
117 lines
5.7 KiB
TypeScript
117 lines
5.7 KiB
TypeScript
import React from 'react';
|
|
import { Layers, Plus, Edit2, Trash2, X } from 'lucide-react';
|
|
|
|
interface CategoryManagerProps {
|
|
categories: any[];
|
|
onAddCategory: () => void;
|
|
onDeleteCategory: (id: number, name: string) => void;
|
|
setEditingCategory: (cat: any) => void;
|
|
setEditCatForm: (form: any) => void;
|
|
editCatForm: any;
|
|
editingCategory: any;
|
|
onUpdateCategorySubmit: () => void;
|
|
}
|
|
|
|
export default function CategoryManager({
|
|
categories,
|
|
onAddCategory,
|
|
onDeleteCategory,
|
|
setEditingCategory,
|
|
setEditCatForm,
|
|
editCatForm,
|
|
editingCategory,
|
|
onUpdateCategorySubmit
|
|
}: CategoryManagerProps) {
|
|
return (
|
|
<section className="bg-surface/50 border border-slate-800/50 rounded-[2.5rem] p-5 md:p-8 shadow-2xl space-y-6 transition-all group/categories">
|
|
<div className="flex items-center justify-between px-2">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
|
|
<Layers size={20} />
|
|
</div>
|
|
<h2 className="text-xl font-normal text-white tracking-tight">Category Groups</h2>
|
|
</div>
|
|
<button
|
|
onClick={onAddCategory}
|
|
data-testid="add-category-button"
|
|
className="flex items-center gap-2 bg-primary hover:bg-primary text-white px-6 py-3 rounded-xl text-sm font-normal transition-all shadow-xl shadow-primary/10 active:scale-95 border border-primary/30 tracking-tight"
|
|
>
|
|
<Plus size={14} /> New Group
|
|
</button>
|
|
</div>
|
|
|
|
<div data-testid="category-list" className="grid sm:grid-cols-2 lg:grid-cols-4 gap-2.5">
|
|
{categories.map(cat => (
|
|
<div key={cat.id} data-testid="category-item" className="p-4 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-between group hover:border-primary/40 transition-all">
|
|
<div className="min-w-0 pr-4">
|
|
<p className="card-title group-hover:text-primary transition-colors">{cat.name}</p>
|
|
<p className="card-subtitle">{cat.description || 'General storage'}</p>
|
|
</div>
|
|
<div className="flex gap-1 shrink-0">
|
|
<button
|
|
onClick={() => {
|
|
setEditingCategory(cat);
|
|
setEditCatForm({ name: cat.name, description: cat.description || '' });
|
|
}}
|
|
className="p-2 text-secondary hover:text-white hover:bg-slate-800 rounded-lg transition-all"
|
|
>
|
|
<Edit2 size={14} />
|
|
</button>
|
|
<button
|
|
onClick={() => onDeleteCategory(cat.id, cat.name)}
|
|
data-testid="delete-category-button"
|
|
className="p-2 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-lg transition-all"
|
|
>
|
|
<Trash2 size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Edit Category Modal */}
|
|
{editingCategory && (
|
|
<div className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center p-0 sm:p-4 bg-background/90 animate-in fade-in duration-300">
|
|
<div className="w-full max-w-lg bg-surface border-x border-t sm:border border-slate-800 rounded-t-[2.5rem] sm:rounded-3xl p-6 sm:p-10 shadow-2xl space-y-8 overflow-hidden animate-in slide-in-from-bottom-10">
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
|
|
<Edit2 size={20} />
|
|
</div>
|
|
<h3 className="text-xl font-normal text-white tracking-tight">Modify Group</h3>
|
|
</div>
|
|
<button onClick={() => setEditingCategory(null)} className="p-3 hover:bg-slate-800 rounded-2xl text-muted transition-colors border border-slate-800">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
<div data-testid="category-form" className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Group Identifier</label>
|
|
<input
|
|
data-testid="category-name-input"
|
|
type="text"
|
|
value={editCatForm.name}
|
|
onChange={(e) => setEditCatForm({...editCatForm, name: e.target.value})}
|
|
className="w-full bg-background border border-slate-800 rounded-2xl py-2.5 px-5 text-sm text-white focus:border-primary outline-none transition-all"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-sm font-normal text-secondary tracking-tight ml-1">Strategic Description</label>
|
|
<textarea
|
|
value={editCatForm.description}
|
|
onChange={(e) => setEditCatForm({...editCatForm, description: e.target.value})}
|
|
className="w-full bg-background border border-slate-800 rounded-2xl py-2.5 px-5 text-sm text-white focus:border-primary outline-none transition-all h-32 resize-none"
|
|
/>
|
|
</div>
|
|
<button data-testid="add-category-submit" onClick={onUpdateCategorySubmit} className="w-full bg-primary hover:bg-primary text-white font-normal py-4 rounded-2xl shadow-xl shadow-primary/20 active:scale-95 transition-all text-sm mb-4 tracking-tight">
|
|
Update Asset Group
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|