Iniciativa Cidadã Independente · Transparência Parlamentar
TSX193 linhas6.612 bytes

apps/web/src/components/premiacao/CategoriaCard.tsx

SHA-256

a6aa2128fd87d2ed6d1263c99d6ab6b8b4372e9f9a58b113eb4281dcd1871aaa

Somente leiturafonte-e58fe007b6ee
'use client'

import Link from 'next/link'
import type { CategoriaPremio, Premiado } from '@/lib/types'

/** ISO (2026-04-01) → 04/2026. */
function formatarData(iso: string): string {
  const [ano, mes] = iso.split('-')
  return mes ? `${mes}/${ano}` : iso
}

function numero(v: number): string {
  return v.toLocaleString('pt-BR', { maximumFractionDigits: 1 })
}

/**
 * Evidência por trás da nota.
 *
 * Nas categorias temáticas é o dado contado (relatorias, proposições…). Nas
 * medidas pelo IDS o número bruto seria a própria nota, então mostra-se a
 * decomposição por dimensão — é o que deixa ver o que produziu o total.
 */
function Indicador({ p }: { p: Premiado }) {
  const { composicao } = p.indicador

  if (composicao) {
    return (
      <span className="flex flex-wrap gap-x-3 gap-y-0.5 text-[11px] leading-relaxed text-muted">
        {composicao.map((d) => (
          <span key={d.rotulo}>
            {d.rotulo}{' '}
            <span className="font-mono font-medium text-ink">{numero(d.valor)}</span>
          </span>
        ))}
      </span>
    )
  }

  return (
    <span className="text-xs leading-relaxed text-muted">
      <span className="font-medium text-ink">
        {p.indicador.rotulo}: {numero(p.indicador.valor)} {p.indicador.unidade}
      </span>
      <span className="text-subtle"> · {p.indicador.detalhe}</span>
    </span>
  )
}

function Foto({ p, size }: { p: Premiado; size: 'lg' | 'sm' }) {
  const cls = size === 'lg' ? 'w-16 h-16' : 'w-9 h-9'
  if (!p.fotoUrl) {
    return <div className={`${cls} rounded-full bg-border flex-shrink-0`} />
  }
  return (
    <img
      src={p.fotoUrl}
      alt={p.nome}
      className={`${cls} rounded-full object-cover flex-shrink-0 border border-border`}
    />
  )
}

function Identificacao({ p }: { p: Premiado }) {
  return (
    <>
      <span className="text-muted">
        {p.partido}-{p.uf}
      </span>
      <span className="text-subtle font-mono"> · {p.mesesAtivos}m</span>
      {!p.emExercicio && p.dataFimExercicio && (
        <span className="text-subtle" title="Fim do exercício na 57ª Legislatura">
          {' '}
          · mandato até {formatarData(p.dataFimExercicio)}
        </span>
      )}
    </>
  )
}

export default function CategoriaCard({ categoria }: { categoria: CategoriaPremio }) {
  const [primeiro, ...demais] = categoria.podio
  const margem =
    demais.length > 0
      ? Math.round((primeiro.valor - demais[0].valor) * 10) / 10
      : null

  return (
    <article className="card flex flex-col">
      <header className="border-b border-border pb-4">
        <div className="flex items-start justify-between gap-3">
          <h3 className="font-serif text-lg font-semibold leading-snug text-ink">
            {categoria.titulo}
          </h3>
          {categoria.peso != null && (
            <span
              className="shrink-0 font-mono text-[11px] text-muted"
              title="Peso desta dimensão no cálculo do IDS"
            >
              peso {Math.round(categoria.peso * 100)}%
            </span>
          )}
        </div>
        <p className="mt-2 text-sm leading-relaxed text-muted">{categoria.descricao}</p>
        <p className="mt-2 text-[11px] text-subtle">
          {categoria.elegiveis} concorrentes
          {categoria.ufs && ` · ${categoria.ufs.join(', ')}`}
          {' · critério: '}
          <code className="font-mono">{categoria.criterio}</code>
        </p>
      </header>

      {/* 1º lugar */}
      <div className="flex items-start gap-4 py-5">
        <Foto p={primeiro} size="lg" />
        <div className="min-w-0 flex-1">
          <div className="flex items-baseline gap-2">
            <span className="seal !w-6 !h-6 !text-xs" aria-label="Primeiro lugar">
              1
            </span>
            <Link
              href={`/senador?codigo=${primeiro.senadorCod}`}
              className="font-serif text-xl font-semibold text-ink hover:text-primary hover:underline underline-offset-2 transition-colors"
            >
              {primeiro.nome}
            </Link>
          </div>
          <p className="mt-1 text-sm">
            <Identificacao p={primeiro} />
          </p>
          <p className="mt-2">
            <Indicador p={primeiro} />
          </p>
        </div>
        <div className="shrink-0 text-right">
          <div className="font-mono text-2xl font-bold text-primary">
            {numero(primeiro.valor)}
          </div>
          <div className="text-[10px] uppercase tracking-wider text-subtle">nota</div>
        </div>
      </div>

      {/* 2º e 3º */}
      {demais.length > 0 && (
        <ol className="border-t border-border">
          {demais.map((p) => (
            <li
              key={p.senadorCod}
              className="flex items-center gap-3 border-b border-border/60 py-3 last:border-b-0"
            >
              <span className="w-4 shrink-0 text-center font-mono text-xs text-subtle">
                {p.posicao}
              </span>
              <Foto p={p} size="sm" />
              <div className="min-w-0 flex-1">
                <Link
                  href={`/senador?codigo=${p.senadorCod}`}
                  className="text-sm font-medium text-ink hover:text-primary hover:underline underline-offset-2 transition-colors"
                >
                  {p.nome}
                </Link>
                <p className="text-xs">
                  <Identificacao p={p} />
                </p>
                <p className="mt-0.5">
                  <Indicador p={p} />
                </p>
              </div>
              <span className="shrink-0 font-mono text-sm font-semibold text-muted">
                {numero(p.valor)}
              </span>
            </li>
          ))}
        </ol>
      )}

      {/* Procedência do resultado — margem e desempate ficam à vista */}
      <footer className="mt-auto border-t border-border pt-3 text-[11px] leading-relaxed text-subtle">
        {categoria.houveDesempate ? (
          <span>
            <strong className="text-warning">Empate na nota.</strong> As notas são
            percentis arredondados e saturam no topo. Desempate pelo indicador
            bruto desta mesma dimensão — não pelo IDS geral, para que a categoria
            seja decidida pelo que ela mede —, depois IDS total, tempo de
            exercício e, por fim, ordem alfabética.
          </span>
        ) : margem != null ? (
          <span>Margem sobre o 2º lugar: {numero(margem)} ponto(s).</span>
        ) : null}
      </footer>
    </article>
  )
}