melhoria filtro de conclusao fechamento

This commit is contained in:
Vitex Tecnologia
2026-05-22 17:58:35 -03:00
parent fc5fb3cc15
commit 422107331b
@@ -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<CompetenciaItem, "mes" | "ano">): 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<Date | undefined>(
undefined,
);
const [filtroDataConclusaoFimDate, setFiltroDataConclusaoFimDate] = useState<Date | undefined>(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() {
</Button>
</div>
{showFilters ? (
<div className="grid grid-cols-1 gap-3 rounded-lg border bg-muted/20 p-3 md:grid-cols-2 xl:grid-cols-4">
<div className="grid grid-cols-1 gap-3 rounded-lg border bg-muted/20 p-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
<div className="space-y-1">
<Label htmlFor="filtro-descricao">Descrição</Label>
<Input
@@ -1222,14 +1301,96 @@ export default function FechamentoDetalhes() {
/>
</div>
<div className="space-y-1">
<Label htmlFor="filtro-data-conclusao">Data de conclusão</Label>
<Input
id="filtro-data-conclusao"
type="date"
value={filtroDataConclusao}
onChange={(e) => setFiltroDataConclusao(e.target.value)}
<Label htmlFor="filtro-data-conclusao-inicio">Data Conclusão (Inicial)</Label>
<Popover>
<PopoverTrigger asChild>
<Button
id="filtro-data-conclusao-inicio"
type="button"
variant="outline"
className="h-10 w-full min-w-0 justify-start gap-2 text-left text-sm font-normal tabular-nums"
>
<CalendarIcon className="h-4 w-4 shrink-0" />
<span
className={cn(
"min-w-0 truncate",
!filtroDataConclusaoInicioDate && "text-muted-foreground",
)}
>
{filtroDataConclusaoInicioDate
? format(filtroDataConclusaoInicioDate, "dd/MM/yyyy", { locale: ptBR })
: "dd/mm/aaaa"}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={filtroDataConclusaoInicioDate}
onSelect={(date) => {
if (date) setFiltroDataConclusaoInicioDate(date);
}}
locale={ptBR}
defaultMonth={filtroDataConclusaoInicioDate ?? filtroDataConclusaoFimDate}
disabled={(date) =>
filtroDataConclusaoFimDate
? startOfDay(date) > startOfDay(filtroDataConclusaoFimDate)
: false
}
initialFocus
className="pointer-events-auto p-3"
/>
</PopoverContent>
</Popover>
</div>
<div className="space-y-1">
<Label htmlFor="filtro-data-conclusao-fim">Data Conclusão (Final)</Label>
<Popover>
<PopoverTrigger asChild>
<Button
id="filtro-data-conclusao-fim"
type="button"
variant="outline"
className="h-10 w-full min-w-0 justify-start gap-2 text-left text-sm font-normal tabular-nums"
>
<CalendarIcon className="h-4 w-4 shrink-0" />
<span
className={cn(
"min-w-0 truncate",
!filtroDataConclusaoFimDate && "text-muted-foreground",
)}
>
{filtroDataConclusaoFimDate
? format(filtroDataConclusaoFimDate, "dd/MM/yyyy", { locale: ptBR })
: "dd/mm/aaaa"}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={filtroDataConclusaoFimDate}
onSelect={(date) => {
if (date) setFiltroDataConclusaoFimDate(date);
}}
locale={ptBR}
defaultMonth={filtroDataConclusaoFimDate ?? filtroDataConclusaoInicioDate}
disabled={(date) =>
filtroDataConclusaoInicioDate
? startOfDay(date) < startOfDay(filtroDataConclusaoInicioDate)
: false
}
initialFocus
className="pointer-events-auto p-3"
/>
</PopoverContent>
</Popover>
</div>
{intervaloDataConclusaoInvalido ? (
<p className="col-span-full text-sm text-destructive">
A data final não pode ser anterior à inicial.
</p>
) : null}
</div>
) : null}
</div>