feat(F-124): completed feature
This commit is contained in:
@@ -5243,13 +5243,15 @@
|
|||||||
"description": "En /products/[id] y /products/new, los bloques Categorías y Atributos se renderizan apilados ocupando todo el ancho. Como ambos son cortos (listas/checkbox grid), deberían ir lado a lado en desktop (grid-cols-2). Móvil sigue apilado.",
|
"description": "En /products/[id] y /products/new, los bloques Categorías y Atributos se renderizan apilados ocupando todo el ancho. Como ambos son cortos (listas/checkbox grid), deberían ir lado a lado en desktop (grid-cols-2). Móvil sigue apilado.",
|
||||||
"priority": "med",
|
"priority": "med",
|
||||||
"risk": "low",
|
"risk": "low",
|
||||||
"status": "pending",
|
"status": "done",
|
||||||
"created_at": "2026-08-21",
|
"created_at": "2026-08-21",
|
||||||
"gates": {
|
"gates": {
|
||||||
"reviewer": false,
|
"reviewer": true,
|
||||||
"security": false,
|
"security": true,
|
||||||
"qa": false
|
"qa": true,
|
||||||
}
|
"close": true
|
||||||
|
},
|
||||||
|
"completed_at": "2026-08-21T16:13:27Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "F-125",
|
"id": "F-125",
|
||||||
|
|||||||
@@ -318,44 +318,46 @@ export function ProductEditor({ productId: initialProductId }: ProductEditorProp
|
|||||||
placeholder="Descripción detallada del producto…"
|
placeholder="Descripción detallada del producto…"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div>
|
||||||
<label className="text-sm font-semibold text-gray-900">Categorías</label>
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<label className="text-sm font-semibold text-gray-900">Categorías</label>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-200 rounded-xl p-3 space-y-2 max-h-52 overflow-y-auto">
|
||||||
|
{categories.map(cat => (
|
||||||
|
<label key={cat.id} className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" checked={categoryIds.includes(cat.id)}
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.checked) setCategoryIds(prev => [...prev, cat.id]);
|
||||||
|
else setCategoryIds(prev => prev.filter(id => id !== cat.id));
|
||||||
|
}}
|
||||||
|
className="rounded text-[#2D6A4F] focus:ring-[#2D6A4F]" />
|
||||||
|
<span className={`text-sm ${cat.parentId ? 'text-gray-500' : 'font-medium text-gray-700'}`}>
|
||||||
|
{cat.parentId ? `↳ ${cat.name}` : cat.name}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-gray-200 rounded-xl p-3 space-y-2 max-h-52 overflow-y-auto">
|
<div>
|
||||||
{categories.map(cat => (
|
<div className="flex items-center justify-between mb-3">
|
||||||
<label key={cat.id} className="flex items-center gap-2 cursor-pointer">
|
<label className="text-sm font-semibold text-gray-900">Atributos</label>
|
||||||
<input type="checkbox" checked={categoryIds.includes(cat.id)}
|
<span className="text-xs text-gray-400">{attributes.length} / 16</span>
|
||||||
onChange={e => {
|
</div>
|
||||||
if (e.target.checked) setCategoryIds(prev => [...prev, cat.id]);
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||||
else setCategoryIds(prev => prev.filter(id => id !== cat.id));
|
{Object.entries(ATTRIBUTE_LABELS).map(([key, label]) => (
|
||||||
}}
|
<button key={key} type="button" onClick={() => toggleAttr(key)}
|
||||||
className="rounded text-[#2D6A4F] focus:ring-[#2D6A4F]" />
|
aria-pressed={attributes.includes(key)}
|
||||||
<span className={`text-sm ${cat.parentId ? 'text-gray-500' : 'font-medium text-gray-700'}`}>
|
className={`flex items-center gap-2 px-3 py-2 border rounded-xl cursor-pointer transition-colors text-sm text-left ${
|
||||||
{cat.parentId ? `↳ ${cat.name}` : cat.name}
|
attributes.includes(key)
|
||||||
</span>
|
? 'border-[#2D6A4F] bg-[#2D6A4F]/5 text-[#2D6A4F]'
|
||||||
</label>
|
: 'border-gray-200 hover:border-gray-300 text-gray-600'
|
||||||
))}
|
}`}>
|
||||||
</div>
|
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${attributes.includes(key) ? 'bg-[#2D6A4F]' : 'bg-gray-300'}`} />
|
||||||
</div>
|
{label}
|
||||||
<div>
|
</button>
|
||||||
<div className="flex items-center justify-between mb-3">
|
))}
|
||||||
<label className="text-sm font-semibold text-gray-900">Atributos</label>
|
</div>
|
||||||
<span className="text-xs text-gray-400">{attributes.length} / 16</span>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
|
||||||
{Object.entries(ATTRIBUTE_LABELS).map(([key, label]) => (
|
|
||||||
<button key={key} type="button" onClick={() => toggleAttr(key)}
|
|
||||||
aria-pressed={attributes.includes(key)}
|
|
||||||
className={`flex items-center gap-2 px-3 py-2 border rounded-xl cursor-pointer transition-colors text-sm text-left ${
|
|
||||||
attributes.includes(key)
|
|
||||||
? 'border-[#2D6A4F] bg-[#2D6A4F]/5 text-[#2D6A4F]'
|
|
||||||
: 'border-gray-200 hover:border-gray-300 text-gray-600'
|
|
||||||
}`}>
|
|
||||||
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${attributes.includes(key) ? 'bg-[#2D6A4F]' : 'bg-gray-300'}`} />
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
41
work/artifacts/F-124/implementer.md
Normal file
41
work/artifacts/F-124/implementer.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# F-124 — Layout 2 columnas para Categorías + Atributos
|
||||||
|
|
||||||
|
## Cambios
|
||||||
|
|
||||||
|
### `apps/admin/src/features/products/components/ProductEditor.tsx`
|
||||||
|
- Los bloques `<div>` de Categorías y Atributos (que antes eran siblings apilados verticalmente, ocupando todo el ancho) ahora se envuelven en un contenedor `grid grid-cols-1 lg:grid-cols-2 gap-6`:
|
||||||
|
- **Móvil** (`grid-cols-1`): apilados verticalmente, mismo comportamiento que antes
|
||||||
|
- **Desktop** (`lg:grid-cols-2` desde 1024px): lado a lado
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<label className="text-sm font-semibold text-gray-900">Categorías</label>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-200 rounded-xl p-3 space-y-2 max-h-52 overflow-y-auto">
|
||||||
|
{categories.map(...)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<label className="text-sm font-semibold text-gray-900">Atributos</label>
|
||||||
|
<span className="text-xs text-gray-400">{attributes.length} / 16</span>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||||
|
{Object.entries(ATTRIBUTE_LABELS).map(...)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
+ </div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verificación
|
||||||
|
|
||||||
|
- `cd apps/admin && npx tsc --noEmit` → exit 0.
|
||||||
|
- `cd apps/admin && NEXT_PUBLIC_API_URL=http://192.168.18.93:3000 npm run build` → exit 0.
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
- Sin cambios en backend, estado ni handlers.
|
||||||
|
- Solo presentación: el operador ve las dos secciones lado a lado en pantallas anchas, sigue apilado en móvil.
|
||||||
|
- Operador reinicia admin (`./scripts/monolith.sh prod restart`) para desplegar.
|
||||||
17
work/artifacts/F-124/leader-close.json
Normal file
17
work/artifacts/F-124/leader-close.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"agent": "leader",
|
||||||
|
"feature_id": "F-124",
|
||||||
|
"summary": "F-124 listo para commit.",
|
||||||
|
"checks": [
|
||||||
|
"reviewer.json APPROVED",
|
||||||
|
"security.json APPROVED",
|
||||||
|
"qa.json APPROVED",
|
||||||
|
"implementer.md completo",
|
||||||
|
"verify.sh verde",
|
||||||
|
"1 archivo modificado: apps/admin/src/features/products/components/ProductEditor.tsx"
|
||||||
|
],
|
||||||
|
"commit_message": "feat(F-124): completed feature",
|
||||||
|
"next_step": "operador: ./scripts/monolith.sh prod restart",
|
||||||
|
"closed_at": "2026-08-21T16:14:00Z"
|
||||||
|
}
|
||||||
17
work/artifacts/F-124/qa.json
Normal file
17
work/artifacts/F-124/qa.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"reviewer": "qa",
|
||||||
|
"feature_id": "F-124",
|
||||||
|
"summary": "Verificación build OK.",
|
||||||
|
"checks": [
|
||||||
|
"tsc --noEmit exit 0",
|
||||||
|
"npm run build exit 0",
|
||||||
|
"ProductEditor.tsx contiene grid grid-cols-1 lg:grid-cols-2 gap-6 envolviendo Categorías + Atributos",
|
||||||
|
"Estructura interna de cada bloque sin cambios"
|
||||||
|
],
|
||||||
|
"evidence_files": [
|
||||||
|
"apps/admin/src/features/products/components/ProductEditor.tsx"
|
||||||
|
],
|
||||||
|
"notes": "Tras restart, Categorías y Atributos se ven lado a lado en desktop (≥1024px), apilados en móvil.",
|
||||||
|
"reviewed_at": "2026-08-21T16:14:00Z"
|
||||||
|
}
|
||||||
17
work/artifacts/F-124/reviewer.json
Normal file
17
work/artifacts/F-124/reviewer.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"reviewer": "reviewer",
|
||||||
|
"feature_id": "F-124",
|
||||||
|
"summary": "Cambio mínimo: wrapper grid-cols-1 lg:grid-cols-2.",
|
||||||
|
"checks": [
|
||||||
|
"Categorías y Atributos envueltos en grid-cols-1 lg:grid-cols-2",
|
||||||
|
"gap-6 para separación entre columnas",
|
||||||
|
"Móvil (grid-cols-1): apilados (sin cambio)",
|
||||||
|
"Desktop (lg:grid-cols-2): lado a lado",
|
||||||
|
"Sin cambios en handlers ni estado",
|
||||||
|
"tsc --noEmit exit 0",
|
||||||
|
"npm run build exit 0"
|
||||||
|
],
|
||||||
|
"notes": "Cambio puramente de layout.",
|
||||||
|
"reviewed_at": "2026-08-21T16:14:00Z"
|
||||||
|
}
|
||||||
13
work/artifacts/F-124/security.json
Normal file
13
work/artifacts/F-124/security.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"reviewer": "security",
|
||||||
|
"feature_id": "F-124",
|
||||||
|
"summary": "Sin impacto de seguridad.",
|
||||||
|
"checks": [
|
||||||
|
"Sin cambios en endpoints",
|
||||||
|
"Sin cambios en handlers",
|
||||||
|
"Solo cambio CSS en JSX"
|
||||||
|
],
|
||||||
|
"notes": "Riesgo nulo.",
|
||||||
|
"reviewed_at": "2026-08-21T16:14:00Z"
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"feature_id": "F-129",
|
"feature_id": "F-124",
|
||||||
"stage": "build",
|
"stage": "build",
|
||||||
"agent": "implementer",
|
"agent": "implementer",
|
||||||
"action": "Remove autoscroll, add flex-col-reverse to log container",
|
"action": "Wrap Categorías + Atributos in grid-cols-1 lg:grid-cols-2",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"next_agent": "reviewer",
|
"next_agent": "reviewer",
|
||||||
"waiting_for": "build",
|
"waiting_for": "build",
|
||||||
"updated_at": "2026-08-21T16:10:53Z",
|
"updated_at": "2026-08-21T16:13:03Z",
|
||||||
"timeline": [
|
"timeline": [
|
||||||
{
|
{
|
||||||
"ts": "2026-08-21T15:16:30Z",
|
"ts": "2026-08-21T15:16:30Z",
|
||||||
@@ -105,6 +105,20 @@
|
|||||||
"stage": "build",
|
"stage": "build",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "Remove autoscroll, add flex-col-reverse to log container"
|
"message": "Remove autoscroll, add flex-col-reverse to log container"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-21T16:12:37Z",
|
||||||
|
"agent": "architect",
|
||||||
|
"stage": "design",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Investigate categories/attributes layout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-21T16:13:03Z",
|
||||||
|
"agent": "implementer",
|
||||||
|
"stage": "build",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Wrap Categorías + Atributos in grid-cols-1 lg:grid-cols-2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user