atualizacoes modulo fechamento
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ClipboardList, ExternalLink, Plus } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { fechamentoCompetenciasService, type CompetenciaItem } from "@/services/fechamento/competencias";
|
||||
|
||||
const meses = [
|
||||
"Janeiro",
|
||||
"Fevereiro",
|
||||
"Março",
|
||||
"Abril",
|
||||
"Maio",
|
||||
"Junho",
|
||||
"Julho",
|
||||
"Agosto",
|
||||
"Setembro",
|
||||
"Outubro",
|
||||
"Novembro",
|
||||
"Dezembro",
|
||||
];
|
||||
|
||||
function formatCompetenciaMes(mes: number, ano: number): string {
|
||||
return `${meses[mes - 1] ?? `Mês ${mes}`} / ${ano}`;
|
||||
}
|
||||
|
||||
export default function Fechamentos() {
|
||||
const navigate = useNavigate();
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [allCompetencias, setAllCompetencias] = useState<CompetenciaItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [anoSelecionado, setAnoSelecionado] = useState<string>("");
|
||||
const [modalAberto, setModalAberto] = useState(false);
|
||||
const [mesSelecionado, setMesSelecionado] = useState<string>("");
|
||||
const [anoNovo, setAnoNovo] = useState<string>("");
|
||||
const [criando, setCriando] = useState(false);
|
||||
|
||||
const anosDisponiveis = useMemo(() => {
|
||||
const anosUnicos = [...new Set(allCompetencias.map((c) => c.ano))];
|
||||
if (!anosUnicos.includes(currentYear)) {
|
||||
anosUnicos.push(currentYear);
|
||||
}
|
||||
return anosUnicos.sort((a, b) => b - a).map(String);
|
||||
}, [allCompetencias, currentYear]);
|
||||
|
||||
const competenciasFiltradas = useMemo(() => {
|
||||
if (!anoSelecionado) return [];
|
||||
return allCompetencias.filter((c) => c.ano === Number(anoSelecionado));
|
||||
}, [allCompetencias, anoSelecionado]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadCompetencias = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await fechamentoCompetenciasService.listarCompetencias({});
|
||||
if (!cancelled) {
|
||||
setAllCompetencias(data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
const message = error instanceof Error ? error.message : "Erro ao carregar competências.";
|
||||
toast.error(message);
|
||||
setAllCompetencias([]);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void loadCompetencias();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (anosDisponiveis.length > 0 && !anoSelecionado) {
|
||||
const defaultAno = anosDisponiveis.includes(String(currentYear))
|
||||
? String(currentYear)
|
||||
: anosDisponiveis[0];
|
||||
setAnoSelecionado(defaultAno);
|
||||
}
|
||||
}, [anosDisponiveis, anoSelecionado, currentYear]);
|
||||
|
||||
const handleCriarCompetencia = async () => {
|
||||
const mes = Number(mesSelecionado);
|
||||
const ano = Number(anoNovo);
|
||||
if (!mes || !ano) {
|
||||
toast.error("Selecione mês e ano.");
|
||||
return;
|
||||
}
|
||||
setCriando(true);
|
||||
try {
|
||||
const nova = await fechamentoCompetenciasService.criarCompetencia({ mes, ano });
|
||||
setAllCompetencias((prev) => [...prev, nova]);
|
||||
setModalAberto(false);
|
||||
setMesSelecionado("");
|
||||
setAnoNovo("");
|
||||
toast.success("Competência criada com sucesso.");
|
||||
setAnoSelecionado(String(ano));
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Erro ao criar competência.";
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setCriando(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col bg-background pb-16 md:pb-0">
|
||||
<div className="border-b border-border p-3 md:p-6">
|
||||
<div className="mb-4 flex flex-col items-start justify-between gap-3 md:flex-row md:items-center">
|
||||
<h1 className="flex items-center gap-2 text-xl font-bold text-foreground md:text-3xl">
|
||||
<ClipboardList className="h-5 w-5 md:h-6 md:w-6" />
|
||||
Fechamentos
|
||||
</h1>
|
||||
<Dialog open={modalAberto} onOpenChange={setModalAberto}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Nova Competência
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Nova Competência</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-4 py-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">Mês</label>
|
||||
<Select value={mesSelecionado} onValueChange={setMesSelecionado}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione o mês" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{meses.map ((m, i) => (
|
||||
<SelectItem key={i + 1} value={String(i + 1)}>
|
||||
{m}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">Ano</label>
|
||||
<Select value={anoNovo} onValueChange={setAnoNovo}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecione o ano" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{anosDisponiveis.map((ano) => (
|
||||
<SelectItem key={ano} value={ano}>
|
||||
{ano}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setModalAberto(false)} disabled={criando}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleCriarCompetencia()} disabled={criando || !mesSelecionado || !anoNovo}>
|
||||
{criando ? "Criando..." : "Criar Competência"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Ano:</span>
|
||||
<Select value={anoSelecionado} onValueChange={setAnoSelecionado}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{anosDisponiveis.map((ano) => (
|
||||
<SelectItem key={ano} value={ano}>
|
||||
{ano}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-3 md:p-6">
|
||||
{!loading && competenciasFiltradas.length === 0 ? (
|
||||
<Card className="mx-auto mt-12 max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>Nenhuma competência encontrada</CardTitle>
|
||||
<CardDescription>Não há competências cadastradas para o ano selecionado.</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="min-w-[220px]">Mês</TableHead>
|
||||
<TableHead className="min-w-[180px]">Quantidade de Fechamentos</TableHead>
|
||||
<TableHead className="min-w-[120px]">Status</TableHead>
|
||||
<TableHead className="text-center">Acessar</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="py-8 text-center text-muted-foreground">
|
||||
Carregando competências...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
competenciasFiltradas.map((competencia) => (
|
||||
<TableRow key={competencia.id}>
|
||||
<TableCell className="font-medium">
|
||||
{formatCompetenciaMes(competencia.mes, competencia.ano)}
|
||||
</TableCell>
|
||||
<TableCell>{competencia.quantidadeFechamentos}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
className={
|
||||
competencia.status === "concluido"
|
||||
? "bg-green-500 hover:bg-green-500/80 text-white border-green-500"
|
||||
: "bg-yellow-500 hover:bg-yellow-500/80 text-black border-yellow-500"
|
||||
}
|
||||
>
|
||||
{competencia.status === "concluido" ? "Concluída" : "Em aberto"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/fechamento-hgtx/competencias/${competencia.id}`)}
|
||||
>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Acessar
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user