Reescrita dos geradores: biblioteca IGenerator, minimal API e novo frontend

- GeradoresService: cada tipo passa a ser um IGenerator (NIF, NIPC, NISS, CC,
  passaporte, IBAN, NIB, cartões, referência MB, pessoa, telefone, código postal,
  email, matrícula, EAN, UUID) registado em GeneratorRegistry; remove DAL/EF
- GeradoresWS: minimal APIs em /api/v1 com OpenAPI + Scalar e health check
- UnitTest: testes de ida e volta, valores conhecidos, detecção e API
- geradoresfe: UI construída a partir dos metadados da API, validador livre,
  exportação CSV/JSON/TXT, nginx com API_UPSTREAM configurável
- Docker, docker-compose e Jenkinsfile actualizados

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 23:51:39 +01:00
parent 0f764b1239
commit d7ea8a9b9e
119 changed files with 7764 additions and 2502 deletions

View File

@@ -1,31 +1,27 @@
import { CheckIcon, XIcon } from "lucide-react";
import { CopyIcon } from "lucide-react";
import { copyToClipboard } from "../../lib/clipboard";
type ResultDisplayProps = {
value: string;
validated: boolean | null;
onChange?: (value: string) => void;
label: string;
};
export function ResultDisplay({ value, validated, onChange }: ResultDisplayProps) {
const baseClasses = "flex items-center gap-2 px-3 py-2 border rounded-md text-sm w-full";
const colors =
validated === true
? "border-green-500 text-green-700 bg-green-50 dark:text-green-400 dark:bg-green-950 dark:border-green-700"
: validated === false
? "border-red-500 text-red-700 bg-red-50 dark:text-red-400 dark:bg-red-950 dark:border-red-700"
: "border-gray-300 text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-zinc-800 dark:border-gray-600";
export function ResultDisplay({ value, label }: ResultDisplayProps) {
return (
<div className={baseClasses + " " + colors}>
<input
type="text"
readOnly={!onChange}
value={value}
onChange={(e) => onChange?.(e.target.value)}
className="flex-1 bg-transparent outline-none font-mono truncate"
/>
{validated === true && <CheckIcon className="w-5 h-5" />}
{validated === false && <XIcon className="w-5 h-5" />}
<div className="flex items-center gap-2 rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 dark:border-zinc-600 dark:bg-zinc-800">
<output aria-label={`${label} gerado`} className="min-h-5 flex-1 truncate font-mono text-sm text-gray-900 dark:text-gray-100">
{value || <span className="text-gray-400">—</span>}
</output>
<button
type="button"
onClick={() => copyToClipboard(value, label)}
disabled={!value}
className="rounded p-1 text-gray-500 hover:bg-gray-200 hover:text-blue-600 disabled:opacity-30 dark:hover:bg-zinc-700"
title="Copiar"
aria-label="Copiar"
>
<CopyIcon className="h-4 w-4" />
</button>
</div>
);
}