novas atualizacoes do sistema de fechamento
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, CheckCircle2, Clock3, ListChecks, Loader2, Pencil, Plus, RotateCcw, Target, Trash2, TrendingUp } from "lucide-react";
|
||||
import { ArrowLeft, CheckCircle2, Clock3, ExternalLink, ListChecks, Loader2, Pencil, Plus, RotateCcw, Target, Trash2, TrendingUp } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -49,9 +49,44 @@ function parsePontuacaoInput(value: string): number {
|
||||
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
||||
}
|
||||
|
||||
/** Pontuação do modal de lançamento: só inteiros ≥ 0 (o tipo bonus/desconto define o sinal no backend). */
|
||||
function sanitizePontuacaoLancamentoDigitando(raw: string): string {
|
||||
const t = raw.trim();
|
||||
if (t === "") return "";
|
||||
const n = parsePontuacaoInput(t);
|
||||
if (!Number.isFinite(n) || n < 0) return "0";
|
||||
return String(Math.trunc(Math.min(n, Number.MAX_SAFE_INTEGER)));
|
||||
}
|
||||
|
||||
function toPontuacaoInput(value: number): string {
|
||||
const rounded = Math.round((value + Number.EPSILON) * 100) / 100;
|
||||
return String(rounded);
|
||||
}
|
||||
|
||||
function formatDateOnly(value: string | null): string {
|
||||
if (!value) return "—";
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return "—";
|
||||
return parsed.toLocaleDateString("pt-BR");
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null): string {
|
||||
if (!value) return "—";
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return "—";
|
||||
return parsed.toLocaleString("pt-BR", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
type FechamentoDetalhesLocationState = {
|
||||
competenciaId?: string;
|
||||
status?: "em_aberto" | "fechado";
|
||||
readonlyView?: boolean;
|
||||
};
|
||||
|
||||
export default function FechamentoDetalhes() {
|
||||
@@ -64,7 +99,8 @@ export default function FechamentoDetalhes() {
|
||||
const [fechamentoStatus, setFechamentoStatus] = useState<"em_aberto" | "fechado">(initialState?.status ?? "em_aberto");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tarefas, setTarefas] = useState<FechamentoTarefaItem[]>([]);
|
||||
const [togglingTaskId, setTogglingTaskId] = useState<string | null>(null);
|
||||
const [estadoRevisaoInicial, setEstadoRevisaoInicial] = useState<Record<string, boolean>>({});
|
||||
const [bulkUpdatingRevisao, setBulkUpdatingRevisao] = useState(false);
|
||||
const [deletingTaskId, setDeletingTaskId] = useState<string | null>(null);
|
||||
const [isLancamentoOpen, setIsLancamentoOpen] = useState(false);
|
||||
const [savingLancamento, setSavingLancamento] = useState(false);
|
||||
@@ -91,7 +127,10 @@ export default function FechamentoDetalhes() {
|
||||
const [edicaoCliente, setEdicaoCliente] = useState("");
|
||||
const [edicaoTempoMinutos, setEdicaoTempoMinutos] = useState("");
|
||||
const [edicaoPontuacao, setEdicaoPontuacao] = useState("");
|
||||
const [competenciaStatus, setCompetenciaStatus] = useState<"em_aberto" | "concluido">("em_aberto");
|
||||
const isFechado = fechamentoStatus === "fechado";
|
||||
const isCompetenciaConcluida = competenciaStatus === "concluido";
|
||||
const isReadonly = Boolean(initialState?.readonlyView) || isFechado || isCompetenciaConcluida;
|
||||
|
||||
const totais = useMemo(() => {
|
||||
const aprovadas = tarefas.filter((t) => t.estaRevisada);
|
||||
@@ -108,14 +147,26 @@ export default function FechamentoDetalhes() {
|
||||
const diferencaParaMeta = totais.pontos - Number(pontuacaoMeta ?? 0);
|
||||
const diferencaPagamentoMeta = pontuacaoPagaNumero - Number(pontuacaoMeta ?? 0);
|
||||
const requerMotivoAjuste = Number.isFinite(bancoCalculado) && Math.abs(bancoCalculado) > 0.0001;
|
||||
const pontuacaoTotalLabel = String(totais.pontos);
|
||||
const pontuacaoTotalLabel = toPontuacaoInput(totais.pontos);
|
||||
const isValorEditado = pontuacaoPagaInput.trim() !== pontuacaoTotalLabel;
|
||||
const todasTarefasRevisadas = tarefas.length > 0 && tarefas.every((tarefa) => tarefa.estaRevisada);
|
||||
|
||||
const loadTarefas = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await fechamentoFechamentosService.listarTarefas(fechamentoId);
|
||||
setTarefas(data);
|
||||
const tarefasOrdenadas = [...data].sort((a, b) => {
|
||||
const timeA = a.dataConclusao ? new Date(a.dataConclusao).getTime() : Number.POSITIVE_INFINITY;
|
||||
const timeB = b.dataConclusao ? new Date(b.dataConclusao).getTime() : Number.POSITIVE_INFINITY;
|
||||
return timeA - timeB;
|
||||
});
|
||||
const estadoInicialBanco = Object.fromEntries(tarefasOrdenadas.map((tarefa) => [tarefa.id, Boolean(tarefa.estaRevisada)]));
|
||||
const tarefasDefaultRevisadas = tarefasOrdenadas.map((tarefa) => ({
|
||||
...tarefa,
|
||||
estaRevisada: true,
|
||||
}));
|
||||
setTarefas(tarefasDefaultRevisadas);
|
||||
setEstadoRevisaoInicial(estadoInicialBanco);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Erro ao carregar detalhes do fechamento.";
|
||||
toast.error(message);
|
||||
@@ -128,11 +179,16 @@ export default function FechamentoDetalhes() {
|
||||
const loadFechamentoStatus = async (currentCompetenciaId: string) => {
|
||||
if (!currentCompetenciaId) return;
|
||||
try {
|
||||
const rows = await fechamentoCompetenciasService.listarFechamentosDaCompetencia(currentCompetenciaId);
|
||||
const [rows, competencias] = await Promise.all([
|
||||
fechamentoCompetenciasService.listarFechamentosDaCompetencia(currentCompetenciaId),
|
||||
fechamentoCompetenciasService.listarCompetencias({}),
|
||||
]);
|
||||
const current = rows.find((row) => row.id === fechamentoId);
|
||||
const competenciaAtual = competencias.find((item) => item.id === currentCompetenciaId);
|
||||
if (!current) return;
|
||||
setFechamentoStatus(current.status);
|
||||
setPontuacaoMeta(current.pontuacaoMeta);
|
||||
setCompetenciaStatus(competenciaAtual?.status ?? "em_aberto");
|
||||
if (!competenciaId) {
|
||||
setCompetenciaId(current.competenciaId);
|
||||
}
|
||||
@@ -145,6 +201,10 @@ export default function FechamentoDetalhes() {
|
||||
toast.error("Fechamento está fechado. Reabra para editar.");
|
||||
};
|
||||
|
||||
const toastBloqueioCompetenciaConcluida = () => {
|
||||
toast.error("Competência concluída. Reabra a competência para editar.");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (fechamentoId) {
|
||||
void loadTarefas();
|
||||
@@ -165,12 +225,14 @@ export default function FechamentoDetalhes() {
|
||||
}, [isFechado]);
|
||||
|
||||
const handleToggleAprovada = async (tarefa: FechamentoTarefaItem, approved: boolean) => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
}
|
||||
const previous = tarefas;
|
||||
setTogglingTaskId(tarefa.id);
|
||||
setTarefas((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === tarefa.id
|
||||
@@ -181,21 +243,48 @@ export default function FechamentoDetalhes() {
|
||||
: item,
|
||||
),
|
||||
);
|
||||
try {
|
||||
if (!me?.id) {
|
||||
throw new Error("Não foi possível identificar o usuário para registrar a edição.");
|
||||
}
|
||||
await fechamentoFechamentosService.patchTarefa(fechamentoId, tarefa.id, {
|
||||
estaRevisada: approved,
|
||||
editadoPorId: me.id,
|
||||
});
|
||||
} catch (error) {
|
||||
setTarefas(previous);
|
||||
const message = error instanceof Error ? error.message : "Erro ao atualizar aprovação da tarefa.";
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setTogglingTaskId(null);
|
||||
};
|
||||
|
||||
const handleToggleTodasAprovadas = async () => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
}
|
||||
const novoValor = !todasTarefasRevisadas;
|
||||
const tarefasParaAtualizar = tarefas.filter((tarefa) => tarefa.estaRevisada !== novoValor);
|
||||
if (tarefasParaAtualizar.length === 0) return;
|
||||
setBulkUpdatingRevisao(true);
|
||||
setTarefas((prev) => prev.map((tarefa) => ({ ...tarefa, estaRevisada: novoValor })));
|
||||
setBulkUpdatingRevisao(false);
|
||||
};
|
||||
|
||||
const persistirRevisoesPendentes = async () => {
|
||||
if (!me?.id) {
|
||||
throw new Error("Não foi possível identificar o usuário para registrar a edição.");
|
||||
}
|
||||
const tarefasAlteradas = tarefas.filter(
|
||||
(tarefa) => estadoRevisaoInicial[tarefa.id] !== undefined && estadoRevisaoInicial[tarefa.id] !== tarefa.estaRevisada,
|
||||
);
|
||||
if (tarefasAlteradas.length === 0) return;
|
||||
await Promise.all(
|
||||
tarefasAlteradas.map((tarefa) =>
|
||||
fechamentoFechamentosService.patchTarefa(fechamentoId, tarefa.id, {
|
||||
estaRevisada: tarefa.estaRevisada,
|
||||
editadoPorId: me.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
setEstadoRevisaoInicial((prev) => {
|
||||
const next = { ...prev };
|
||||
for (const tarefa of tarefasAlteradas) {
|
||||
next[tarefa.id] = tarefa.estaRevisada;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const resetLancamentoForm = () => {
|
||||
@@ -205,12 +294,16 @@ export default function FechamentoDetalhes() {
|
||||
};
|
||||
|
||||
const handleSalvarLancamento = async () => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
}
|
||||
const descricao = lancamentoDescricao.trim();
|
||||
const pontuacao = Number(lancamentoPontuacao);
|
||||
const pontuacao = Math.trunc(Number(lancamentoPontuacao));
|
||||
if (!descricao) {
|
||||
toast.error("Informe a descrição do lançamento.");
|
||||
return;
|
||||
@@ -240,6 +333,10 @@ export default function FechamentoDetalhes() {
|
||||
};
|
||||
|
||||
const handleAbrirConcluir = () => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
@@ -250,6 +347,10 @@ export default function FechamentoDetalhes() {
|
||||
};
|
||||
|
||||
const handleConcluirFechamento = async () => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
@@ -269,13 +370,14 @@ export default function FechamentoDetalhes() {
|
||||
}
|
||||
try {
|
||||
setConcluindo(true);
|
||||
await persistirRevisoesPendentes();
|
||||
const data = await fechamentoFechamentosService.concluirFechamento(fechamentoId, {
|
||||
pontuacaoPaga: pontuacaoPagaRaw,
|
||||
motivoAjuste: motivoAjuste.trim() || undefined,
|
||||
});
|
||||
toast.success(`Fechamento concluído. Banco de pontos: ${data.pontuacaoBanco}.`);
|
||||
setFechamentoStatus("fechado");
|
||||
navigate(`/fechamento-hgtx/competencias/${data.competenciaId}`);
|
||||
navigate(`/fechamento/competencias/${data.competenciaId}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Erro ao concluir fechamento.";
|
||||
toast.error(message);
|
||||
@@ -321,6 +423,10 @@ export default function FechamentoDetalhes() {
|
||||
};
|
||||
|
||||
const handleExcluirLancamento = (tarefa: FechamentoTarefaItem) => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
@@ -349,6 +455,10 @@ export default function FechamentoDetalhes() {
|
||||
};
|
||||
|
||||
const openEditarTarefa = (tarefa: FechamentoTarefaItem) => {
|
||||
if (isCompetenciaConcluida) {
|
||||
toastBloqueioCompetenciaConcluida();
|
||||
return;
|
||||
}
|
||||
if (isFechado) {
|
||||
toastBloqueioFechado();
|
||||
return;
|
||||
@@ -446,7 +556,7 @@ export default function FechamentoDetalhes() {
|
||||
<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-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate(-1)}>
|
||||
<Button variant="ghost" size="sm" className="hover:bg-muted/60 hover:text-foreground" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="mr-1 h-4 w-4" />
|
||||
Voltar
|
||||
</Button>
|
||||
@@ -463,24 +573,25 @@ export default function FechamentoDetalhes() {
|
||||
|
||||
{!loading ? (
|
||||
<div className="flex flex-wrap items-center gap-2 xl:justify-end">
|
||||
<Badge variant={isFechado ? "secondary" : "outline"}>{isFechado ? "Fechado" : "Em aberto"}</Badge>
|
||||
<Badge variant={isReadonly ? "secondary" : "outline"}>{isFechado ? "Fechado" : "Em aberto"}</Badge>
|
||||
{isCompetenciaConcluida ? <Badge variant="secondary">Competência concluída</Badge> : null}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsLancamentoOpen(true)}
|
||||
disabled={isFechado}
|
||||
className="min-w-[152px]"
|
||||
disabled={isReadonly}
|
||||
className="min-w-[152px] hover:bg-muted/60 hover:text-foreground"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Fazer lançamento
|
||||
</Button>
|
||||
{!isFechado ? (
|
||||
{!isReadonly ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsReprocessAsanaOpen(true)}
|
||||
disabled={reprocessandoAsana}
|
||||
className="min-w-[152px]"
|
||||
className="min-w-[152px] hover:bg-muted/60 hover:text-foreground"
|
||||
>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
Reprocessar Asana
|
||||
@@ -491,7 +602,7 @@ export default function FechamentoDetalhes() {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsReabrirOpen(true)}
|
||||
disabled={reabrindo}
|
||||
disabled={reabrindo || isCompetenciaConcluida}
|
||||
className="min-w-[152px]"
|
||||
>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
@@ -502,7 +613,7 @@ export default function FechamentoDetalhes() {
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={handleAbrirConcluir}
|
||||
disabled={tarefas.length === 0 || concluindo}
|
||||
disabled={tarefas.length === 0 || concluindo || isCompetenciaConcluida}
|
||||
className="min-w-[152px]"
|
||||
>
|
||||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||||
@@ -515,6 +626,11 @@ export default function FechamentoDetalhes() {
|
||||
|
||||
{!loading ? (
|
||||
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{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.
|
||||
</div>
|
||||
) : null}
|
||||
<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">
|
||||
@@ -564,7 +680,26 @@ export default function FechamentoDetalhes() {
|
||||
</CardHeader>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<div className="space-y-3">
|
||||
{!loading ? (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => void handleToggleTodasAprovadas()}
|
||||
disabled={isReadonly || bulkUpdatingRevisao}
|
||||
>
|
||||
{bulkUpdatingRevisao ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{todasTarefasRevisadas ? "Desmarcar todas" : "Marcar todas"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -573,6 +708,10 @@ export default function FechamentoDetalhes() {
|
||||
<TableHead className="min-w-[320px]">Descrição</TableHead>
|
||||
<TableHead className="min-w-[180px]">Cliente</TableHead>
|
||||
<TableHead className="min-w-[110px]">Tipo</TableHead>
|
||||
<TableHead className="min-w-[130px]">Data início</TableHead>
|
||||
<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>
|
||||
@@ -581,7 +720,7 @@ export default function FechamentoDetalhes() {
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="py-8 text-center text-muted-foreground">
|
||||
<TableCell colSpan={12} className="py-8 text-center text-muted-foreground">
|
||||
Carregando tarefas...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -593,25 +732,48 @@ export default function FechamentoDetalhes() {
|
||||
<Checkbox
|
||||
checked={tarefa.estaRevisada}
|
||||
onCheckedChange={(checked) => void handleToggleAprovada(tarefa, Boolean(checked))}
|
||||
disabled={togglingTaskId === tarefa.id || isFechado}
|
||||
disabled={isReadonly || bulkUpdatingRevisao}
|
||||
/>
|
||||
{togglingTaskId === tarefa.id ? <Loader2 className="h-3 w-3 animate-spin" /> : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{tarefa.numeroTicket || "—"}</TableCell>
|
||||
<TableCell className="font-medium">{tarefa.descricao}</TableCell>
|
||||
<TableCell>{tarefa.cliente || "—"}</TableCell>
|
||||
<TableCell>{tarefa.tipo}</TableCell>
|
||||
<TableCell>{formatDateOnly(tarefa.dataInicio)}</TableCell>
|
||||
<TableCell>{formatDateOnly(tarefa.dataVencimento)}</TableCell>
|
||||
<TableCell>{formatDateTime(tarefa.dataConclusao)}</TableCell>
|
||||
<TableCell>
|
||||
{tarefa.etiquetas && tarefa.etiquetas.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{tarefa.etiquetas.map((etiqueta) => (
|
||||
<Badge key={`${tarefa.id}-${etiqueta.gid}`} variant="outline" className="font-normal">
|
||||
{etiqueta.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</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">
|
||||
{tarefa.linkAsana ? (
|
||||
<Button type="button" size="sm" variant="ghost" asChild>
|
||||
<a href={tarefa.linkAsana} target="_blank" rel="noopener noreferrer" title="Abrir tarefa no Asana">
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
<span className="ml-1">Abrir</span>
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => openEditarTarefa(tarefa)}
|
||||
disabled={isFechado}
|
||||
disabled={isReadonly}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<span className="ml-1">Editar</span>
|
||||
@@ -623,7 +785,7 @@ export default function FechamentoDetalhes() {
|
||||
variant="ghost"
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
onClick={() => void handleExcluirLancamento(tarefa)}
|
||||
disabled={isFechado || deletingTaskId === tarefa.id}
|
||||
disabled={isReadonly || deletingTaskId === tarefa.id}
|
||||
>
|
||||
{deletingTaskId === tarefa.id ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -641,6 +803,7 @@ export default function FechamentoDetalhes() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -675,10 +838,16 @@ export default function FechamentoDetalhes() {
|
||||
<Input
|
||||
id="lancamento-pontuacao"
|
||||
type="number"
|
||||
min={1}
|
||||
inputMode="numeric"
|
||||
min={0}
|
||||
step={1}
|
||||
value={lancamentoPontuacao}
|
||||
onChange={(e) => setLancamentoPontuacao(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "-" || e.key === "+" || e.key === "e" || e.key === "E" || e.key === "," || e.key === ".") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onChange={(e) => setLancamentoPontuacao(sanitizePontuacaoLancamentoDigitando(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -696,6 +865,7 @@ export default function FechamentoDetalhes() {
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => {
|
||||
setIsLancamentoOpen(false);
|
||||
resetLancamentoForm();
|
||||
@@ -704,7 +874,7 @@ export default function FechamentoDetalhes() {
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleSalvarLancamento()} disabled={savingLancamento || isFechado}>
|
||||
<Button onClick={() => void handleSalvarLancamento()} disabled={savingLancamento || isReadonly}>
|
||||
{savingLancamento ? "Salvando..." : "Salvar lançamento"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -799,11 +969,27 @@ export default function FechamentoDetalhes() {
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsConcluirOpen(false)} disabled={concluindo}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => setIsConcluirOpen(false)}
|
||||
disabled={concluindo}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleConcluirFechamento()} disabled={concluindo || isFechado}>
|
||||
{concluindo ? "Concluindo..." : "Confirmar conclusão"}
|
||||
<Button
|
||||
onClick={() => void handleConcluirFechamento()}
|
||||
disabled={concluindo || isReadonly}
|
||||
className="min-w-[172px] shadow-lg hover:shadow-primary/50"
|
||||
>
|
||||
{concluindo ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Concluindo...
|
||||
</>
|
||||
) : (
|
||||
"Confirmar conclusão"
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -827,7 +1013,12 @@ export default function FechamentoDetalhes() {
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsReabrirOpen(false)} disabled={reabrindo}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => setIsReabrirOpen(false)}
|
||||
disabled={reabrindo}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleReabrirFechamento()} disabled={reabrindo}>
|
||||
@@ -849,6 +1040,7 @@ export default function FechamentoDetalhes() {
|
||||
<DialogFooter className="gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => {
|
||||
setIsDeleteDialogOpen(false);
|
||||
setDeletingTarefa(null);
|
||||
@@ -880,13 +1072,26 @@ export default function FechamentoDetalhes() {
|
||||
<DialogFooter className="gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => setIsReprocessAsanaOpen(false)}
|
||||
disabled={reprocessandoAsana}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleReprocessarAsana()} disabled={reprocessandoAsana}>
|
||||
{reprocessandoAsana ? "Reprocessando..." : "Confirmar reprocessamento"}
|
||||
<Button
|
||||
onClick={() => void handleReprocessarAsana()}
|
||||
disabled={reprocessandoAsana}
|
||||
variant="secondary"
|
||||
className="shadow-md hover:shadow-secondary/40"
|
||||
>
|
||||
{reprocessandoAsana ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Reprocessando...
|
||||
</>
|
||||
) : (
|
||||
"Confirmar reprocessamento"
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -948,10 +1153,15 @@ export default function FechamentoDetalhes() {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsEditarOpen(false)} disabled={savingEdicao}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-muted/60 hover:text-foreground"
|
||||
onClick={() => setIsEditarOpen(false)}
|
||||
disabled={savingEdicao}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={() => void handleSalvarEdicao()} disabled={savingEdicao}>
|
||||
<Button onClick={() => void handleSalvarEdicao()} disabled={savingEdicao || isReadonly}>
|
||||
{savingEdicao ? "Salvando..." : "Salvar"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user