From 1bab6874a9be25f42bf87ba2156f44160b1bc0e6 Mon Sep 17 00:00:00 2001 From: markosbenitez Date: Wed, 10 Jun 2026 21:41:47 -0300 Subject: [PATCH] feat(admin): colapsar/expandir submissions + eliminar columna Tipo --- app/admin/responses/page.tsx | 71 +++--------------- app/admin/responses/responses-table.tsx | 98 +++++++++++++++++++++++++ app/globals.css | 8 +- 3 files changed, 112 insertions(+), 65 deletions(-) create mode 100644 app/admin/responses/responses-table.tsx diff --git a/app/admin/responses/page.tsx b/app/admin/responses/page.tsx index 19734d1..86f17e1 100644 --- a/app/admin/responses/page.tsx +++ b/app/admin/responses/page.tsx @@ -1,4 +1,5 @@ import { createClient } from '@supabase/supabase-js' +import { ResponsesTable, type DisplayRow } from './responses-table' // Server Component sin auth — herramienta interna de diagnóstico (Etapa 3V-B). // Usa SUPABASE_SERVICE_ROLE_KEY: vive solo acá, server-side, nunca al cliente. @@ -28,19 +29,6 @@ interface ResponseRow { answers: AnswerRow[] } -interface FlatRow { - key: string - submissionNum: number - isFirstInGroup: boolean - submitted_at: string - qi_zone: string | null - qi_sector: string | null - code: string - prompt: string - type: string - value: unknown -} - function getSupabaseAdmin() { const url = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co' const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'placeholder' @@ -147,7 +135,7 @@ export default async function AdminResponsesPage() { : { count: 0 } // 4. Aplanar: una fila por answer, ordenadas por position dentro de cada submission - const rows: FlatRow[] = [] + const rows: DisplayRow[] = [] responses.forEach((response, idx) => { const answers = response.answers .filter((a) => questionMap.has(a.question_id)) @@ -158,17 +146,17 @@ export default async function AdminResponsesPage() { answers.forEach((a, ai) => { const q = questionMap.get(a.question_id)! + const isFirstInGroup = ai === 0 rows.push({ key: `${response.id}-${a.question_id}`, submissionNum: idx + 1, - isFirstInGroup: ai === 0, - submitted_at: response.submitted_at, - qi_zone: response.qi_zone, - qi_sector: response.qi_sector, + isFirstInGroup, + date: isFirstInGroup ? formatDate(response.submitted_at) : '', code: q.code, - prompt: q.prompt, - type: q.type, - value: a.value, + prompt: truncate(q.prompt), + rendered: renderValue(a.value, q.type), + zone: isFirstInGroup ? (response.qi_zone ?? '—') : '', + sector: isFirstInGroup ? (response.qi_sector ?? '—') : '', }) }) }) @@ -187,46 +175,7 @@ export default async function AdminResponsesPage() {
- - - - - - - - - - - - - - - {rows.map((row) => { - const groupClass = - row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd' - const trClass = row.isFirstInGroup ? `${groupClass} admin-group-start` : groupClass - const rendered = renderValue(row.value, row.type) - return ( - - - - - - - - - - - ) - })} - -
#FechaCódigoPreguntaTipoValorDepartamentoSector
{row.submissionNum}{row.isFirstInGroup ? formatDate(row.submitted_at) : ''}{row.code}{truncate(row.prompt)}{row.type} - {rendered === null ? sin dato : rendered} - - {row.isFirstInGroup ? (row.qi_zone ?? '—') : ''} - - {row.isFirstInGroup ? (row.qi_sector ?? '—') : ''} -
+ {rows.length === 0 && (

Sin respuestas registradas todavía.

)} diff --git a/app/admin/responses/responses-table.tsx b/app/admin/responses/responses-table.tsx new file mode 100644 index 0000000..2bed8cf --- /dev/null +++ b/app/admin/responses/responses-table.tsx @@ -0,0 +1,98 @@ +'use client' + +import { useState } from 'react' + +export interface DisplayRow { + key: string + submissionNum: number + isFirstInGroup: boolean + date: string + code: string + prompt: string + rendered: string | null + zone: string + sector: string +} + +export function ResponsesTable({ rows }: { rows: DisplayRow[] }) { + const [expanded, setExpanded] = useState>(new Set()) + + function toggle(submissionNum: number) { + setExpanded((prev) => { + const next = new Set(prev) + if (next.has(submissionNum)) next.delete(submissionNum) + else next.add(submissionNum) + return next + }) + } + + return ( + + + + + + + + + + + + + + {rows + .filter((row) => row.isFirstInGroup || expanded.has(row.submissionNum)) + .map((row) => { + const groupClass = + row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd' + const isExpanded = expanded.has(row.submissionNum) + const trClass = row.isFirstInGroup + ? `${groupClass} admin-group-start admin-row-toggle` + : groupClass + + return ( + toggle(row.submissionNum) : undefined} + onKeyDown={ + row.isFirstInGroup + ? (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + toggle(row.submissionNum) + } + } + : undefined + } + role={row.isFirstInGroup ? 'button' : undefined} + tabIndex={row.isFirstInGroup ? 0 : undefined} + aria-expanded={row.isFirstInGroup ? isExpanded : undefined} + > + + + + + + + + + ) + })} + +
#FechaCódigoPreguntaValorDepartamentoSector
+ {row.isFirstInGroup && ( + + )} + {row.submissionNum} + {row.date}{row.code}{row.prompt} + {row.rendered === null ? ( + sin dato + ) : ( + row.rendered + )} + {row.zone}{row.sector}
+ ) +} diff --git a/app/globals.css b/app/globals.css index ff1eb46..29392e1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -771,10 +771,10 @@ body { .admin-table tbody tr.admin-group-start td { border-top: 2px solid var(--color-re-dark); } .admin-table tbody tr:hover { background: var(--color-primary-light); } .admin-prompt { color: var(--color-text); } -.admin-type-badge { - display: inline-block; padding: 1px 8px; border-radius: 10px; - background: var(--color-primary-light); color: var(--color-primary-hover); - font-size: 0.7rem; font-weight: 600; white-space: nowrap; +.admin-row-toggle { cursor: pointer; } +.admin-toggle-icon { + display: inline-block; width: 14px; margin-right: 4px; + color: var(--color-text-muted); font-size: 0.7rem; } .admin-nodata { display: inline-block; padding: 1px 8px; border-radius: 10px;