From 422107331b49be904b3e37ee0505d93f24d61319 Mon Sep 17 00:00:00 2001 From: Vitex Tecnologia Date: Fri, 22 May 2026 17:58:35 -0300 Subject: [PATCH] melhoria filtro de conclusao fechamento --- .../pages/FechamentoDetalhes.tsx | 187 ++++++++++++++++-- 1 file changed, 174 insertions(+), 13 deletions(-) diff --git a/src/modules/intelligence-score/pages/FechamentoDetalhes.tsx b/src/modules/intelligence-score/pages/FechamentoDetalhes.tsx index f24a432..855ce1b 100644 --- a/src/modules/intelligence-score/pages/FechamentoDetalhes.tsx +++ b/src/modules/intelligence-score/pages/FechamentoDetalhes.tsx @@ -1,8 +1,11 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { format, startOfDay } from "date-fns"; +import { ptBR } from "date-fns/locale"; import { AlertCircle, ArrowLeft, + CalendarIcon, CheckCircle2, ExternalLink, FileText, @@ -22,8 +25,10 @@ import { toast } from "sonner"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Dialog, DialogContent, @@ -43,6 +48,7 @@ import { useAuthAccess } from "@/contexts/AuthAccessContext"; import { fechamentoBancoPontosService } from "@/services/fechamento/bancoPontos"; import { fechamentoCompetenciasService, type CompetenciaItem } from "@/services/fechamento/competencias"; import { fechamentoFechamentosService, type FechamentoTarefaItem } from "@/services/fechamento/fechamentos"; +import { cn } from "@/lib/utils"; function formatPontos(valor: number): string { return valor.toLocaleString("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 4 }); @@ -196,6 +202,46 @@ function toFilterDate(value: string | null): string { return `${year}-${month}-${day}`; } +function dateToFilterYmd(date: Date): string { + return format(date, "yyyy-MM-dd"); +} + +function todayAsFilterDate(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function matchesConclusaoDateRange( + dataConclusao: string | null, + inicio: string, + fim: string, +): boolean { + const taskDay = toFilterDate(dataConclusao); + if (!taskDay) return false; + + let min = ""; + let max = ""; + + if (inicio && fim) { + min = inicio; + max = fim; + } else if (inicio) { + min = inicio; + max = todayAsFilterDate(); + } else if (fim) { + max = fim; + } else { + return true; + } + + if (min && taskDay < min) return false; + if (max && taskDay > max) return false; + return true; +} + function formatCompetenciaMesAno(item: Pick): string { return `${String(item.mes).padStart(2, "0")}/${item.ano}`; } @@ -275,7 +321,18 @@ export default function FechamentoDetalhes() { const [filtroDescricao, setFiltroDescricao] = useState(""); const [filtroTicket, setFiltroTicket] = useState(""); const [filtroCliente, setFiltroCliente] = useState(""); - const [filtroDataConclusao, setFiltroDataConclusao] = useState(""); + const [filtroDataConclusaoInicioDate, setFiltroDataConclusaoInicioDate] = useState( + undefined, + ); + const [filtroDataConclusaoFimDate, setFiltroDataConclusaoFimDate] = useState(undefined); + const filtroDataConclusaoInicio = useMemo( + () => (filtroDataConclusaoInicioDate ? dateToFilterYmd(filtroDataConclusaoInicioDate) : ""), + [filtroDataConclusaoInicioDate], + ); + const filtroDataConclusaoFim = useMemo( + () => (filtroDataConclusaoFimDate ? dateToFilterYmd(filtroDataConclusaoFimDate) : ""), + [filtroDataConclusaoFimDate], + ); const [competenciaStatus, setCompetenciaStatus] = useState<"em_aberto" | "concluido">("em_aberto"); const isFechado = fechamentoStatus === "fechado"; const isCompetenciaConcluida = competenciaStatus === "concluido"; @@ -339,22 +396,39 @@ export default function FechamentoDetalhes() { () => competenciasAbertas.filter((item) => item.id !== competenciaId), [competenciasAbertas, competenciaId], ); + const intervaloDataConclusaoInvalido = Boolean( + filtroDataConclusaoInicioDate && + filtroDataConclusaoFimDate && + startOfDay(filtroDataConclusaoFimDate) < startOfDay(filtroDataConclusaoInicioDate), + ); const hasActiveFilters = Boolean( filtroDescricao || filtroTicket || filtroCliente || - filtroDataConclusao, + filtroDataConclusaoInicioDate || + filtroDataConclusaoFimDate, ); const tarefasFiltradas = useMemo(() => { const descricaoFilter = normalizeTextFilter(filtroDescricao); const ticketFilter = normalizeTextFilter(filtroTicket); const clienteFilter = normalizeTextFilter(filtroCliente); + const temFiltroData = Boolean(filtroDataConclusaoInicio || filtroDataConclusaoFim); return tarefas.filter((tarefa) => { if (descricaoFilter && !normalizeTextFilter(tarefa.descricao ?? "").includes(descricaoFilter)) return false; if (ticketFilter && !normalizeTextFilter(tarefa.numeroTicket ?? "").includes(ticketFilter)) return false; if (clienteFilter && !normalizeTextFilter(tarefa.cliente ?? "").includes(clienteFilter)) return false; - if (filtroDataConclusao && toFilterDate(tarefa.dataConclusao) !== filtroDataConclusao) return false; + if (temFiltroData && !intervaloDataConclusaoInvalido) { + if ( + !matchesConclusaoDateRange( + tarefa.dataConclusao, + filtroDataConclusaoInicio, + filtroDataConclusaoFim, + ) + ) { + return false; + } + } return true; }); }, [ @@ -362,7 +436,11 @@ export default function FechamentoDetalhes() { filtroDescricao, filtroTicket, filtroCliente, - filtroDataConclusao, + filtroDataConclusaoInicioDate, + filtroDataConclusaoFimDate, + filtroDataConclusaoInicio, + filtroDataConclusaoFim, + intervaloDataConclusaoInvalido, ]); const loadTarefas = async () => { @@ -542,7 +620,8 @@ export default function FechamentoDetalhes() { setFiltroDescricao(""); setFiltroTicket(""); setFiltroCliente(""); - setFiltroDataConclusao(""); + setFiltroDataConclusaoInicioDate(undefined); + setFiltroDataConclusaoFimDate(undefined); }; const handleToggleTodasAprovadas = async () => { @@ -1193,7 +1272,7 @@ export default function FechamentoDetalhes() { {showFilters ? ( -
+
- - setFiltroDataConclusao(e.target.value)} - /> + + + + + + + { + if (date) setFiltroDataConclusaoInicioDate(date); + }} + locale={ptBR} + defaultMonth={filtroDataConclusaoInicioDate ?? filtroDataConclusaoFimDate} + disabled={(date) => + filtroDataConclusaoFimDate + ? startOfDay(date) > startOfDay(filtroDataConclusaoFimDate) + : false + } + initialFocus + className="pointer-events-auto p-3" + /> + +
+
+ + + + + + + { + if (date) setFiltroDataConclusaoFimDate(date); + }} + locale={ptBR} + defaultMonth={filtroDataConclusaoFimDate ?? filtroDataConclusaoInicioDate} + disabled={(date) => + filtroDataConclusaoInicioDate + ? startOfDay(date) < startOfDay(filtroDataConclusaoInicioDate) + : false + } + initialFocus + className="pointer-events-auto p-3" + /> + + +
+ {intervaloDataConclusaoInvalido ? ( +

+ A data final não pode ser anterior à inicial. +

+ ) : null}
) : null}