melhoria filtro de conclusao fechamento
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { format, startOfDay } from "date-fns";
|
||||||
|
import { ptBR } from "date-fns/locale";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
CalendarIcon,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
FileText,
|
FileText,
|
||||||
@@ -22,8 +25,10 @@ import { toast } from "sonner";
|
|||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -43,6 +48,7 @@ import { useAuthAccess } from "@/contexts/AuthAccessContext";
|
|||||||
import { fechamentoBancoPontosService } from "@/services/fechamento/bancoPontos";
|
import { fechamentoBancoPontosService } from "@/services/fechamento/bancoPontos";
|
||||||
import { fechamentoCompetenciasService, type CompetenciaItem } from "@/services/fechamento/competencias";
|
import { fechamentoCompetenciasService, type CompetenciaItem } from "@/services/fechamento/competencias";
|
||||||
import { fechamentoFechamentosService, type FechamentoTarefaItem } from "@/services/fechamento/fechamentos";
|
import { fechamentoFechamentosService, type FechamentoTarefaItem } from "@/services/fechamento/fechamentos";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function formatPontos(valor: number): string {
|
function formatPontos(valor: number): string {
|
||||||
return valor.toLocaleString("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 4 });
|
return valor.toLocaleString("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 4 });
|
||||||
@@ -196,6 +202,46 @@ function toFilterDate(value: string | null): string {
|
|||||||
return `${year}-${month}-${day}`;
|
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 {
|
function formatCompetenciaMesAno(item: Pick<CompetenciaItem, "mes" | "ano">): string {
|
||||||
return `${String(item.mes).padStart(2, "0")}/${item.ano}`;
|
return `${String(item.mes).padStart(2, "0")}/${item.ano}`;
|
||||||
}
|
}
|
||||||
@@ -275,7 +321,18 @@ export default function FechamentoDetalhes() {
|
|||||||
const [filtroDescricao, setFiltroDescricao] = useState("");
|
const [filtroDescricao, setFiltroDescricao] = useState("");
|
||||||
const [filtroTicket, setFiltroTicket] = useState("");
|
const [filtroTicket, setFiltroTicket] = useState("");
|
||||||
const [filtroCliente, setFiltroCliente] = 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 [competenciaStatus, setCompetenciaStatus] = useState<"em_aberto" | "concluido">("em_aberto");
|
||||||
const isFechado = fechamentoStatus === "fechado";
|
const isFechado = fechamentoStatus === "fechado";
|
||||||
const isCompetenciaConcluida = competenciaStatus === "concluido";
|
const isCompetenciaConcluida = competenciaStatus === "concluido";
|
||||||
@@ -339,22 +396,39 @@ export default function FechamentoDetalhes() {
|
|||||||
() => competenciasAbertas.filter((item) => item.id !== competenciaId),
|
() => competenciasAbertas.filter((item) => item.id !== competenciaId),
|
||||||
[competenciasAbertas, competenciaId],
|
[competenciasAbertas, competenciaId],
|
||||||
);
|
);
|
||||||
|
const intervaloDataConclusaoInvalido = Boolean(
|
||||||
|
filtroDataConclusaoInicioDate &&
|
||||||
|
filtroDataConclusaoFimDate &&
|
||||||
|
startOfDay(filtroDataConclusaoFimDate) < startOfDay(filtroDataConclusaoInicioDate),
|
||||||
|
);
|
||||||
const hasActiveFilters = Boolean(
|
const hasActiveFilters = Boolean(
|
||||||
filtroDescricao ||
|
filtroDescricao ||
|
||||||
filtroTicket ||
|
filtroTicket ||
|
||||||
filtroCliente ||
|
filtroCliente ||
|
||||||
filtroDataConclusao,
|
filtroDataConclusaoInicioDate ||
|
||||||
|
filtroDataConclusaoFimDate,
|
||||||
);
|
);
|
||||||
const tarefasFiltradas = useMemo(() => {
|
const tarefasFiltradas = useMemo(() => {
|
||||||
const descricaoFilter = normalizeTextFilter(filtroDescricao);
|
const descricaoFilter = normalizeTextFilter(filtroDescricao);
|
||||||
const ticketFilter = normalizeTextFilter(filtroTicket);
|
const ticketFilter = normalizeTextFilter(filtroTicket);
|
||||||
const clienteFilter = normalizeTextFilter(filtroCliente);
|
const clienteFilter = normalizeTextFilter(filtroCliente);
|
||||||
|
const temFiltroData = Boolean(filtroDataConclusaoInicio || filtroDataConclusaoFim);
|
||||||
|
|
||||||
return tarefas.filter((tarefa) => {
|
return tarefas.filter((tarefa) => {
|
||||||
if (descricaoFilter && !normalizeTextFilter(tarefa.descricao ?? "").includes(descricaoFilter)) return false;
|
if (descricaoFilter && !normalizeTextFilter(tarefa.descricao ?? "").includes(descricaoFilter)) return false;
|
||||||
if (ticketFilter && !normalizeTextFilter(tarefa.numeroTicket ?? "").includes(ticketFilter)) return false;
|
if (ticketFilter && !normalizeTextFilter(tarefa.numeroTicket ?? "").includes(ticketFilter)) return false;
|
||||||
if (clienteFilter && !normalizeTextFilter(tarefa.cliente ?? "").includes(clienteFilter)) 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;
|
return true;
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
@@ -362,7 +436,11 @@ export default function FechamentoDetalhes() {
|
|||||||
filtroDescricao,
|
filtroDescricao,
|
||||||
filtroTicket,
|
filtroTicket,
|
||||||
filtroCliente,
|
filtroCliente,
|
||||||
filtroDataConclusao,
|
filtroDataConclusaoInicioDate,
|
||||||
|
filtroDataConclusaoFimDate,
|
||||||
|
filtroDataConclusaoInicio,
|
||||||
|
filtroDataConclusaoFim,
|
||||||
|
intervaloDataConclusaoInvalido,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const loadTarefas = async () => {
|
const loadTarefas = async () => {
|
||||||
@@ -542,7 +620,8 @@ export default function FechamentoDetalhes() {
|
|||||||
setFiltroDescricao("");
|
setFiltroDescricao("");
|
||||||
setFiltroTicket("");
|
setFiltroTicket("");
|
||||||
setFiltroCliente("");
|
setFiltroCliente("");
|
||||||
setFiltroDataConclusao("");
|
setFiltroDataConclusaoInicioDate(undefined);
|
||||||
|
setFiltroDataConclusaoFimDate(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleTodasAprovadas = async () => {
|
const handleToggleTodasAprovadas = async () => {
|
||||||
@@ -1193,7 +1272,7 @@ export default function FechamentoDetalhes() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{showFilters ? (
|
{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">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="filtro-descricao">Descrição</Label>
|
<Label htmlFor="filtro-descricao">Descrição</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -1222,14 +1301,96 @@ export default function FechamentoDetalhes() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="filtro-data-conclusao">Data de conclusão</Label>
|
<Label htmlFor="filtro-data-conclusao-inicio">Data Conclusão (Inicial)</Label>
|
||||||
<Input
|
<Popover>
|
||||||
id="filtro-data-conclusao"
|
<PopoverTrigger asChild>
|
||||||
type="date"
|
<Button
|
||||||
value={filtroDataConclusao}
|
id="filtro-data-conclusao-inicio"
|
||||||
onChange={(e) => setFiltroDataConclusao(e.target.value)}
|
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>
|
||||||
|
<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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user