155 lines
6.1 KiB
Markdown
155 lines
6.1 KiB
Markdown
# ETAPA ADMIN — Vista de respuestas mejorada + vista detallada
|
|
|
|
## 1. Objetivo
|
|
Mejorar `/admin/responses` para que sea una interfaz de administración real,
|
|
y crear `/admin/responses/[id]` como vista detallada de una respuesta individual.
|
|
Todo detrás del middleware de auth existente.
|
|
|
|
## 2. Contexto
|
|
- Estado actual: `app/admin/responses/page.tsx` (Server Component, 249 líneas) +
|
|
`app/admin/responses/responses-table.tsx` (Client Component, 98 líneas).
|
|
- Auth: ya implementada — getSupabaseSession() con anon key + cookies, respeta RLS.
|
|
- Exclusión de sensibles: `is_sensitive=false` y `type != 'text_long'` en query de
|
|
questions — NO tocar, mantener exactamente igual.
|
|
- SURVEY_OPTIONS hardcodeadas con UUIDs literales — reemplazar por query dinámica.
|
|
- LIMIT 500 sin paginación real — reemplazar por paginación con page size de 50.
|
|
|
|
## 3. DECISIONES YA TOMADAS — no consultar
|
|
- Paginación: page size 50, parámetro `?page=N` en la URL (Server Component,
|
|
recarga completa — no client-side infinite scroll).
|
|
- Filtro de survey: `<select>` que carga surveys desde la DB (solo las que el
|
|
usuario puede ver según RLS), no hardcodeado.
|
|
- Vista detallada: ruta `/admin/responses/[id]` — Server Component, muestra todas
|
|
las respuestas no sensibles de esa submission, agrupadas por sección.
|
|
- Diseño: usar clases Tailwind o clases `.admin-*` existentes — sin nuevo sistema.
|
|
- Sin librería de tablas externa.
|
|
- El expand/collapse por submission en la tabla se conserva.
|
|
|
|
## 4. Tareas
|
|
|
|
### 4.1 Vista lista mejorada — `app/admin/responses/page.tsx`
|
|
|
|
**Filtro de survey dinámico:**
|
|
Reemplazar SURVEY_OPTIONS hardcodeadas por query a `surveys` table:
|
|
```ts
|
|
const { data: surveys } = await supabase
|
|
.from('surveys')
|
|
.select('id, title')
|
|
.order('title')
|
|
```
|
|
Renderizar como `<select>` con onChange que navega a `?survey_id=<id>`.
|
|
Opción "Todos los surveys" con value=''.
|
|
|
|
**Paginación:**
|
|
- Leer `searchParams.page` (default 1).
|
|
- Query de responses: `.range((page-1)*50, page*50-1)` en vez de `.limit(500)`.
|
|
- Mostrar: "Página N de M · X respuestas totales".
|
|
- Links Anterior / Siguiente que modifican `?page=N` en la URL.
|
|
- Si hay menos de 50 resultados, no mostrar paginación.
|
|
|
|
**Columna de acciones:**
|
|
Agregar columna "Ver" en la fila cabecera de cada submission (isFirstInGroup=true)
|
|
con link a `/admin/responses/[response_id]`.
|
|
|
|
**Header informativo:**
|
|
Mostrar claramente: total de submissions, total de answers, survey seleccionado.
|
|
|
|
### 4.2 Vista detallada — `app/admin/responses/[id]/page.tsx`
|
|
|
|
Server Component. Ruta: `/admin/responses/[id]` donde `id` = `response_id` (uuid).
|
|
|
|
**Query principal:**
|
|
```ts
|
|
// 1. Metadata de la respuesta
|
|
const { data: response } = await supabase
|
|
.from('responses')
|
|
.select('id, submitted_at, qi_zone, qi_sector, qi_age_bucket, survey_id')
|
|
.eq('id', id)
|
|
.single()
|
|
|
|
// 2. Preguntas permitidas (mismo filtro que la lista)
|
|
const { data: questions } = await supabase
|
|
.from('questions')
|
|
.select('id, code, prompt, type, position, section_number, section_title')
|
|
.eq('is_sensitive', false)
|
|
.neq('type', 'text_long')
|
|
.eq('survey_id', response.survey_id)
|
|
.order('position')
|
|
|
|
// 3. Answers de esta respuesta
|
|
const { data: answers } = await supabase
|
|
.from('answers')
|
|
.select('question_id, value, instance_id')
|
|
.eq('response_id', id)
|
|
.in('question_id', questions.map(q => q.id))
|
|
```
|
|
|
|
**Layout:**
|
|
- Header: fecha, zona, sector, edad — datos de qi_* de la respuesta.
|
|
- Link "← Volver a la lista" que preserva el `?survey_id` y `?page` anteriores
|
|
(pasar como searchParam o usar `referrer`).
|
|
- Respuestas agrupadas por sección (section_number + section_title).
|
|
- Dentro de cada sección: tabla código | pregunta | valor.
|
|
- Instancias (instance_id != 'default'): mostrar agrupadas bajo "Familiar N"
|
|
con badge visual.
|
|
- Valores nulos: badge "sin respuesta" (igual que la lista).
|
|
- Si `id` no existe o RLS bloquea: mostrar "Respuesta no encontrada" —
|
|
no crashear.
|
|
|
|
### 4.3 Tabla de respuestas — `app/admin/responses/responses-table.tsx`
|
|
Agregar la columna "Ver" en las filas isFirstInGroup con `<Link>` a
|
|
`/admin/responses/[submissionId]`. Pasar `surveyId` y `page` como props
|
|
para construir el link de "← Volver" en la vista detallada.
|
|
|
|
## 5. DoD antes de reportar
|
|
- [ ] `git log --name-only -1` — listar todos los archivos tocados
|
|
- [ ] Filtro de survey carga desde DB, no hardcodeado
|
|
- [ ] Paginación funciona: page=1 muestra primeros 50, page=2 los siguientes
|
|
- [ ] Link "Ver" en cada submission lleva a `/admin/responses/[id]`
|
|
- [ ] Vista detallada muestra respuestas agrupadas por sección
|
|
- [ ] Instancias agrupadas bajo "Familiar N"
|
|
- [ ] RLS respetado: misma exclusión de sensibles que la lista
|
|
- [ ] "Respuesta no encontrada" si el id no existe
|
|
- [ ] Build sin errores: `npm run build`
|
|
- [ ] `git log --name-only -1`
|
|
- [ ] Commit + push a Gitea
|
|
|
|
## 6. Qué reportar
|
|
```
|
|
Verificado:
|
|
- Filtro survey: ✓ — query a tabla surveys, no hardcodeado
|
|
- Paginación: ✓ — page size 50, range correcto
|
|
- Link "Ver" → /admin/responses/[id]: ✓
|
|
- Vista detallada: ✓ — agrupada por sección, instancias bajo "Familiar N"
|
|
- RLS/exclusión sensibles: ✓ — mismo filtro que antes
|
|
- "No encontrada" si id inválido: ✓
|
|
- Build: ✓
|
|
- git log --name-only -1: [lista completa]
|
|
- Commit: [hash]
|
|
|
|
Decisiones de diseño tomadas:
|
|
- [lo que haya]
|
|
|
|
Hallazgos:
|
|
- [lo que aparezca, especialmente sobre questions.section_number/section_title
|
|
— si esas columnas no existen, reportarlo antes de inventar una solución]
|
|
```
|
|
|
|
## 7. Qué NO hacer
|
|
- ❌ No tocar el filtro `is_sensitive=false` ni `type != 'text_long'`
|
|
- ❌ No usar service_role — solo getSupabaseSession() con RLS
|
|
- ❌ No agregar librerías de tablas o UI externas
|
|
- ❌ No implementar búsqueda por texto (fuera de alcance)
|
|
- ❌ No hacer deploy — solo commit y push a Gitea
|
|
- ❌ Si `section_number` o `section_title` no existen en questions,
|
|
reportarlo y NO inventar columnas — esperar instrucción
|
|
|
|
## 8. Versionado
|
|
```
|
|
feat(admin): vista respuestas mejorada + vista detallada [admin]
|
|
|
|
/admin/responses: filtro survey dinámico, paginación 50/página,
|
|
link a vista detallada. /admin/responses/[id]: respuestas por sección,
|
|
instancias bajo "Familiar N". RLS y exclusión de sensibles intactos.
|
|
```
|