atualizacoes modulo fechamento

This commit is contained in:
Vitex Tecnologia
2026-04-26 22:54:56 -03:00
parent 7a01c6f97b
commit 6da067c641
29 changed files with 5973 additions and 3 deletions
+163
View File
@@ -0,0 +1,163 @@
import { useEffect, type ReactNode } from "react";
import { Loader2 } from "lucide-react";
import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions";
import { useAuthAccess } from "@/contexts/AuthAccessContext";
import { authMeService } from "@/services/fechamento/authMe";
import { NoAccessScreen } from "@/components/auth/NoAccessScreen";
type AuthGateProps = {
children: ReactNode;
};
const CORE_URL_FALLBACK = "https://core.hgtx.com.br";
export function AuthGate({ children }: AuthGateProps) {
const {
loading,
hasAccess,
reason,
errorMessage,
setAuthState,
} = useAuthAccess();
useEffect(() => {
let mounted = true;
const bootstrapAccess = async () => {
const isLogado = GlobalFunctions.isUsuarioLogado();
if (!isLogado) {
setAuthState({ loading: false, isAuthenticated: false, hasAccess: false });
window.location.replace(import.meta.env.VITE_BASE_URL_HGTX_CORE || CORE_URL_FALLBACK);
return;
}
let userEmail = GlobalFunctions.getUsuarioLogado().email;
if (!userEmail) {
const transferEmail = GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail);
if (typeof transferEmail === "string" && transferEmail.trim()) {
userEmail = transferEmail.trim();
}
}
if (!userEmail) {
try {
await GlobalFunctions.getToken();
await new Promise((resolve) => setTimeout(resolve, 500));
userEmail = GlobalFunctions.getUsuarioLogado().email;
} catch (error) {
console.error("Erro ao obter token para validação de acesso:", error);
}
}
if (!userEmail) {
setAuthState({
loading: false,
isAuthenticated: false,
hasAccess: false,
reason: "erro",
errorMessage: "Não foi possível identificar o e-mail do usuário logado.",
});
return;
}
try {
const me = await authMeService.getMe(userEmail);
if (!mounted) {
return;
}
if (!me) {
const estabelecimentoCodigo = String(
GlobalFunctions.getTransferProperty(TransferAreaProperties.EstabelecimentoCodigo) ?? "",
).trim();
if (estabelecimentoCodigo) {
try {
const bootstrap = await authMeService.getBootstrapStatus(estabelecimentoCodigo);
if (bootstrap.requiresBootstrap) {
setAuthState({
loading: false,
isAuthenticated: true,
hasAccess: false,
reason: "bootstrap",
papel: null,
me: null,
errorMessage: estabelecimentoCodigo,
});
return;
}
} catch {
// Mantém fluxo padrão de não cadastrado caso não consiga consultar bootstrap.
}
}
setAuthState({
loading: false,
isAuthenticated: true,
hasAccess: false,
reason: "nao_cadastrado",
papel: null,
me: null,
errorMessage: null,
});
return;
}
if (!me.estaAtivo) {
setAuthState({
loading: false,
isAuthenticated: true,
hasAccess: false,
reason: "inativo",
papel: me.papel,
me,
errorMessage: null,
});
return;
}
setAuthState({
loading: false,
isAuthenticated: true,
hasAccess: true,
papel: me.papel,
me,
reason: null,
errorMessage: null,
});
} catch (error) {
if (!mounted) {
return;
}
setAuthState({
loading: false,
isAuthenticated: true,
hasAccess: false,
reason: "erro",
errorMessage: error instanceof Error ? error.message : "Erro ao validar acesso.",
});
}
};
bootstrapAccess();
return () => {
mounted = false;
};
}, [setAuthState]);
if (loading) {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<div className="flex flex-col items-center gap-3">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Validando acesso...</p>
</div>
</div>
);
}
if (!hasAccess) {
return <NoAccessScreen reason={reason} errorMessage={errorMessage} />;
}
return <>{children}</>;
}
+125
View File
@@ -0,0 +1,125 @@
import { useState } from "react";
import { Loader2, ShieldX } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { AccessBlockReason } from "@/contexts/AuthAccessContext";
import { authMeService } from "@/services/fechamento/authMe";
type NoAccessScreenProps = {
reason: AccessBlockReason | null;
errorMessage?: string | null;
};
function getMessage(reason: AccessBlockReason | null): string {
if (reason === "inativo") {
return "Seu usuário está inativo neste módulo. Solicite a reativação ao administrador.";
}
if (reason === "erro") {
return "Não foi possível validar seu acesso agora. Tente novamente em instantes.";
}
if (reason === "bootstrap") {
return "Primeiro acesso detectado. Cadastre a unidade e o primeiro usuário administrador para iniciar o sistema.";
}
return "Você não tem acesso a este módulo no momento. Solicite acesso ao administrador.";
}
export function NoAccessScreen({ reason, errorMessage }: NoAccessScreenProps) {
const [unidadeNome, setUnidadeNome] = useState("");
const [adminNome, setAdminNome] = useState("");
const [adminEmail, setAdminEmail] = useState("");
const [saving, setSaving] = useState(false);
const estabelecimentoId = (errorMessage ?? "").trim();
const handleBootstrap = async () => {
if (!estabelecimentoId) {
return;
}
if (!unidadeNome.trim() || !adminNome.trim() || !adminEmail.trim()) {
alert("Preencha nome da unidade, nome do admin e e-mail.");
return;
}
try {
setSaving(true);
await authMeService.bootstrapInitialize({
estabelecimentoId,
unidadeNome: unidadeNome.trim(),
adminNome: adminNome.trim(),
adminEmail: adminEmail.trim(),
});
window.location.reload();
} catch (error) {
alert(error instanceof Error ? error.message : "Erro ao inicializar ambiente.");
} finally {
setSaving(false);
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<Card className="w-full max-w-xl">
<CardHeader className="text-center">
<div className="mb-3 flex justify-center">
<ShieldX className="h-10 w-10 text-destructive" />
</div>
<CardTitle>Você não tem acesso a este módulo</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-center">
<p className="text-muted-foreground">{getMessage(reason)}</p>
{reason === "bootstrap" ? (
<div className="space-y-4 rounded-md border p-4 text-left">
<div className="space-y-1">
<Label>Código do estabelecimento</Label>
<Input value={estabelecimentoId} readOnly className="font-mono bg-muted" />
</div>
<div className="space-y-1">
<Label htmlFor="bootstrap-unidade">Nome da unidade</Label>
<Input
id="bootstrap-unidade"
value={unidadeNome}
onChange={(e) => setUnidadeNome(e.target.value)}
placeholder="Ex.: Unidade Matriz"
/>
</div>
<div className="space-y-1">
<Label htmlFor="bootstrap-admin-nome">Nome do primeiro admin</Label>
<Input
id="bootstrap-admin-nome"
value={adminNome}
onChange={(e) => setAdminNome(e.target.value)}
placeholder="Ex.: João Silva"
/>
</div>
<div className="space-y-1">
<Label htmlFor="bootstrap-admin-email">E-mail do primeiro admin</Label>
<Input
id="bootstrap-admin-email"
type="email"
value={adminEmail}
onChange={(e) => setAdminEmail(e.target.value)}
placeholder="admin@empresa.com"
/>
</div>
<div className="flex justify-end">
<Button type="button" onClick={handleBootstrap} disabled={saving}>
{saving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Inicializando...
</>
) : (
"Inicializar ambiente"
)}
</Button>
</div>
</div>
) : null}
{reason !== "bootstrap" && errorMessage ? (
<p className="text-sm text-muted-foreground">{errorMessage}</p>
) : null}
</CardContent>
</Card>
</div>
);
}