feat(F-170): completed feature

This commit is contained in:
chattie
2026-08-22 19:13:31 +02:00
parent 34f36ae253
commit 8f02d1c6cc
12 changed files with 53 additions and 27 deletions

View File

@@ -12,7 +12,7 @@ function slugify(text: string): string {
.replace(/^-+|-+$/g, '');
}
function CategoryRow({ cat, onEdit, onDelete }: { cat: Category; onEdit: (c: Category) => void; onDelete: (id: string) => void }) {
function CategoryRow({ cat, onEdit, onDelete, deleting }: { cat: Category; onEdit: (c: Category) => void; onDelete: (category: Category) => void; deleting: boolean }) {
return (
<tr className="hover:bg-gray-50 transition-colors">
<td className="px-4 py-3">
@@ -45,7 +45,7 @@ function CategoryRow({ cat, onEdit, onDelete }: { cat: Category; onEdit: (c: Cat
<path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
<button onClick={() => onDelete(cat.id)} className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" title="Eliminar">
<button disabled={deleting} onClick={() => onDelete(cat)} className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors disabled:cursor-wait disabled:opacity-40" title="Eliminar">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
@@ -117,6 +117,7 @@ export default function CategoriesPage() {
const [form, setForm] = useState<FormState>(EMPTY_FORM);
const [saving, setSaving] = useState(false);
const [generating, setGenerating] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [msg, setMsg] = useState('');
const load = useCallback(async () => {
@@ -239,13 +240,22 @@ export default function CategoriesPage() {
}
};
const handleDelete = async (id: string) => {
if (!confirm('¿Eliminar esta categoría?')) return;
const handleDelete = async (category: Category) => {
const childCount = category.children?.length ?? 0;
const warning = childCount > 0
? `¿Eliminar “${category.name}” y sus ${childCount} subcategorías? También se retirarán sus asociaciones con productos.`
: `¿Eliminar “${category.name}”? También se retirarán sus asociaciones con productos.`;
if (!confirm(warning)) return;
setDeletingId(category.id);
setMsg('');
try {
await categoriesApi.delete(id);
load();
await categoriesApi.delete(category.id);
setMsg(`Categoría “${category.name}” eliminada`);
await load();
} catch (e) {
alert(e instanceof Error ? e.message : 'Error al eliminar');
setMsg(`Error al eliminar: ${e instanceof Error ? e.message : 'Error desconocido'}`);
} finally {
setDeletingId(null);
}
};
@@ -507,7 +517,7 @@ export default function CategoriesPage() {
</thead>
<tbody className="divide-y divide-gray-50">
{flat(tree).map((c) => (
<CategoryRow key={c.id} cat={c} onEdit={openEdit} onDelete={handleDelete} />
<CategoryRow key={c.id} cat={c} onEdit={openEdit} onDelete={handleDelete} deleting={deletingId === c.id} />
))}
</tbody>
</table>

View File

@@ -36,6 +36,7 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
);
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}