From 343aae602f5d516848d71e5685c0b724dc6b9d6c Mon Sep 17 00:00:00 2001 From: Vitex Tecnologia Date: Thu, 7 May 2026 11:34:55 -0300 Subject: [PATCH] =?UTF-8?q?novas=20atualiza=C3=A7=C3=B5es=20na=20exportaca?= =?UTF-8?q?o=20planilha,=20tela=20de=20listar=20fechamento,=20filtros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 14 +- src/components/fechamento/UnidadeGate.tsx | 4 +- src/modules/fechamento-hgtx/App.tsx | 4 +- .../components/layout/AppSidebar.tsx | 2 +- .../fechamento-hgtx/pages/BancoPontos.tsx | 2 +- .../pages/BancoPontosExtrato.tsx | 4 +- .../pages/CompetenciaFechamentos.tsx | 17 +- .../fechamento-hgtx/pages/Configuracoes.tsx | 2 +- .../pages/FechamentoDetalhes.tsx | 255 ++++++++++++++---- .../fechamento-hgtx/pages/Fechamentos.tsx | 2 +- .../fechamento-hgtx/pages/MeuFechamento.tsx | 4 +- .../pages/MeuFechamentoBancoPontos.tsx | 4 +- .../fechamento-hgtx/pages/NotFound.tsx | 2 +- 13 files changed, 228 insertions(+), 88 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f506e32..8a2211b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,13 +11,18 @@ import React from "react"; import IntelligenceIAApp from "./modules/intelligence-ia/App"; import FechamentoHgtxApp from "./modules/fechamento-hgtx/App"; +const INTELLIGENCE_SCORE_PREFIX = "/intelligence-score"; const FECHAMENTO_LEGACY_PREFIX = "/fechamento-hgtx"; +const FECHAMENTO_PREFIX = "/fechamento"; -/** Redireciona URLs antigas `/fechamento-hgtx/...` para `/fechamento/...` (mesmo sufixo, query e hash). */ +/** Redireciona URLs antigas para `/intelligence-score/...` (mesmo sufixo, query e hash). */ function FechamentoLegacyRedirect() { const location = useLocation(); - const tail = location.pathname.slice(FECHAMENTO_LEGACY_PREFIX.length); - const to = `/fechamento${tail}`; + const fromPrefix = location.pathname.startsWith(FECHAMENTO_LEGACY_PREFIX) + ? FECHAMENTO_LEGACY_PREFIX + : FECHAMENTO_PREFIX; + const tail = location.pathname.slice(fromPrefix.length); + const to = `${INTELLIGENCE_SCORE_PREFIX}${tail}`; return ; } @@ -44,7 +49,8 @@ const App = () => ( /> } /> - } /> + } /> + } /> } /> } /> diff --git a/src/components/fechamento/UnidadeGate.tsx b/src/components/fechamento/UnidadeGate.tsx index 3f31f73..7098500 100644 --- a/src/components/fechamento/UnidadeGate.tsx +++ b/src/components/fechamento/UnidadeGate.tsx @@ -105,7 +105,7 @@ export function UnidadeGate({ children }: UnidadeGateProps) {

O código do estabelecimento não foi enviado pelo ambiente (TransferArea). Abra o módulo - Fechamento HGTX a partir do Codex com o estabelecimento carregado no transfer. + HGTX Intelligence Score a partir do Codex com o estabelecimento carregado no transfer.

@@ -134,7 +134,7 @@ export function UnidadeGate({ children }: UnidadeGateProps) { Cadastre o nome da unidade em Configurações (integração com o código atual do transfer).

) : ( diff --git a/src/modules/fechamento-hgtx/App.tsx b/src/modules/fechamento-hgtx/App.tsx index e725629..3614a79 100644 --- a/src/modules/fechamento-hgtx/App.tsx +++ b/src/modules/fechamento-hgtx/App.tsx @@ -23,7 +23,7 @@ function RequireAdminRoute({ children }: { children: JSX.Element }) { return children; } - return ; + return ; } function RequireFechamentoDetalheRoute({ children }: { children: JSX.Element }) { @@ -39,7 +39,7 @@ function RequireFechamentoDetalheRoute({ children }: { children: JSX.Element }) return children; } - return ; + return ; } const FechamentoHgtxApp = () => { diff --git a/src/modules/fechamento-hgtx/components/layout/AppSidebar.tsx b/src/modules/fechamento-hgtx/components/layout/AppSidebar.tsx index bc98cb2..596c6e8 100644 --- a/src/modules/fechamento-hgtx/components/layout/AppSidebar.tsx +++ b/src/modules/fechamento-hgtx/components/layout/AppSidebar.tsx @@ -97,7 +97,7 @@ export function AppSidebar() {
-

Fechamento HGTX

+

HGTX Intelligence Score

{isAdmin ? "Painel Admin" : "Painel Parceiro"}

diff --git a/src/modules/fechamento-hgtx/pages/BancoPontos.tsx b/src/modules/fechamento-hgtx/pages/BancoPontos.tsx index 27b7ea1..e4ce6c7 100644 --- a/src/modules/fechamento-hgtx/pages/BancoPontos.tsx +++ b/src/modules/fechamento-hgtx/pages/BancoPontos.tsx @@ -213,7 +213,7 @@ export default function BancoPontos() { variant="outline" className="border-border bg-background text-foreground hover:bg-muted/50 hover:text-foreground" onClick={() => - navigate(`/fechamento/banco-pontos/${row.parceiroId}`, { + navigate(`/intelligence-score/banco-pontos/${row.parceiroId}`, { state: { nomeExibicao: getDisplayNome(row) }, }) } diff --git a/src/modules/fechamento-hgtx/pages/BancoPontosExtrato.tsx b/src/modules/fechamento-hgtx/pages/BancoPontosExtrato.tsx index 7a7fd5b..3adb508 100644 --- a/src/modules/fechamento-hgtx/pages/BancoPontosExtrato.tsx +++ b/src/modules/fechamento-hgtx/pages/BancoPontosExtrato.tsx @@ -154,7 +154,7 @@ export default function BancoPontosExtrato() {
- @@ -275,7 +275,7 @@ export default function BancoPontosExtrato() { variant="ghost" className="gap-1 text-foreground hover:bg-zinc-200 hover:text-foreground dark:hover:bg-zinc-700" onClick={() => - navigate(`/fechamento/fechamentos/${linha.fechamentoId}`, { + navigate(`/intelligence-score/fechamentos/${linha.fechamentoId}`, { state: { competenciaId: undefined, status: undefined }, }) } diff --git a/src/modules/fechamento-hgtx/pages/CompetenciaFechamentos.tsx b/src/modules/fechamento-hgtx/pages/CompetenciaFechamentos.tsx index 88e8638..06bde7e 100644 --- a/src/modules/fechamento-hgtx/pages/CompetenciaFechamentos.tsx +++ b/src/modules/fechamento-hgtx/pages/CompetenciaFechamentos.tsx @@ -36,15 +36,6 @@ function getDisplayNome(row: FechamentoDaCompetenciaItem): string { return row.parceiroNome; } -function formatHoras(minutos: number | null): string { - if (!minutos || minutos <= 0) return "0h"; - const horas = Math.floor(minutos / 60); - const mins = Math.round(minutos % 60); - if (horas === 0) return `${mins}min`; - if (mins === 0) return `${horas}h`; - return `${horas}h ${mins}min`; -} - function AsanaImportLoadingCard() { return (
- @@ -334,7 +325,6 @@ export default function CompetenciaFechamentos() { Logo Nome Pontuação Total - Horas Total Status Ações @@ -342,7 +332,7 @@ export default function CompetenciaFechamentos() { {loading ? ( - +
{getDisplayNome(row)} {row.pontuacaoTotalEntregue} - {formatHoras(row.horasTotal * 60)} - navigate(`/fechamento/fechamentos/${row.id}`, { + navigate(`/intelligence-score/fechamentos/${row.id}`, { state: { competenciaId, status: row.status }, }) } diff --git a/src/modules/fechamento-hgtx/pages/Configuracoes.tsx b/src/modules/fechamento-hgtx/pages/Configuracoes.tsx index af9783e..2d0acc5 100644 --- a/src/modules/fechamento-hgtx/pages/Configuracoes.tsx +++ b/src/modules/fechamento-hgtx/pages/Configuracoes.tsx @@ -211,7 +211,7 @@ export default function Configuracoes() { await fechamentoUnidadesService.createUnidade({ nome, estabelecimentoId: codigo }); toast.success("Unidade cadastrada."); clearCommanderUnidadeIdCache(); - navigate("/fechamento", { replace: true }); + navigate("/intelligence-score", { replace: true }); } } catch (error) { const message = error instanceof Error ? error.message : "Erro ao salvar unidade."; diff --git a/src/modules/fechamento-hgtx/pages/FechamentoDetalhes.tsx b/src/modules/fechamento-hgtx/pages/FechamentoDetalhes.tsx index 4cc294f..ad71d29 100644 --- a/src/modules/fechamento-hgtx/pages/FechamentoDetalhes.tsx +++ b/src/modules/fechamento-hgtx/pages/FechamentoDetalhes.tsx @@ -1,9 +1,8 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { ArrowLeft, CheckCircle2, - Clock3, ExternalLink, ListChecks, Loader2, @@ -41,23 +40,6 @@ import { fechamentoBancoPontosService } from "@/services/fechamento/bancoPontos" import { fechamentoCompetenciasService, type CompetenciaItem } from "@/services/fechamento/competencias"; import { fechamentoFechamentosService, type FechamentoTarefaItem } from "@/services/fechamento/fechamentos"; -function formatHoras(minutos: number | null): string { - if (!minutos || minutos <= 0) return "0h"; - const horas = Math.floor(minutos / 60); - const mins = Math.round(minutos % 60); - if (horas === 0) return `${mins}min`; - if (mins === 0) return `${horas}h`; - return `${horas}h ${mins}min`; -} - -function formatHorasResumo(minutos: number): string { - const horas = Math.floor(minutos / 60); - const mins = Math.round(minutos % 60); - if (horas === 0) return `${mins}min`; - if (mins === 0) return `${horas}h`; - return `${horas}h ${mins}min`; -} - function formatPontos(valor: number): string { return valor.toLocaleString("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 4 }); } @@ -146,10 +128,33 @@ function formatDateTime(value: string | null): string { }); } +function normalizeTextFilter(value: string): string { + return value.trim().toLocaleLowerCase("pt-BR"); +} + +function toFilterDate(value: string | null): string { + if (!value) return ""; + const isoMatch = value.match(/^\d{4}-\d{2}-\d{2}/); + if (isoMatch) return isoMatch[0]; + + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) return ""; + const year = parsed.getFullYear(); + const month = String(parsed.getMonth() + 1).padStart(2, "0"); + const day = String(parsed.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + function formatCompetenciaMesAno(item: Pick): string { return `${String(item.mes).padStart(2, "0")}/${item.ano}`; } +function getTipoLancamentoLabel(tipo: string): string { + if (tipo === "bonus") return "Bônus Comercial"; + if (tipo === "desconto") return "Desconto Comercial"; + return tipo; +} + type FechamentoDetalhesLocationState = { competenciaId?: string; status?: "em_aberto" | "fechado"; @@ -206,6 +211,15 @@ export default function FechamentoDetalhes() { const [edicaoCliente, setEdicaoCliente] = useState(""); const [edicaoTempoMinutos, setEdicaoTempoMinutos] = useState(""); const [edicaoPontuacao, setEdicaoPontuacao] = useState(""); + const tableScrollRef = useRef(null); + const stickyScrollRef = useRef(null); + const stickyScrollSpacerRef = useRef(null); + const [showStickyScrollbar, setShowStickyScrollbar] = useState(false); + const [showFilters, setShowFilters] = useState(false); + const [filtroDescricao, setFiltroDescricao] = useState(""); + const [filtroTicket, setFiltroTicket] = useState(""); + const [filtroCliente, setFiltroCliente] = useState(""); + const [filtroDataConclusao, setFiltroDataConclusao] = useState(""); const [competenciaStatus, setCompetenciaStatus] = useState<"em_aberto" | "concluido">("em_aberto"); const isFechado = fechamentoStatus === "fechado"; const isCompetenciaConcluida = competenciaStatus === "concluido"; @@ -215,10 +229,8 @@ export default function FechamentoDetalhes() { const totais = useMemo(() => { const aprovadas = tarefas.filter((t) => t.estaRevisada); const pontos = aprovadas.reduce((acc, t) => acc + Number(t.pontuacao || 0), 0); - const minutos = aprovadas.reduce((acc, t) => acc + Number(t.tempoMinutos || 0), 0); return { pontos, - horas: formatHorasResumo(minutos), aprovadas: aprovadas.length, }; }, [tarefas]); @@ -244,6 +256,31 @@ export default function FechamentoDetalhes() { () => competenciasAbertas.filter((item) => item.id !== competenciaId), [competenciasAbertas, competenciaId], ); + const hasActiveFilters = Boolean( + filtroDescricao || + filtroTicket || + filtroCliente || + filtroDataConclusao, + ); + const tarefasFiltradas = useMemo(() => { + const descricaoFilter = normalizeTextFilter(filtroDescricao); + const ticketFilter = normalizeTextFilter(filtroTicket); + const clienteFilter = normalizeTextFilter(filtroCliente); + + 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; + return true; + }); + }, [ + tarefas, + filtroDescricao, + filtroTicket, + filtroCliente, + filtroDataConclusao, + ]); const loadTarefas = async () => { try { @@ -344,6 +381,51 @@ export default function FechamentoDetalhes() { } }, [isConcluirOpen, parceiroId]); + useEffect(() => { + const main = tableScrollRef.current; + const sticky = stickyScrollRef.current; + if (!main || !sticky) return; + + let syncingFromMain = false; + let syncingFromSticky = false; + + const syncMetricsAndVisibility = () => { + const spacer = stickyScrollSpacerRef.current; + const canScrollX = main.scrollWidth > main.clientWidth + 1; + if (spacer) spacer.style.width = `${main.scrollWidth}px`; + if (sticky.scrollLeft !== main.scrollLeft) sticky.scrollLeft = main.scrollLeft; + setShowStickyScrollbar(canScrollX); + }; + + const onMainScroll = () => { + if (syncingFromSticky) return; + syncingFromMain = true; + sticky.scrollLeft = main.scrollLeft; + syncingFromMain = false; + }; + + const onStickyScroll = () => { + if (syncingFromMain) return; + syncingFromSticky = true; + main.scrollLeft = sticky.scrollLeft; + syncingFromSticky = false; + }; + + syncMetricsAndVisibility(); + main.addEventListener("scroll", onMainScroll, { passive: true }); + sticky.addEventListener("scroll", onStickyScroll, { passive: true }); + window.addEventListener("resize", syncMetricsAndVisibility); + const resizeObserver = new ResizeObserver(() => syncMetricsAndVisibility()); + resizeObserver.observe(main); + + return () => { + main.removeEventListener("scroll", onMainScroll); + sticky.removeEventListener("scroll", onStickyScroll); + window.removeEventListener("resize", syncMetricsAndVisibility); + resizeObserver.disconnect(); + }; + }, [tarefas.length, loading]); + const handleToggleAprovada = async (tarefa: FechamentoTarefaItem, approved: boolean) => { if (isCompetenciaConcluida) { toastBloqueioCompetenciaConcluida(); @@ -365,6 +447,13 @@ export default function FechamentoDetalhes() { ); }; + const handleLimparFiltros = () => { + setFiltroDescricao(""); + setFiltroTicket(""); + setFiltroCliente(""); + setFiltroDataConclusao(""); + }; + const handleToggleTodasAprovadas = async () => { if (isCompetenciaConcluida) { toastBloqueioCompetenciaConcluida(); @@ -501,7 +590,7 @@ export default function FechamentoDetalhes() { }); toast.success(`Fechamento concluído. Banco de pontos: ${formatPontosAte2Casas(data.pontuacaoBanco)}.`); setFechamentoStatus("fechado"); - navigate(`/fechamento/competencias/${data.competenciaId}`); + navigate(`/intelligence-score/competencias/${data.competenciaId}`); } catch (error) { const message = error instanceof Error ? error.message : "Erro ao concluir fechamento."; toast.error(message); @@ -824,7 +913,7 @@ export default function FechamentoDetalhes() {
{!loading ? ( -
+
{isCompetenciaConcluida ? (
Competência concluída. Reabra a competência para editar este fechamento. @@ -857,15 +946,6 @@ export default function FechamentoDetalhes() { {formatPontos(totais.pontos)} - - -
- Horas aprovadas - -
- {totais.horas} -
-
) : null}
@@ -881,24 +961,78 @@ export default function FechamentoDetalhes() { ) : (
{!loading ? ( -
- +
+
+ + + +
+ {showFilters ? ( +
+
+ + setFiltroDescricao(e.target.value)} + placeholder="Digite parte da descrição" + /> +
+
+ + setFiltroTicket(e.target.value)} + placeholder="Ex.: TKT-1234" + /> +
+
+ + setFiltroCliente(e.target.value)} + placeholder="Nome do cliente" + /> +
+
+ + setFiltroDataConclusao(e.target.value)} + /> +
+
+ ) : null}
) : null} -
+
@@ -911,7 +1045,6 @@ export default function FechamentoDetalhes() { Data vencimento Data conclusão Etiquetas - Horas Pontuação Ações @@ -919,12 +1052,18 @@ export default function FechamentoDetalhes() { {loading ? ( - + Carregando tarefas... + ) : tarefasFiltradas.length === 0 ? ( + + + Nenhuma tarefa encontrada com os filtros aplicados. + + ) : ( - tarefas.map((tarefa) => ( + tarefasFiltradas.map((tarefa) => (
@@ -938,7 +1077,7 @@ export default function FechamentoDetalhes() { {tarefa.numeroTicket || "—"} {tarefa.descricao} {tarefa.cliente || "—"} - {tarefa.tipo} + {getTipoLancamentoLabel(tarefa.tipo)} {formatDateOnly(tarefa.dataInicio)} {formatDateOnly(tarefa.dataVencimento)} {formatDateTime(tarefa.dataConclusao)} @@ -955,7 +1094,6 @@ export default function FechamentoDetalhes() { "—" )} - {formatHoras(tarefa.tempoMinutos)} {Number(tarefa.pontuacao || 0)}
@@ -1014,6 +1152,13 @@ export default function FechamentoDetalhes() {
+ {showStickyScrollbar ? ( +
+
+
+
+
+ ) : null}
)}
@@ -1038,8 +1183,8 @@ export default function FechamentoDetalhes() { - Bonificação - Desconto + {getTipoLancamentoLabel("bonus")} + {getTipoLancamentoLabel("desconto")}
diff --git a/src/modules/fechamento-hgtx/pages/Fechamentos.tsx b/src/modules/fechamento-hgtx/pages/Fechamentos.tsx index ad1ed4b..3e60a31 100644 --- a/src/modules/fechamento-hgtx/pages/Fechamentos.tsx +++ b/src/modules/fechamento-hgtx/pages/Fechamentos.tsx @@ -258,7 +258,7 @@ export default function Fechamentos() { @@ -266,7 +266,7 @@ export default function MeuFechamento() { variant="outline" disabled={!item.fechamento || item.fechamento.status !== "fechado"} onClick={() => - navigate(`/fechamento/fechamentos/${item.fechamento?.id}`, { + navigate(`/intelligence-score/fechamentos/${item.fechamento?.id}`, { state: { competenciaId: item.competenciaId, status: item.fechamento?.status, diff --git a/src/modules/fechamento-hgtx/pages/MeuFechamentoBancoPontos.tsx b/src/modules/fechamento-hgtx/pages/MeuFechamentoBancoPontos.tsx index 5261226..93b2979 100644 --- a/src/modules/fechamento-hgtx/pages/MeuFechamentoBancoPontos.tsx +++ b/src/modules/fechamento-hgtx/pages/MeuFechamentoBancoPontos.tsx @@ -114,7 +114,7 @@ export default function MeuFechamentoBancoPontos() { if (semVinculo) { return (
- @@ -133,7 +133,7 @@ export default function MeuFechamentoBancoPontos() {
- diff --git a/src/modules/fechamento-hgtx/pages/NotFound.tsx b/src/modules/fechamento-hgtx/pages/NotFound.tsx index 5733afa..32d3446 100644 --- a/src/modules/fechamento-hgtx/pages/NotFound.tsx +++ b/src/modules/fechamento-hgtx/pages/NotFound.tsx @@ -16,7 +16,7 @@ export default function NotFound() {

404

Rota não encontrada neste módulo.

- + Voltar para Fechamentos