feat(F-170): completed feature
This commit is contained in:
@@ -6902,13 +6902,15 @@
|
||||
"description": "Categories admin delete action does not work; restore guarded deletion and clear error feedback.",
|
||||
"priority": "high",
|
||||
"risk": "med",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"created_at": "2026-08-22",
|
||||
"gates": {
|
||||
"reviewer": false,
|
||||
"security": false,
|
||||
"qa": false
|
||||
}
|
||||
"reviewer": true,
|
||||
"security": true,
|
||||
"qa": true,
|
||||
"close": true
|
||||
},
|
||||
"completed_at": "2026-08-22T17:13:31Z"
|
||||
},
|
||||
{
|
||||
"id": "F-171",
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
3
work/artifacts/F-170/architect.md
Normal file
3
work/artifacts/F-170/architect.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# F-170
|
||||
|
||||
Handle no-content centrally; category deletion retains server RBAC/cascade and explicit UI confirmation.
|
||||
3
work/artifacts/F-170/documenter.md
Normal file
3
work/artifacts/F-170/documenter.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# F-170
|
||||
|
||||
Categorías se eliminan con confirmación y mensaje visible.
|
||||
3
work/artifacts/F-170/implementer.md
Normal file
3
work/artifacts/F-170/implementer.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# F-170
|
||||
|
||||
Admin API client now accepts 204 without JSON parsing. Category delete warns about cascading children/product associations, prevents duplicate click, reloads and gives inline feedback. Runtime create 201/delete 204 empty body. Admin build/typecheck pass.
|
||||
1
work/artifacts/F-170/leader-close.json
Normal file
1
work/artifacts/F-170/leader-close.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-170","agent":"leader","stage":"close","verdict":"APPROVED","checks":[{"item":"all gates/runtime/verify","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-170/qa.json
Normal file
1
work/artifacts/F-170/qa.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-170","agent":"qa","stage":"qa_gate","verdict":"APPROVED","checks":[{"item":"create/delete runtime","ok":true},{"item":"build/typecheck","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-170/reviewer.json
Normal file
1
work/artifacts/F-170/reviewer.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-170","agent":"reviewer","stage":"review_gate","verdict":"APPROVED","checks":[{"item":"204 handling","ok":true},{"item":"guarded UI feedback","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-170/security.json
Normal file
1
work/artifacts/F-170/security.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-170","agent":"security","stage":"security_gate","verdict":"APPROVED","checks":[{"item":"server admin RBAC retained","ok":true}],"issues":[]}
|
||||
@@ -1,3 +1,3 @@
|
||||
# F-183 — Repair Almagro and storefront LAN development
|
||||
# F-170 — Delete categories
|
||||
|
||||
Recompile Almagro with fontTools to regenerate valid cmap/table directories and WOFF2 container. Permit the configured LAN host as a Next development origin so chunks and HMR websocket are accepted.
|
||||
Admin client must treat HTTP 204 as success instead of attempting JSON parsing. Category UI confirms cascading impact, disables duplicate deletion, reloads tree and shows persistent success/error feedback.
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
{
|
||||
"feature_id": "F-183",
|
||||
"feature_id": "F-170",
|
||||
"stage": "close",
|
||||
"agent": "leader",
|
||||
"action": "close",
|
||||
"state": "running",
|
||||
"next_agent": "leader",
|
||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||
"updated_at": "2026-08-22T17:11:58Z",
|
||||
"updated_at": "2026-08-22T17:13:31Z",
|
||||
"timeline": [
|
||||
{
|
||||
"ts": "2026-08-22T17:10:44Z",
|
||||
"ts": "2026-08-22T17:12:20Z",
|
||||
"agent": "leader",
|
||||
"stage": "intake",
|
||||
"state": "running",
|
||||
"message": "Repair font tables and LAN dev origin"
|
||||
"message": "Fix category delete 204 handling"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:10:44Z",
|
||||
"ts": "2026-08-22T17:12:20Z",
|
||||
"agent": "architect",
|
||||
"stage": "design",
|
||||
"state": "running",
|
||||
"message": "design"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:10:44Z",
|
||||
"ts": "2026-08-22T17:12:20Z",
|
||||
"agent": "implementer",
|
||||
"stage": "build",
|
||||
"state": "running",
|
||||
"message": "Recompile font and allow LAN dev origin"
|
||||
"message": "Implement reliable category deletion"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:11:58Z",
|
||||
"ts": "2026-08-22T17:13:31Z",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"state": "running",
|
||||
"message": "review"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:11:58Z",
|
||||
"ts": "2026-08-22T17:13:31Z",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"state": "running",
|
||||
"message": "security"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:11:58Z",
|
||||
"ts": "2026-08-22T17:13:31Z",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"state": "running",
|
||||
"message": "qa"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:11:58Z",
|
||||
"ts": "2026-08-22T17:13:31Z",
|
||||
"agent": "documenter",
|
||||
"stage": "document",
|
||||
"state": "running",
|
||||
"message": "document"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-22T17:11:58Z",
|
||||
"ts": "2026-08-22T17:13:31Z",
|
||||
"agent": "leader",
|
||||
"stage": "close",
|
||||
"state": "running",
|
||||
|
||||
Reference in New Issue
Block a user