novas atualizações na exportacao planilha, tela de listar fechamento, filtros
This commit is contained in:
+10
-4
@@ -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 <Navigate to={`${to}${location.search}${location.hash}`} replace />;
|
||||
}
|
||||
|
||||
@@ -44,7 +49,8 @@ const App = () => (
|
||||
/>
|
||||
|
||||
<Route path="/intelligence-ia/*" element={<IntelligenceIAApp />} />
|
||||
<Route path="/fechamento/*" element={<FechamentoHgtxApp />} />
|
||||
<Route path="/intelligence-score/*" element={<FechamentoHgtxApp />} />
|
||||
<Route path="/fechamento/*" element={<FechamentoLegacyRedirect />} />
|
||||
<Route path="/fechamento-hgtx/*" element={<FechamentoLegacyRedirect />} />
|
||||
|
||||
<Route path="/*" element={<Index />} />
|
||||
|
||||
@@ -105,7 +105,7 @@ export function UnidadeGate({ children }: UnidadeGateProps) {
|
||||
<CardContent className="space-y-4 text-center text-muted-foreground">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -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).
|
||||
</p>
|
||||
<Button asChild>
|
||||
<Link to="/fechamento/configuracoes#unidade">Ir para Configurações</Link>
|
||||
<Link to="/intelligence-score/configuracoes#unidade">Ir para Configurações</Link>
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -23,7 +23,7 @@ function RequireAdminRoute({ children }: { children: JSX.Element }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <Navigate to="/fechamento/meu-fechamento" replace />;
|
||||
return <Navigate to="/intelligence-score/meu-fechamento" replace />;
|
||||
}
|
||||
|
||||
function RequireFechamentoDetalheRoute({ children }: { children: JSX.Element }) {
|
||||
@@ -39,7 +39,7 @@ function RequireFechamentoDetalheRoute({ children }: { children: JSX.Element })
|
||||
return children;
|
||||
}
|
||||
|
||||
return <Navigate to="/fechamento/meu-fechamento" replace />;
|
||||
return <Navigate to="/intelligence-score/meu-fechamento" replace />;
|
||||
}
|
||||
|
||||
const FechamentoHgtxApp = () => {
|
||||
|
||||
@@ -97,7 +97,7 @@ export function AppSidebar() {
|
||||
<ClipboardList className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div className="min-w-0 animate-fade-in">
|
||||
<h1 className="truncate text-lg font-semibold text-sidebar-foreground">Fechamento HGTX</h1>
|
||||
<h1 className="truncate text-lg font-semibold text-sidebar-foreground">HGTX Intelligence Score</h1>
|
||||
<p className="text-xs text-muted-foreground">{isAdmin ? "Painel Admin" : "Painel Parceiro"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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) },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export default function BancoPontosExtrato() {
|
||||
<div className="flex h-full flex-col bg-background pb-16 md:pb-0">
|
||||
<div className="border-b border-border bg-muted/20 p-3 md:p-6">
|
||||
<div className="mb-3">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/fechamento/banco-pontos")}>
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/intelligence-score/banco-pontos")}>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Voltar ao banco de pontos
|
||||
</Button>
|
||||
@@ -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 },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
@@ -258,7 +249,7 @@ export default function CompetenciaFechamentos() {
|
||||
<div className="flex h-full flex-col bg-background pb-16 md:pb-0">
|
||||
<div className="border-b border-border p-3 md:p-6">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/fechamento")}>
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/intelligence-score")}>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Voltar
|
||||
</Button>
|
||||
@@ -334,7 +325,6 @@ export default function CompetenciaFechamentos() {
|
||||
<TableHead className="min-w-[100px]">Logo</TableHead>
|
||||
<TableHead className="min-w-[280px]">Nome</TableHead>
|
||||
<TableHead className="min-w-[140px]">Pontuação Total</TableHead>
|
||||
<TableHead className="min-w-[120px]">Horas Total</TableHead>
|
||||
<TableHead className="min-w-[120px]">Status</TableHead>
|
||||
<TableHead className="text-center">Ações</TableHead>
|
||||
</TableRow>
|
||||
@@ -342,7 +332,7 @@ export default function CompetenciaFechamentos() {
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="py-16">
|
||||
<TableCell colSpan={5} className="py-16">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-4 text-center"
|
||||
role="status"
|
||||
@@ -372,7 +362,6 @@ export default function CompetenciaFechamentos() {
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{getDisplayNome(row)}</TableCell>
|
||||
<TableCell>{row.pontuacaoTotalEntregue}</TableCell>
|
||||
<TableCell>{formatHoras(row.horasTotal * 60)}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
className={
|
||||
@@ -402,7 +391,7 @@ export default function CompetenciaFechamentos() {
|
||||
variant="outline"
|
||||
className="transition-colors hover:bg-muted/50 hover:text-foreground"
|
||||
onClick={() =>
|
||||
navigate(`/fechamento/fechamentos/${row.id}`, {
|
||||
navigate(`/intelligence-score/fechamentos/${row.id}`, {
|
||||
state: { competenciaId, status: row.status },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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<CompetenciaItem, "mes" | "ano">): 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<HTMLDivElement | null>(null);
|
||||
const stickyScrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const stickyScrollSpacerRef = useRef<HTMLDivElement | null>(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() {
|
||||
</div>
|
||||
|
||||
{!loading ? (
|
||||
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{isCompetenciaConcluida ? (
|
||||
<div className="sm:col-span-2 xl:col-span-4 rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-900">
|
||||
Competência concluída. Reabra a competência para editar este fechamento.
|
||||
@@ -857,15 +946,6 @@ export default function FechamentoDetalhes() {
|
||||
<CardTitle className="text-3xl">{formatPontos(totais.pontos)}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="border-border/70 bg-card shadow-sm transition hover:shadow-md">
|
||||
<CardHeader className="space-y-2 p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<CardDescription className="text-[11px] uppercase tracking-wide">Horas aprovadas</CardDescription>
|
||||
<Clock3 className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<CardTitle className="text-3xl">{totais.horas}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -881,7 +961,20 @@ export default function FechamentoDetalhes() {
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{!loading ? (
|
||||
<div className="flex justify-end">
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Button type="button" size="sm" variant="outline" onClick={() => setShowFilters((prev) => !prev)}>
|
||||
{showFilters ? "Ocultar filtros" : "Exibir filtros"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleLimparFiltros}
|
||||
disabled={!hasActiveFilters}
|
||||
>
|
||||
Limpar filtros
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -897,8 +990,49 @@ export default function FechamentoDetalhes() {
|
||||
{todasTarefasRevisadas ? "Desmarcar todas" : "Marcar todas"}
|
||||
</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="space-y-1">
|
||||
<Label htmlFor="filtro-descricao">Descrição</Label>
|
||||
<Input
|
||||
id="filtro-descricao"
|
||||
value={filtroDescricao}
|
||||
onChange={(e) => setFiltroDescricao(e.target.value)}
|
||||
placeholder="Digite parte da descrição"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="filtro-ticket">Ticket</Label>
|
||||
<Input
|
||||
id="filtro-ticket"
|
||||
value={filtroTicket}
|
||||
onChange={(e) => setFiltroTicket(e.target.value)}
|
||||
placeholder="Ex.: TKT-1234"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="filtro-cliente">Cliente</Label>
|
||||
<Input
|
||||
id="filtro-cliente"
|
||||
value={filtroCliente}
|
||||
onChange={(e) => setFiltroCliente(e.target.value)}
|
||||
placeholder="Nome do cliente"
|
||||
/>
|
||||
</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)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
</div>
|
||||
) : null}
|
||||
<div ref={tableScrollRef} className="overflow-x-auto rounded-lg border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -911,7 +1045,6 @@ export default function FechamentoDetalhes() {
|
||||
<TableHead className="min-w-[140px]">Data vencimento</TableHead>
|
||||
<TableHead className="min-w-[170px]">Data conclusão</TableHead>
|
||||
<TableHead className="min-w-[240px]">Etiquetas</TableHead>
|
||||
<TableHead className="min-w-[120px]">Horas</TableHead>
|
||||
<TableHead className="min-w-[120px]">Pontuação</TableHead>
|
||||
<TableHead className="min-w-[140px] text-right">Ações</TableHead>
|
||||
</TableRow>
|
||||
@@ -919,12 +1052,18 @@ export default function FechamentoDetalhes() {
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={12} className="py-8 text-center text-muted-foreground">
|
||||
<TableCell colSpan={11} className="py-8 text-center text-muted-foreground">
|
||||
Carregando tarefas...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : tarefasFiltradas.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={11} className="py-8 text-center text-muted-foreground">
|
||||
Nenhuma tarefa encontrada com os filtros aplicados.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
tarefas.map((tarefa) => (
|
||||
tarefasFiltradas.map((tarefa) => (
|
||||
<TableRow key={tarefa.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -938,7 +1077,7 @@ export default function FechamentoDetalhes() {
|
||||
<TableCell>{tarefa.numeroTicket || "—"}</TableCell>
|
||||
<TableCell className="font-medium">{tarefa.descricao}</TableCell>
|
||||
<TableCell>{tarefa.cliente || "—"}</TableCell>
|
||||
<TableCell>{tarefa.tipo}</TableCell>
|
||||
<TableCell>{getTipoLancamentoLabel(tarefa.tipo)}</TableCell>
|
||||
<TableCell>{formatDateOnly(tarefa.dataInicio)}</TableCell>
|
||||
<TableCell>{formatDateOnly(tarefa.dataVencimento)}</TableCell>
|
||||
<TableCell>{formatDateTime(tarefa.dataConclusao)}</TableCell>
|
||||
@@ -955,7 +1094,6 @@ export default function FechamentoDetalhes() {
|
||||
"—"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{formatHoras(tarefa.tempoMinutos)}</TableCell>
|
||||
<TableCell>{Number(tarefa.pontuacao || 0)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
@@ -1014,6 +1152,13 @@ export default function FechamentoDetalhes() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{showStickyScrollbar ? (
|
||||
<div className="fixed bottom-2 left-2 right-2 z-50 rounded-md border bg-background/95 shadow-md backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
||||
<div ref={stickyScrollRef} className="overflow-x-auto">
|
||||
<div ref={stickyScrollSpacerRef} className="h-4 min-w-full" />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1038,8 +1183,8 @@ export default function FechamentoDetalhes() {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="bonus">Bonificação</SelectItem>
|
||||
<SelectItem value="desconto">Desconto</SelectItem>
|
||||
<SelectItem value="bonus">{getTipoLancamentoLabel("bonus")}</SelectItem>
|
||||
<SelectItem value="desconto">{getTipoLancamentoLabel("desconto")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -258,7 +258,7 @@ export default function Fechamentos() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/fechamento/competencias/${competencia.id}`)}
|
||||
onClick={() => navigate(`/intelligence-score/competencias/${competencia.id}`)}
|
||||
>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Acessar
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function MeuFechamento() {
|
||||
<ListChecks className="h-5 w-5 md:h-6 md:w-6" />
|
||||
Meu Fechamento
|
||||
</h1>
|
||||
<Button onClick={() => navigate("/fechamento/meu-fechamento/banco-pontos")} className="gap-2">
|
||||
<Button onClick={() => navigate("/intelligence-score/meu-fechamento/banco-pontos")} className="gap-2">
|
||||
<Landmark className="h-4 w-4" />
|
||||
Banco de Pontos
|
||||
</Button>
|
||||
@@ -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,
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function MeuFechamentoBancoPontos() {
|
||||
if (semVinculo) {
|
||||
return (
|
||||
<div className="p-3 md:p-6">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/fechamento/meu-fechamento")}>
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/intelligence-score/meu-fechamento")}>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Voltar
|
||||
</Button>
|
||||
@@ -133,7 +133,7 @@ export default function MeuFechamentoBancoPontos() {
|
||||
<div className="flex h-full flex-col bg-background pb-16 md:pb-0">
|
||||
<div className="border-b border-border bg-muted/20 p-3 md:p-6">
|
||||
<div className="mb-3">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/fechamento/meu-fechamento")}>
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate("/intelligence-score/meu-fechamento")}>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Voltar ao meu fechamento
|
||||
</Button>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function NotFound() {
|
||||
<div className="text-center">
|
||||
<h1 className="mb-3 text-4xl font-bold">404</h1>
|
||||
<p className="mb-4 text-muted-foreground">Rota não encontrada neste módulo.</p>
|
||||
<Link to="/fechamento" className="text-primary underline hover:text-primary/90">
|
||||
<Link to="/intelligence-score" className="text-primary underline hover:text-primary/90">
|
||||
Voltar para Fechamentos
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user