melhoria filtro de conclusao fechamento
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user