feat(F-170): completed feature
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user