feat(admin): paginacao na interface, edicao de utilizadores e pagina de testes

- fila e utilizadores passam a pedir uma pagina de cada vez, com pesquisa e
  filtros; nada e cortado no browser, porque o que faltava nunca chegou a ser
  buscado. A pesquisa espera que se pare de escrever: cada tecla seria mais
  uma consulta, e as respostas podem chegar fora de ordem
- pageWindow e pageRange tem testes proprios: uma pagina fora do intervalo vem
  de um URL velho ou de uma lista que encolheu, e nao pode dar um paginador
  vazio ou negativo, nem dizer "76 a 100 de 82"
- editar utilizador: papel, quota, idioma, com os apelidos que entregam
  naquela caixa a mostra no mesmo painel
- "Caixas de e-mail" passa a chamar-se Utilizadores, que e o que sao

Pagina nova /admin/tests: verificacoes que o operador corre a mao, sobre o
servidor ou sobre um utilizador. Todas leem estado; nenhuma envia correio nem
altera definicoes -- uma pagina de diagnostico que mexe naquilo que
diagnostica e pior do que nao existir.

As regras de cada verificacao sao funcoes puras com testes, separadas da
recolha dos factos. Alguns casos que estes testes fixam: uma quota zero
significa sem limite, e um racio marcaria todas as caixas ilimitadas como
cheias; um bloqueio ja expirado nao bloqueia ninguem e nao deve mandar
ninguem investigar; e uma resposta de ausencia ligada com o periodo ja
passado esta ligada sem fazer nada, que e o que parece avariado a quem a pos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-08-24 09:36:35 -03:00
1 parent 52bf43b74f
commit 2b92abd185
16 files changed
+1431 -153

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
/**
* The page numbers a pager should offer.
*
* Rendering every page is fine for three and unusable for four hundred, so the
* window follows the current page and the ends stay reachable.
*/
export function pageWindow(page: number, totalPages: number, span = 2): number[] {
if (!Number.isFinite(totalPages) || totalPages < 1) return [1];
const current = Math.min(Math.max(Math.trunc(page) || 1, 1), totalPages);
const first = Math.max(1, current - span);
const last = Math.min(totalPages, current + span);
const pages: number[] = [];
for (let p = first; p <= last; p++) pages.push(p);
return pages;
}
/** The human sentence under a list: which rows these are, out of how many. */
export function pageRange(page: number, pageSize: number, total: number): { from: number; to: number } {
if (total <= 0) return { from: 0, to: 0 };
const from = (page - 1) * pageSize + 1;
return { from, to: Math.min(total, page * pageSize) };
}