Compare commits

13 Commits

Author SHA1 Message Date
0f764b1239 docker compose 2026-01-21 22:05:21 +00:00
0df0baf822 Fix Geradores 2025-11-03 21:28:23 +00:00
32315e179e Push 2025-10-07 21:02:53 +01:00
bd8d5677b4 jenkins user 2025-10-06 23:22:52 +01:00
381b750841 fix user 2025-10-06 23:19:35 +01:00
2b413e479a fix 2025-10-06 23:17:05 +01:00
a6707fe731 Jenkins Git cred 2025-10-06 23:15:58 +01:00
19ad8deb6f Paginas 2025-10-06 23:08:28 +01:00
Marco Santos
6e697e40c1 Fix jenkins 2025-07-24 11:55:16 +01:00
Marco Santos
b25a39df8c Fix a generate 2025-07-24 11:40:08 +01:00
Marco Santos
be3085ce15 master 2025-07-24 02:36:31 +01:00
Marco Santos
ed729b9c8e jenkinsfile 2025-07-24 02:12:56 +01:00
e06815053a Merge pull request 'Novo Layout' (#1) from newlayout into master
Reviewed-on: Marco/Geradores#1
2025-07-24 00:08:59 +01:00
22 changed files with 464 additions and 365 deletions

174
Jenkinsfile vendored
View File

@@ -1,171 +1,103 @@
pipeline { pipeline {
agent any agent any
parameters {
booleanParam(name: 'DeployAll', defaultValue: false, description: 'Deploy Site/WS ?')
booleanParam(name: 'deploySite', defaultValue: false, description: 'Deploy Site ?')
}
triggers {
pollSCM('* * * * *')
}
/*
environment { environment {
now = new Date().format('yyyyMMdd-HHmm', TimeZone.getTimeZone('UTC')) DOCKER_REGISTRY = 'git.homeware.pt/marco'
DOCKER_HOST = 'tcp://192.168.2.20:2375'
DOCKER_TAG = "${env.BUILD_ID}" DOCKER_TAG = "${env.BUILD_ID}"
DOCKER_REGISTRY = 'Shini89' //'your-docker-registry.com' // Registro Docker
REACT_DIR = 'geradoresfe'
REACT_DOCKER_IMAGE = 'shini89/geradoresfe:lastest'
SERVICE_DIR = 'GeradoresService'
API_DIR = 'GeradoresWS'
API_DOCKER_IMAGE = 'shini89/geradoresws:lastest'
}
*/
environment {
DOCKER_HOST = 'tcp://192.168.2.20:2375'
DOCKER_REGISTRY = 'docker.io/shini89'
DOCKER_TAG = "${env.BUILD_ID}" // Tag única para builds
REACT_DIR = 'geradoresfe' REACT_DIR = 'geradoresfe'
SERVICE_DIR = 'GeradoresService' SERVICE_DIR = 'GeradoresService'
API_DIR = 'GeradoresWS' API_DIR = 'GeradoresWS'
REACT_IMAGE = "${DOCKER_REGISTRY}/geradores/fe:${DOCKER_TAG}"
WS_IMAGE = "${DOCKER_REGISTRY}/geradores/ws:${DOCKER_TAG}"
DOCKER_CREDENTIALS_ID = 'docker-registry-creds'
KUBECONFIG_CREDENTIALS_ID = 'kubeconfig-jenkins'
GIT_CREDENTIALS_ID = '3e806a5c-ee46-49fb-974d-15f3fe2e1d49'
} }
stages { stages {
stage('Validate Docker Connection') {
stage('Preparar Ambiente') {
steps { steps {
script { script {
echo "Validando conexão com Docker remoto: ${DOCKER_HOST}" echo "Verificar Docker local"
// Testa a conexão com o Docker remoto sh 'docker version'
sh 'docker --version'
} }
} }
} }
stage('Check for Changes') { stage('Checkout Repositório') {
steps { steps {
script { git branch: 'master',
def message = "" url: 'https://git.homeware.pt/Marco/Geradores.git',
CodeChanges = currentBuild.changeSets != [] credentialsId: env.GIT_CREDENTIALS_ID
if (CodeChanges) {
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
message = message + "<br>Autor: ${entry.author.fullName} ( ${entry.author} ) <br> Commit: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
message = message + '<br>Ficheiros:<br><ul>'
for (int k = 0; k < files.size(); k++) {
def file = files[k]
message = message + "<li> ${file.path} ( ${file.editType.name} ) </li>"
}
message = message + '</ul>'
}
}
}
}
} }
} }
stage('Checkout Code') { stage('Build .NET Services') {
steps { steps {
git branch: 'master', url: 'https://git.homeware.pt/Marco/Geradores.git' dir("${SERVICE_DIR}") {
} sh 'dotnet build -c Release'
} }
/******************************************************* dir("${API_DIR}") {
Stage BUILD
*******************************************************/
stage('Build DLL') {
steps {
dir("${env.SERVICE_DIR}") {
sh 'dotnet build -c Release' sh 'dotnet build -c Release'
} }
} }
} }
stage('Build WS') { stage('Build React App') {
steps { steps {
dir("${env.API_DIR}") { dir("${REACT_DIR}") {
sh 'dotnet build -c Release'
}
}
}
stage('Build WS Docker Image') {
steps {
script {
docker.build("${DOCKER_REGISTRY}/geradoresws:${DOCKER_TAG}", "-f ${API_DIR}/Dockerfile .")
}
}
}
stage('Build React') {
steps {
dir("${env.REACT_DIR}") {
sh 'npm install' sh 'npm install'
sh 'npm run build' sh 'npm run build'
/*
sh """
docker build -t ${env.DOCKER_REGISTRY}/react-frontend:latest .
docker push ${env.DOCKER_REGISTRY}/react-frontend:latest
"""*/
} }
} }
} }
stage('Build React Docker Image') { stage('Docker Build Imagens') {
steps { steps {
dir("${env.REACT_DIR}") { script {
docker.build("${WS_IMAGE}")
docker.build("${REACT_IMAGE}", "${REACT_DIR}")
}
}
}
stage('Push para Docker Registry') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", GIT_CREDENTIALS_ID) {
docker.image("${WS_IMAGE}").push()
docker.image("${REACT_IMAGE}").push()
}
}
}
}
stage('Deploy no Kubernetes') {
steps {
withKubeConfig(credentialsId: KUBECONFIG_CREDENTIALS_ID) {
script { script {
docker.build("${DOCKER_REGISTRY}/geradoresfe:${DOCKER_TAG}", ".") // Substitui as imagens no manifest e aplica
sh """
sed 's|{{WS_IMAGE}}|${WS_IMAGE}|g; s|{{FE_IMAGE}}|${REACT_IMAGE}|g' k8s/deployment.template.yaml > k8s/deployment.yaml
kubectl apply -f k8s/deployment.yaml
"""
} }
} }
} }
} }
stage('Push Docker Images') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'docker-credentials') {
docker.image("${DOCKER_REGISTRY}/geradoresws:${DOCKER_TAG}").push()
docker.image("${DOCKER_REGISTRY}/geradoresfe:${DOCKER_TAG}").push()
}
}
}
}
stage('Deploy Containers Locally') {
steps {
script {
// Remove contêiner antigo (se existir)
sh """
docker rm -f geradoresws-container || true
docker rm -f geradoresfe-container || true
"""
// Executa contêineres com as novas imagens
sh """
docker run -d --name geradoresws-container -p 32772:8080 ${DOCKER_REGISTRY}/geradoresws:${DOCKER_TAG}
docker run -d --name geradoresfe-container -p 3000:3000 ${DOCKER_REGISTRY}/geradoresfe:${DOCKER_TAG}
"""
}
}
}
} }
post { post {
success { success {
echo 'Services deployed successfully!' echo 'Deploy feito com sucesso!'
sh 'docker system prune -f' sh 'docker system prune -f'
} }
failure { failure {
echo 'Failed to deploy services. Check logs.' echo 'Erro no pipeline. Verifica os logs!'
} }
} }
} }

View File

@@ -2,3 +2,6 @@
<code> docker build -t shini89/geradoresws ..</code> <code> docker build -t shini89/geradoresws ..</code>
<code> docker run --rm -p 44329:8080 shini89/geradoresws</code> <code> docker run --rm -p 44329:8080 shini89/geradoresws</code>
<code>npx swagger-typescript-api generate -p http://localhost:29191/swagger/v1/swagger.json -o src/service -n api.ts --enum-names-as-values</code>
npx swagger-typescript-api generate -p http://localhost:29191/swagger/v1/swagger.json -o src/service -n api.ts --http-client axios --enum-names-as-values

View File

@@ -18,11 +18,11 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
image: docker.io/shini89/geradoresfe:latest image: docker.io/shini89/geradoresfe:latest
ports: ports:
- "3000:3000" - "3000:80"
depends_on: depends_on:
- geradoresws - geradoresws
environment: environment:
- REACT_APP_API_URL=http://localhost:5050/ - VITE_API_URL=http://localhost:5050/
networks: networks:
- app-network - app-network

View File

@@ -1 +1 @@
VITE_API_URL=http://localhost:44329/ VITE_API_URL=https://localhost:7266/

View File

@@ -1,8 +1,26 @@
FROM node:20-alpine # ---------- BUILD ----------
FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json .
COPY package.json package-lock.json ./
RUN npm install RUN npm install
COPY . . COPY . .
ENV REACT_APP_API_URL=http://localhost:5015/
EXPOSE 3000 # Variável de ambiente correta para Vite
CMD ["npm", "start"] ENV VITE_API_URL=http://localhost:5050/
RUN npm run build
# ---------- SERVE ----------
FROM nginx:alpine
# Copia os ficheiros do build para a pasta do nginx
COPY --from=build /app/dist /usr/share/nginx/html
# Configuração mínima do nginx (opcional: para SPA fallback)
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

14
geradoresfe/nginx.conf Normal file
View File

@@ -0,0 +1,14 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
# Optional: gzip
gzip on;
gzip_types text/plain application/javascript text/css application/json image/svg+xml;
}

View File

@@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview"
}, },
@@ -35,6 +35,7 @@
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0", "globals": "^16.3.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"swagger-typescript-api": "^13.2.7",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"typescript-eslint": "^8.35.1", "typescript-eslint": "^8.35.1",

View File

@@ -1,6 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import type { AxiosResponse } from 'axios'; import type { AxiosResponse } from 'axios';
import { NIFType } from '../service/api';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
const API_URL = import.meta.env.VITE_API_URL; const API_URL = import.meta.env.VITE_API_URL;
@@ -8,6 +7,7 @@ const API_URL = import.meta.env.VITE_API_URL;
class GeradorService { class GeradorService {
static async GenerateNIF(type: string | null): Promise<any[]> { static async GenerateNIF(type: string | null): Promise<any[]> {
try { try {
debugger;
const response: AxiosResponse = await axios.get(API_URL + 'Generate/GenerateNIF', const response: AxiosResponse = await axios.get(API_URL + 'Generate/GenerateNIF',
{ {
params: { params: {
@@ -15,7 +15,7 @@ class GeradorService {
} }
}); });
return response.data; return response.data;
} catch (error) { } catch (error : any) {
toast.error('Error fetching NIF:' + error.message); toast.error('Error fetching NIF:' + error.message);
return []; return [];

View File

@@ -3,6 +3,9 @@ import { Toaster } from 'react-hot-toast';
import Home from './pages/Home'; import Home from './pages/Home';
import { Layout } from './layout/Layout'; import { Layout } from './layout/Layout';
import { useTheme } from './context/ThemeContext'; import { useTheme } from './context/ThemeContext';
import NIF from './pages/NIF';
import CC from './pages/CC';
import NISS from './pages/NISS';
function App() { function App() {
const { theme } = useTheme(); const { theme } = useTheme();
@@ -12,9 +15,9 @@ function App() {
<Layout> <Layout>
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/NIF" /> <Route path="/NIF" element={<NIF />} />
<Route path="/NISS" /> <Route path="/NISS" element={<NISS />} />
<Route path="/CC" /> <Route path="/CC" element={<CC />} />
</Routes> </Routes>
<Toaster position="top-right" <Toaster position="top-right"
toastOptions={{ toastOptions={{
@@ -22,8 +25,7 @@ function App() {
background: theme === 'dark' ? '#1f2937' : '#fff', background: theme === 'dark' ? '#1f2937' : '#fff',
color: theme === 'dark' ? '#fff' : '#000', color: theme === 'dark' ? '#fff' : '#000',
}, },
}} }} />
theme={theme} />
</Layout> </Layout>
</Router> </Router>
); );

View File

@@ -1,16 +1,17 @@
import React, { useState } from "react"; // src/components/Geradores/GeradorCard.tsx
import { useCallback, useState } from "react";
import { RefreshCwIcon, CopyIcon } from "lucide-react"; import { RefreshCwIcon, CopyIcon } from "lucide-react";
import { Button } from "../ui/Button"; import { Button } from "../ui/Button";
import { Input } from "../ui/Input"; import { Input } from "../ui/Input";
import { Select } from "../ui/Select"; import { Select } from "../ui/Select";
import { ResultDisplay } from "../ui/ResultDisplay"; import { ResultDisplay } from "../ui/ResultDisplay";
import { toast } from 'react-hot-toast'; import { toast } from "react-hot-toast";
type Parametro = export type Parametro =
| { nome: string; tipo: "text" | "number" | "password"; placeholder?: string } | { nome: string; tipo: "text" | "number" | "password"; placeholder?: string; default?: string }
| { nome: string; tipo: "select"; options: { label: string; value: string }[] }; | { nome: string; tipo: "select"; options: { label: string; value: string }[]; default?: string };
type GeradorCardProps = { export type GeradorCardProps = {
readonly titulo: string; readonly titulo: string;
readonly descricao?: string; readonly descricao?: string;
readonly parametros?: Parametro[]; readonly parametros?: Parametro[];
@@ -25,50 +26,92 @@ export default function GeradorCard({
onGerar, onGerar,
onValidar, onValidar,
}: GeradorCardProps) { }: GeradorCardProps) {
const [resultado, setResultado] = useState(""); // Inicializa paramInputs com defaults vindos de `parametros`
const [paramInputs, setParamInputs] = useState<Record<string, string>>({}); const initialParamInputs = parametros.reduce<Record<string, string>>((acc, p) => {
const [validado, setValidado] = useState<boolean | null>(null); debugger;
if (p.default) {
const handleInputChange = (nome: string, valor: string) => { acc[p.nome] = p.default;
setParamInputs((prev) => ({ ...prev, [nome]: valor })); } else {
}; acc[p.nome] = "";
const handleGerar = async () => {
const gerado = await onGerar(paramInputs);
setResultado(gerado);
setValidado(null);
};
const handleValidar = async () => {
if (onValidar) {
const isValid = await onValidar(resultado);
setValidado(isValid);
} }
}; 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; if (!resultado) return;
navigator.clipboard.writeText(resultado); try {
toast.success(`${titulo} copiado!`, await navigator.clipboard.writeText(resultado);
{ toast.success(`${titulo} copiado!`, { duration: 2000 });
duration: 3000 } 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); setResultado(valor);
setValidado(null); // resetar estado de validação ao editar setValidado(null); // reset ao editar
}; }, []);
return ( 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"> <div
{/* Título e descrição */} 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"> <div className="mb-6">
<h3 className="text-xl font-bold text-blue-700 dark:text-blue-400">{titulo}</h3> <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>} {descricao && <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{descricao}</p>}
</div> </div>
{/* Parâmetros dinâmicos */}
{parametros.length > 0 && ( {parametros.length > 0 && (
<div className="space-y-4 mb-6"> <div className="space-y-4 mb-6">
{parametros.map((param) => { {parametros.map((param) => {
@@ -78,9 +121,10 @@ export default function GeradorCard({
key={param.nome} key={param.nome}
label={param.nome} label={param.nome}
options={param.options} options={param.options}
value={paramInputs[param.nome] || ""} value={paramInputs[param.nome] ?? ""}
onChange={(val) => handleInputChange(param.nome, val)} onChange={(val) => handleInputChange(param.nome, val)}
placeholder={`Selecionar ${param.nome}`} placeholder={`Selecionar ${param.nome}`}
aria-label={param.nome}
/> />
); );
} }
@@ -90,21 +134,23 @@ export default function GeradorCard({
key={param.nome} key={param.nome}
type={param.tipo} type={param.tipo}
placeholder={param.placeholder || param.nome} placeholder={param.placeholder || param.nome}
value={paramInputs[param.nome] || ""} value={paramInputs[param.nome] ?? ""}
onChange={(e) => handleInputChange(param.nome, e.target.value)} onChange={(e) => handleInputChange(param.nome, e.target.value)}
aria-label={param.nome}
/> />
); );
})} })}
</div> </div>
)} )}
{/* Resultado + botão de copiar como ícone */}
<div className="mb-6 flex items-center gap-2 relative"> <div className="mb-6 flex items-center gap-2 relative">
<ResultDisplay <ResultDisplay
value={resultado} value={resultado}
validated={validado} validated={validado}
onChange={handleResultadoChange} onChange={handleResultadoChange}
aria-label={`${titulo} resultado`}
/> />
{resultado && ( {resultado && (
<Button <Button
onClick={handleCopiar} onClick={handleCopiar}
@@ -112,25 +158,31 @@ export default function GeradorCard({
variant="secondary" variant="secondary"
className="flex items-center gap-1 px-2 py-1 text-xs h-auto" className="flex items-center gap-1 px-2 py-1 text-xs h-auto"
title="Copiar" title="Copiar"
> />
</Button>
)} )}
</div> </div>
{/* Botões distribuídos numa linha com preenchimento igual */}
<div className="flex w-full gap-x-2"> <div className="flex w-full gap-x-2">
<Button <Button
onClick={handleGerar} onClick={handleGerar}
icon={<RefreshCwIcon className="w-4 h-4" />} icon={<RefreshCwIcon className="w-4 h-4" />}
variant="primary" variant="primary"
className="flex-1" className="flex-1"
title={`Gerar ${titulo}`}
disabled={loading}
> >
Gerar {loading ? "A gerar..." : "Gerar"}
</Button> </Button>
{onValidar && ( {onValidar && (
<Button onClick={handleValidar} variant="danger" className="flex-1"> <Button
Validar onClick={handleValidar}
variant={validado === true ? "primary" : "danger"}
className="flex-1"
title={`Validar ${titulo}`}
disabled={loading || !resultado}
>
{loading ? "Verificando..." : "Validar"}
</Button> </Button>
)} )}
</div> </div>

View File

@@ -1,6 +1,5 @@
import React from "react"; import GeradorCard, { type Parametro } from "../GeradorCard";
import GeradorCard from "../GeradorCard"; import { EnumToOptions, getEnumKeyByValue } from "../../../library/utils";
import { EnumToOptions } from "../../../library/utils";
import { NIFType } from "../../../service/api"; import { NIFType } from "../../../service/api";
import GeradorService from "../../../Api/GeradorApi"; import GeradorService from "../../../Api/GeradorApi";
@@ -10,31 +9,31 @@ const NifTypes = EnumToOptions(NIFType).map((opt) => ({
})); }));
const GenerateNIF = () => { const GenerateNIF = () => {
const handleGenerateNIF = async (params?: Record<string, string>) => {
const handleGenerateNIF = async (params: Record<string, string>) => { const tipoSelecionado = params?.["Tipo de NIF"] ?? String(NIFType.PessoaSingular2);
const tipoSelecionado = params["Tipo de NIF"]; const nif = String(await GeradorService.GenerateNIF(tipoSelecionado));
const nif = await GeradorService.GenerateNIF(tipoSelecionado);
return nif; return nif;
}; };
const handleValidateNIF = async (valor: string) => { const handleValidateNIF = async (valor: string) => {
// Lógica real de validação const bol = Boolean(await GeradorService.ValidateNIF(valor));
const bol = await GeradorService.ValidateNIF(valor);
return bol; return bol;
}; };
const parametros: Parametro[] = [
{
nome: "Tipo de NIF",
tipo: "select",
options: NifTypes,
default: getEnumKeyByValue(NIFType, NIFType.PessoaSingular2)
},
];
return ( return (
<div className="max-w-md mx-auto p-6"> <div className="max-w-md mx-auto p-6">
<GeradorCard <GeradorCard
titulo="NIF" titulo="NIF"
parametros={[ parametros={parametros}
{
nome: "Tipo de NIF",
tipo: "select",
options: NifTypes,
},
]}
onGerar={handleGenerateNIF} onGerar={handleGenerateNIF}
onValidar={handleValidateNIF} onValidar={handleValidateNIF}
/> />

View File

@@ -1,4 +1,3 @@
import { useEffect, useState } from "react";
import GeradorService from "../../../Api/GeradorApi"; import GeradorService from "../../../Api/GeradorApi";
import GeradorCard from "../GeradorCard.tsx"; import GeradorCard from "../GeradorCard.tsx";

View File

@@ -25,7 +25,7 @@ export default function Footer() {
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/geradores" className="hover:text-blue-500 dark:hover:text-blue-400 transition-colors duration-200"> <Link to="/home" className="hover:text-blue-500 dark:hover:text-blue-400 transition-colors duration-200">
Geradores Geradores
</Link> </Link>
</li> </li>

View File

@@ -10,8 +10,9 @@ const navItems = [
{ {
name: "Geradores", name: "Geradores",
subItems: [ subItems: [
{ name: "Gerador de Senhas", path: "/geradores/senhas" }, { name: "Gerador de NIFs", path: "/NIF", },
{ name: "Gerador de Nomes", path: "/geradores/nomes" }, { name: "Gerador de NISS", path: "/NISS" },
{ name: "Gerador de CC", path: "/CC" },
], ],
}, },
{ name: "Contacto", path: "/contacto" }, { name: "Contacto", path: "/contacto" },

View File

@@ -1,7 +1,6 @@
// components/layout/Layout.tsx // components/layout/Layout.tsx
import Footer from './Footer'; import Footer from './Footer';
import Header from './Header'; import Header from './Header';
import { useTheme } from "../context/ThemeContext"; // caminho conforme a tua estrutura
export const Layout = ({ children }: { children: React.ReactNode }) => { export const Layout = ({ children }: { children: React.ReactNode }) => {

View File

@@ -1,18 +1,24 @@
const Utils = { const Utils = {
EnumToOptions, 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[] { export function EnumToOptions<T extends Record<string, string>>(enumObj: T): any[] {
return Object.entries(enumObj) 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]) => ({ .map(([key, value]) => ({
value: key, // A chave do enum como `value` value: key, // A chave do enum como `value`
label: value, // O valor do enum como `label` 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);
}

View File

@@ -1,6 +1,3 @@
import React from "react";
export default function Blank() { export default function Blank() {
console.log("inicalizar componente"); console.log("inicalizar componente");
return ( return (

View File

@@ -0,0 +1,31 @@
import { useEffect } from "react";
import { useLocation } from "react-router";
import GenerateCC from "../components/Geradores/Tipos/GenerateCC";
export default function CC() {
const location = useLocation();
// Efeito para rolar até a seção correspondente ao path
useEffect(() => {
const featuresSection = document.getElementById(location.hash.substring(1));
if (featuresSection) {
featuresSection.scrollIntoView({ behavior: "smooth" });
}
}, [location]);
return (
<div >
<div >
{/* Seção de Funcionalidades */}
<section id="features" className="py-16 px-4 md:px-8">
<div className="max-w-6xl mx-auto grid grid-cols-1">
<div id="nif">
<GenerateCC />
</div>
</div>
</section>
</div>
</div>
);
}

View File

@@ -28,9 +28,9 @@ export default function Home() {
<div id="niss"> <div id="niss">
<GenerateNISS /> <GenerateNISS />
</div> </div>
<div id="cc"> <div id="cc">
<GenerateCC /> <GenerateCC />
</div> </div>
</div> </div>
</section> </section>
</div> </div>

View File

@@ -0,0 +1,31 @@
import { useEffect } from "react";
import { useLocation } from "react-router";
import GenerateNIF from "../components/Geradores/Tipos/GenerateNIF";
export default function NIF() {
const location = useLocation();
// Efeito para rolar até a seção correspondente ao path
useEffect(() => {
const featuresSection = document.getElementById(location.hash.substring(1));
if (featuresSection) {
featuresSection.scrollIntoView({ behavior: "smooth" });
}
}, [location]);
return (
<div >
<div >
{/* Seção de Funcionalidades */}
<section id="features" className="py-16 px-4 md:px-8">
<div className="max-w-6xl mx-auto grid grid-cols-1">
<div id="nif">
<GenerateNIF />
</div>
</div>
</section>
</div>
</div>
);
}

View File

@@ -0,0 +1,31 @@
import { useEffect } from "react";
import { useLocation } from "react-router";
import GenerateNISS from "../components/Geradores/Tipos/GenerateNISS";
export default function NISS() {
const location = useLocation();
// Efeito para rolar até a seção correspondente ao path
useEffect(() => {
const featuresSection = document.getElementById(location.hash.substring(1));
if (featuresSection) {
featuresSection.scrollIntoView({ behavior: "smooth" });
}
}, [location]);
return (
<div >
<div >
{/* Seção de Funcionalidades */}
<section id="features" className="py-16 px-4 md:px-8">
<div className="max-w-6xl mx-auto grid grid-cols-1">
<div id="nif">
<GenerateNISS />
</div>
</div>
</section>
</div>
</div>
);
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable */ /* eslint-disable */
/* tslint:disable */ /* tslint:disable */
// @ts-nocheck
/* /*
* --------------------------------------------------------------- * ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
@@ -10,12 +11,19 @@
*/ */
export enum NIFType { export enum NIFType {
/** Pessoa singular (1) */
PessoaSingular1 = "Pessoa singular (1)", PessoaSingular1 = "Pessoa singular (1)",
/** Pessoa singular (2) */
PessoaSingular2 = "Pessoa singular (2)", PessoaSingular2 = "Pessoa singular (2)",
/** Pessoa singular (3; novo em 2019) */
PessoaSingular3 = "Pessoa singular (3; novo em 2019)", PessoaSingular3 = "Pessoa singular (3; novo em 2019)",
/** Pessoa colectiva (5) */
PessoaColectiva = "Pessoa colectiva (5)", PessoaColectiva = "Pessoa colectiva (5)",
/** Pessoa colectiva pública (6) */
PessoaColectivaPublica = "Pessoa colectiva pública (6)", PessoaColectivaPublica = "Pessoa colectiva pública (6)",
/** Empresário em nome individual (8) */
EmpresarioIndividual = "Empresário em nome individual (8)", EmpresarioIndividual = "Empresário em nome individual (8)",
/** Pessoa colectiva irregular ou número provisório (9) */
PessoaColectivaIrregular = "Pessoa colectiva irregular ou número provisório (9)", PessoaColectivaIrregular = "Pessoa colectiva irregular ou número provisório (9)",
} }
@@ -29,10 +37,19 @@ export interface WeatherForecast {
summary?: string | null; summary?: string | null;
} }
export type QueryParamsType = Record<string | number, any>; import type {
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">; AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
HeadersDefaults,
ResponseType,
} from "axios";
import axios from "axios";
export interface FullRequestParams extends Omit<RequestInit, "body"> { export type QueryParamsType = Record<string | number, any>;
export interface FullRequestParams
extends Omit<AxiosRequestConfig, "data" | "params" | "url" | "responseType"> {
/** set parameter to `true` for call `securityWorker` for this request */ /** set parameter to `true` for call `securityWorker` for this request */
secure?: boolean; secure?: boolean;
/** request path */ /** request path */
@@ -42,199 +59,155 @@ export interface FullRequestParams extends Omit<RequestInit, "body"> {
/** query params */ /** query params */
query?: QueryParamsType; query?: QueryParamsType;
/** format of response (i.e. response.json() -> format: "json") */ /** format of response (i.e. response.json() -> format: "json") */
format?: ResponseFormat; format?: ResponseType;
/** request body */ /** request body */
body?: unknown; body?: unknown;
/** base url */
baseUrl?: string;
/** request cancellation token */
cancelToken?: CancelToken;
} }
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">; export type RequestParams = Omit<
FullRequestParams,
"body" | "method" | "query" | "path"
>;
export interface ApiConfig<SecurityDataType = unknown> { export interface ApiConfig<SecurityDataType = unknown>
baseUrl?: string; extends Omit<AxiosRequestConfig, "data" | "cancelToken"> {
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">; securityWorker?: (
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void; securityData: SecurityDataType | null,
customFetch?: typeof fetch; ) => Promise<AxiosRequestConfig | void> | AxiosRequestConfig | void;
secure?: boolean;
format?: ResponseType;
} }
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
data: D;
error: E;
}
type CancelToken = Symbol | string | number;
export enum ContentType { export enum ContentType {
Json = "application/json", Json = "application/json",
JsonApi = "application/vnd.api+json",
FormData = "multipart/form-data", FormData = "multipart/form-data",
UrlEncoded = "application/x-www-form-urlencoded", UrlEncoded = "application/x-www-form-urlencoded",
Text = "text/plain", Text = "text/plain",
} }
export class HttpClient<SecurityDataType = unknown> { export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = ""; public instance: AxiosInstance;
private securityData: SecurityDataType | null = null; private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"]; private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private abortControllers = new Map<CancelToken, AbortController>(); private secure?: boolean;
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams); private format?: ResponseType;
private baseApiParams: RequestParams = { constructor({
credentials: "same-origin", securityWorker,
headers: {}, secure,
redirect: "follow", format,
referrerPolicy: "no-referrer", ...axiosConfig
}; }: ApiConfig<SecurityDataType> = {}) {
this.instance = axios.create({
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) { ...axiosConfig,
Object.assign(this, apiConfig); baseURL: axiosConfig.baseURL || "",
});
this.secure = secure;
this.format = format;
this.securityWorker = securityWorker;
} }
public setSecurityData = (data: SecurityDataType | null) => { public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data; this.securityData = data;
}; };
protected encodeQueryParam(key: string, value: any) { protected mergeRequestParams(
const encodedKey = encodeURIComponent(key); params1: AxiosRequestConfig,
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; params2?: AxiosRequestConfig,
} ): AxiosRequestConfig {
const method = params1.method || (params2 && params2.method);
protected addQueryParam(query: QueryParamsType, key: string) {
return this.encodeQueryParam(key, query[key]);
}
protected addArrayQueryParam(query: QueryParamsType, key: string) {
const value = query[key];
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
}
protected toQueryString(rawQuery?: QueryParamsType): string {
const query = rawQuery || {};
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
return keys
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
.join("&");
}
protected addQueryParams(rawQuery?: QueryParamsType): string {
const queryString = this.toQueryString(rawQuery);
return queryString ? `?${queryString}` : "";
}
private contentFormatters: Record<ContentType, (input: any) => any> = {
[ContentType.Json]: (input: any) =>
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
[ContentType.FormData]: (input: any) =>
Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
formData.append(
key,
property instanceof Blob
? property
: typeof property === "object" && property !== null
? JSON.stringify(property)
: `${property}`,
);
return formData;
}, new FormData()),
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
};
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
return { return {
...this.baseApiParams, ...this.instance.defaults,
...params1, ...params1,
...(params2 || {}), ...(params2 || {}),
headers: { headers: {
...(this.baseApiParams.headers || {}), ...((method &&
this.instance.defaults.headers[
method.toLowerCase() as keyof HeadersDefaults
]) ||
{}),
...(params1.headers || {}), ...(params1.headers || {}),
...((params2 && params2.headers) || {}), ...((params2 && params2.headers) || {}),
}, },
}; };
} }
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { protected stringifyFormItem(formItem: unknown) {
if (this.abortControllers.has(cancelToken)) { if (typeof formItem === "object" && formItem !== null) {
const abortController = this.abortControllers.get(cancelToken); return JSON.stringify(formItem);
if (abortController) { } else {
return abortController.signal; return `${formItem}`;
}
}
protected createFormData(input: Record<string, unknown>): FormData {
if (input instanceof FormData) {
return input;
}
return Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
const propertyContent: any[] =
property instanceof Array ? property : [property];
for (const formItem of propertyContent) {
const isFileType = formItem instanceof Blob || formItem instanceof File;
formData.append(
key,
isFileType ? formItem : this.stringifyFormItem(formItem),
);
} }
return void 0;
}
const abortController = new AbortController(); return formData;
this.abortControllers.set(cancelToken, abortController); }, new FormData());
return abortController.signal; }
};
public abortRequest = (cancelToken: CancelToken) => { public request = async <T = any, _E = any>({
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
abortController.abort();
this.abortControllers.delete(cancelToken);
}
};
public request = async <T = any, E = any>({
body,
secure, secure,
path, path,
type, type,
query, query,
format, format,
baseUrl, body,
cancelToken,
...params ...params
}: FullRequestParams): Promise<HttpResponse<T, E>> => { }: FullRequestParams): Promise<AxiosResponse<T>> => {
const secureParams = const secureParams =
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && ((typeof secure === "boolean" ? secure : this.secure) &&
this.securityWorker && this.securityWorker &&
(await this.securityWorker(this.securityData))) || (await this.securityWorker(this.securityData))) ||
{}; {};
const requestParams = this.mergeRequestParams(params, secureParams); const requestParams = this.mergeRequestParams(params, secureParams);
const queryString = query && this.toQueryString(query); const responseFormat = format || this.format || undefined;
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
const responseFormat = format || requestParams.format;
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { if (
type === ContentType.FormData &&
body &&
body !== null &&
typeof body === "object"
) {
body = this.createFormData(body as Record<string, unknown>);
}
if (
type === ContentType.Text &&
body &&
body !== null &&
typeof body !== "string"
) {
body = JSON.stringify(body);
}
return this.instance.request({
...requestParams, ...requestParams,
headers: { headers: {
...(requestParams.headers || {}), ...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), ...(type ? { "Content-Type": type } : {}),
}, },
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, params: query,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), responseType: responseFormat,
}).then(async (response) => { data: body,
const r = response.clone() as HttpResponse<T, E>; url: path,
r.data = null as unknown as T;
r.error = null as unknown as E;
const data = !responseFormat
? r
: await response[responseFormat]()
.then((data) => {
if (r.ok) {
r.data = data;
} else {
r.error = data;
}
return r;
})
.catch((e) => {
r.error = e;
return r;
});
if (cancelToken) {
this.abortControllers.delete(cancelToken);
}
if (!response.ok) throw data;
return data;
}); });
}; };
} }
@@ -244,7 +217,9 @@ export class HttpClient<SecurityDataType = unknown> {
* @version v1 * @version v1
* @contact Marco Santos * @contact Marco Santos
*/ */
export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> { export class Api<
SecurityDataType extends unknown,
> extends HttpClient<SecurityDataType> {
generate = { generate = {
/** /**
* No description * No description
@@ -255,14 +230,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
*/ */
generateNifList: ( generateNifList: (
query?: { query?: {
type?: type?: NIFType;
| "Pessoa singular (1)"
| "Pessoa singular (2)"
| "Pessoa singular (3; novo em 2019)"
| "Pessoa colectiva (5)"
| "Pessoa colectiva pública (6)"
| "Empresário em nome individual (8)"
| "Pessoa colectiva irregular ou número provisório (9)";
}, },
params: RequestParams = {}, params: RequestParams = {},
) => ) =>
@@ -330,6 +298,21 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
format: "json", format: "json",
...params, ...params,
}), }),
/**
* No description
*
* @tags Generate
* @name GenerateCcList
* @request GET:/Generate/GenerateCC
*/
generateCcList: (params: RequestParams = {}) =>
this.request<string, any>({
path: `/Generate/GenerateCC`,
method: "GET",
format: "json",
...params,
}),
}; };
weatherForecast = { weatherForecast = {
/** /**