Files
enkelbudget/client/src/components/CategorySettingsSection.jsx
T
michaelswanson 860d5f55cc Initial commit
2026-06-25 19:58:40 +00:00

149 lines
5.7 KiB
React
Executable File

import { useState } from 'react';
import { FolderCog, Pencil, Plus, Trash2 } from 'lucide-react';
import Modal from './Modal.jsx';
const PRESET_COLORS = ['#7C3AED', '#2563EB', '#D97706', '#059669', '#DC2626', '#0F766E', '#9333EA', '#475569'];
function CategoryForm({ initial, suggestedSortOrder, onSave, onClose }) {
const [name, setName] = useState(initial?.name ?? '');
const [color, setColor] = useState(initial?.color ?? PRESET_COLORS[0]);
const [sortOrder, setSortOrder] = useState(initial?.sort_order ?? suggestedSortOrder ?? 1);
function submit(e) {
e.preventDefault();
if (!name.trim()) return;
onSave({
name: name.trim(),
color,
sort_order: parseInt(sortOrder) || suggestedSortOrder || 1,
});
onClose();
}
return (
<form onSubmit={submit} className="space-y-3">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Namn</label>
<input
className="w-full border border-slate-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
value={name}
onChange={e => setName(e.target.value)}
placeholder="t.ex. Försäkringar"
required
autoFocus
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Färg</label>
<div className="flex flex-wrap gap-2">
{PRESET_COLORS.map(option => (
<button
key={option}
type="button"
onClick={() => setColor(option)}
className={`w-8 h-8 rounded-full border-2 ${color === option ? 'border-slate-800' : 'border-transparent'}`}
style={{ backgroundColor: option }}
aria-label={`Välj färg ${option}`}
/>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Sorteringsordning</label>
<input
type="number"
min="1"
className="w-full border border-slate-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
value={sortOrder}
onChange={e => setSortOrder(e.target.value)}
/>
</div>
<div className="flex gap-2 pt-1">
<button type="button" onClick={onClose} className="flex-1 py-2.5 rounded-xl border border-slate-200 text-sm font-medium text-slate-600 hover:bg-slate-50">
Avbryt
</button>
<button type="submit" className="flex-1 py-2.5 rounded-xl bg-blue-600 text-white text-sm font-medium hover:bg-blue-700">
Spara
</button>
</div>
</form>
);
}
export default function CategorySettingsSection({ categories, onAdd, onUpdate, onDelete, deleteError }) {
const [modal, setModal] = useState(null);
const suggestedSortOrder = (categories.at(-1)?.sort_order ?? 0) + 1;
return (
<section className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden mb-3">
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100">
<div className="flex items-center gap-2">
<FolderCog size={16} className="text-emerald-600" />
<span className="font-semibold text-slate-700 text-sm uppercase tracking-wide">Kategorier</span>
</div>
<button
onClick={() => setModal('add')}
className="flex items-center gap-1 text-blue-600 text-xs font-medium hover:text-blue-700"
>
<Plus size={14} /> Lägg till
</button>
</div>
<div className="px-4 py-3 border-b border-slate-100 bg-emerald-50/70 text-sm text-slate-600">
Här skapar och ändrar du kategorierna som används i budgeten, transaktionerna och AI-förslagen. Byter du namn eller färg här följer det med i hela appen.
</div>
{deleteError && (
<div className="mx-4 mt-4 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
{deleteError}
</div>
)}
<div className="divide-y divide-slate-50">
{categories.map(category => (
<div key={category.id} className="flex items-center gap-3 px-4 py-3 group">
<span className="w-3 h-3 rounded-full shrink-0" style={{ backgroundColor: category.color }} />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-slate-800">{category.name}</div>
<div className="text-xs text-slate-500">Ordning {category.sort_order}</div>
</div>
<div className="flex gap-1 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity shrink-0">
<button onClick={() => setModal({ edit: category })} className="p-1 rounded hover:bg-slate-100 text-slate-400">
<Pencil size={13} />
</button>
<button onClick={() => onDelete(category.id)} className="p-1 rounded hover:bg-red-50 text-slate-400 hover:text-red-500">
<Trash2 size={13} />
</button>
</div>
</div>
))}
</div>
{modal === 'add' && (
<Modal title="Lägg till kategori" onClose={() => setModal(null)}>
<CategoryForm
suggestedSortOrder={suggestedSortOrder}
onSave={onAdd}
onClose={() => setModal(null)}
/>
</Modal>
)}
{modal?.edit && (
<Modal title="Redigera kategori" onClose={() => setModal(null)}>
<CategoryForm
initial={modal.edit}
suggestedSortOrder={modal.edit.sort_order}
onSave={data => onUpdate(modal.edit.id, data)}
onClose={() => setModal(null)}
/>
</Modal>
)}
</section>
);
}