Fix Geradores
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import { useState } from "react";
|
||||
// src/components/Geradores/GeradorCard.tsx
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshCwIcon, CopyIcon } from "lucide-react";
|
||||
import { Button } from "../ui/Button";
|
||||
import { Input } from "../ui/Input";
|
||||
import { Select } from "../ui/Select";
|
||||
import { ResultDisplay } from "../ui/ResultDisplay";
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
type Parametro =
|
||||
| { nome: string; tipo: "text" | "number" | "password"; placeholder?: string }
|
||||
| { nome: string; tipo: "select"; options: { label: string; value: string }[] };
|
||||
export type Parametro =
|
||||
| { nome: string; tipo: "text" | "number" | "password"; placeholder?: string; default?: string }
|
||||
| { nome: string; tipo: "select"; options: { label: string; value: string }[]; default?: string };
|
||||
|
||||
type GeradorCardProps = {
|
||||
export type GeradorCardProps = {
|
||||
readonly titulo: string;
|
||||
readonly descricao?: string;
|
||||
readonly parametros?: Parametro[];
|
||||
@@ -25,50 +26,92 @@ export default function GeradorCard({
|
||||
onGerar,
|
||||
onValidar,
|
||||
}: GeradorCardProps) {
|
||||
const [resultado, setResultado] = useState("");
|
||||
const [paramInputs, setParamInputs] = useState<Record<string, string>>({});
|
||||
const [validado, setValidado] = useState<boolean | null>(null);
|
||||
|
||||
const handleInputChange = (nome: string, valor: string) => {
|
||||
setParamInputs((prev) => ({ ...prev, [nome]: valor }));
|
||||
};
|
||||
|
||||
const handleGerar = async () => {
|
||||
const gerado = await onGerar(paramInputs);
|
||||
setResultado(gerado);
|
||||
setValidado(null);
|
||||
};
|
||||
|
||||
const handleValidar = async () => {
|
||||
if (onValidar) {
|
||||
const isValid = await onValidar(resultado);
|
||||
setValidado(isValid);
|
||||
// Inicializa paramInputs com defaults vindos de `parametros`
|
||||
const initialParamInputs = parametros.reduce<Record<string, string>>((acc, p) => {
|
||||
debugger;
|
||||
if (p.default) {
|
||||
acc[p.nome] = p.default;
|
||||
} else {
|
||||
acc[p.nome] = "";
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const handleCopiar = () => {
|
||||
const [resultado, setResultado] = useState("");
|
||||
const [paramInputs, setParamInputs] = useState<Record<string, string>>(initialParamInputs);
|
||||
const [validado, setValidado] = useState<boolean | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleInputChange = useCallback((nome: string, valor: string) => {
|
||||
setParamInputs((prev) => ({ ...prev, [nome]: valor }));
|
||||
}, []);
|
||||
|
||||
const handleGerar = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setValidado(null);
|
||||
try {
|
||||
const gerado = await onGerar(paramInputs);
|
||||
setResultado(typeof gerado === "string" ? gerado : String(gerado ?? ""));
|
||||
toast.success(`${titulo} gerado com sucesso.`, { duration: 2000 });
|
||||
} catch (err) {
|
||||
console.error("Erro ao gerar:", err);
|
||||
toast.error("Falha ao gerar. Tente novamente.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [onGerar, paramInputs, titulo]);
|
||||
|
||||
const handleValidar = useCallback(async () => {
|
||||
if (!onValidar) return;
|
||||
if (!resultado) {
|
||||
toast.error("Nenhum valor para validar.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const isValid = await onValidar(resultado);
|
||||
setValidado(Boolean(isValid));
|
||||
if (isValid) {
|
||||
toast.success("Válido", { duration: 1500 });
|
||||
} else {
|
||||
toast.error("Inválido", { duration: 1500 });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erro ao validar:", err);
|
||||
toast.error("Falha na validação. Tente novamente.");
|
||||
setValidado(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [onValidar, resultado]);
|
||||
|
||||
const handleCopiar = useCallback(async () => {
|
||||
if (!resultado) return;
|
||||
navigator.clipboard.writeText(resultado);
|
||||
toast.success(`${titulo} copiado!`,
|
||||
{
|
||||
duration: 3000
|
||||
});
|
||||
};
|
||||
try {
|
||||
await navigator.clipboard.writeText(resultado);
|
||||
toast.success(`${titulo} copiado!`, { duration: 2000 });
|
||||
} catch (err) {
|
||||
console.error("Erro ao copiar:", err);
|
||||
toast.error("Não foi possível copiar para a área de transferência.");
|
||||
}
|
||||
}, [resultado, titulo]);
|
||||
|
||||
const handleResultadoChange = (valor: string) => {
|
||||
const handleResultadoChange = useCallback((valor: string) => {
|
||||
setResultado(valor);
|
||||
setValidado(null); // resetar estado de validação ao editar
|
||||
};
|
||||
setValidado(null); // reset ao editar
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md rounded-2xl border border-gray-200 bg-white dark:bg-zinc-900 p-6 shadow-lg hover:shadow-xl transition-shadow duration-300">
|
||||
{/* Título e descrição */}
|
||||
<div
|
||||
className="w-full max-w-md rounded-2xl border border-gray-200 bg-white dark:bg-zinc-900 p-6 shadow-lg hover:shadow-xl transition-shadow duration-300"
|
||||
aria-busy={loading}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<h3 className="text-xl font-bold text-blue-700 dark:text-blue-400">{titulo}</h3>
|
||||
{descricao && <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{descricao}</p>}
|
||||
</div>
|
||||
|
||||
{/* Parâmetros dinâmicos */}
|
||||
{parametros.length > 0 && (
|
||||
<div className="space-y-4 mb-6">
|
||||
{parametros.map((param) => {
|
||||
@@ -78,9 +121,10 @@ export default function GeradorCard({
|
||||
key={param.nome}
|
||||
label={param.nome}
|
||||
options={param.options}
|
||||
value={paramInputs[param.nome] || ""}
|
||||
value={paramInputs[param.nome] ?? ""}
|
||||
onChange={(val) => handleInputChange(param.nome, val)}
|
||||
placeholder={`Selecionar ${param.nome}`}
|
||||
aria-label={param.nome}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -90,21 +134,23 @@ export default function GeradorCard({
|
||||
key={param.nome}
|
||||
type={param.tipo}
|
||||
placeholder={param.placeholder || param.nome}
|
||||
value={paramInputs[param.nome] || ""}
|
||||
value={paramInputs[param.nome] ?? ""}
|
||||
onChange={(e) => handleInputChange(param.nome, e.target.value)}
|
||||
aria-label={param.nome}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Resultado + botão de copiar como ícone */}
|
||||
<div className="mb-6 flex items-center gap-2 relative">
|
||||
<ResultDisplay
|
||||
value={resultado}
|
||||
validated={validado}
|
||||
onChange={handleResultadoChange}
|
||||
aria-label={`${titulo} resultado`}
|
||||
/>
|
||||
|
||||
{resultado && (
|
||||
<Button
|
||||
onClick={handleCopiar}
|
||||
@@ -112,25 +158,31 @@ export default function GeradorCard({
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs h-auto"
|
||||
title="Copiar"
|
||||
>
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botões distribuídos numa linha com preenchimento igual */}
|
||||
<div className="flex w-full gap-x-2">
|
||||
<Button
|
||||
onClick={handleGerar}
|
||||
icon={<RefreshCwIcon className="w-4 h-4" />}
|
||||
variant="primary"
|
||||
className="flex-1"
|
||||
title={`Gerar ${titulo}`}
|
||||
disabled={loading}
|
||||
>
|
||||
Gerar
|
||||
{loading ? "A gerar..." : "Gerar"}
|
||||
</Button>
|
||||
|
||||
{onValidar && (
|
||||
<Button onClick={handleValidar} variant="danger" className="flex-1">
|
||||
Validar
|
||||
<Button
|
||||
onClick={handleValidar}
|
||||
variant={validado === true ? "primary" : "danger"}
|
||||
className="flex-1"
|
||||
title={`Validar ${titulo}`}
|
||||
disabled={loading || !resultado}
|
||||
>
|
||||
{loading ? "Verificando..." : "Validar"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import GeradorCard from "../GeradorCard";
|
||||
import { EnumToOptions } from "../../../library/utils";
|
||||
import GeradorCard, { type Parametro } from "../GeradorCard";
|
||||
import { EnumToOptions, getEnumKeyByValue } from "../../../library/utils";
|
||||
import { NIFType } from "../../../service/api";
|
||||
import GeradorService from "../../../Api/GeradorApi";
|
||||
|
||||
@@ -9,31 +9,31 @@ const NifTypes = EnumToOptions(NIFType).map((opt) => ({
|
||||
}));
|
||||
|
||||
const GenerateNIF = () => {
|
||||
|
||||
const handleGenerateNIF = async (params: Record<string, string>) => {
|
||||
const tipoSelecionado = params["Tipo de NIF"];
|
||||
const nif = await GeradorService.GenerateNIF(tipoSelecionado);
|
||||
|
||||
const handleGenerateNIF = async (params?: Record<string, string>) => {
|
||||
const tipoSelecionado = params?.["Tipo de NIF"] ?? String(NIFType.PessoaSingular2);
|
||||
const nif = String(await GeradorService.GenerateNIF(tipoSelecionado));
|
||||
return nif;
|
||||
};
|
||||
|
||||
const handleValidateNIF = async (valor: string) => {
|
||||
// Lógica real de validação
|
||||
const bol = await GeradorService.ValidateNIF(valor);
|
||||
const bol = Boolean(await GeradorService.ValidateNIF(valor));
|
||||
return bol;
|
||||
};
|
||||
|
||||
const parametros: Parametro[] = [
|
||||
{
|
||||
nome: "Tipo de NIF",
|
||||
tipo: "select",
|
||||
options: NifTypes,
|
||||
default: getEnumKeyByValue(NIFType, NIFType.PessoaSingular2)
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto p-6">
|
||||
<GeradorCard
|
||||
titulo="NIF"
|
||||
parametros={[
|
||||
{
|
||||
nome: "Tipo de NIF",
|
||||
tipo: "select",
|
||||
options: NifTypes,
|
||||
},
|
||||
]}
|
||||
parametros={parametros}
|
||||
onGerar={handleGenerateNIF}
|
||||
onValidar={handleValidateNIF}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
|
||||
const Utils = {
|
||||
EnumToOptions,
|
||||
// Outras fun<75><6E>es podem ser adicionadas aqui
|
||||
// Outras fun<75><6E>es podem ser adicionadas aqui
|
||||
};
|
||||
|
||||
export function EnumToOptions<T extends Record<string, string>>(enumObj: T): any[] {
|
||||
return Object.entries(enumObj)
|
||||
.filter(([key]) => isNaN(Number(key))) // Ignora entradas num<75>ricas, caso existam
|
||||
.filter(([key]) => isNaN(Number(key))) // Ignora entradas num<75>ricas, caso existam
|
||||
.map(([key, value]) => ({
|
||||
value: key, // A chave do enum como `value`
|
||||
label: value, // O valor do enum como `label`
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEnumKeyByValue<T extends Record<string, string>>(
|
||||
enumObj: T,
|
||||
value: string
|
||||
): string | undefined {
|
||||
return Object.keys(enumObj).find((key) => enumObj[key as keyof T] === value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user