nova função de alterar task de competencia

This commit is contained in:
Vitex Tecnologia
2026-04-29 10:13:17 -03:00
parent b19e2963c4
commit d570029890
3 changed files with 206 additions and 2 deletions
BIN
View File
Binary file not shown.
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
import { ArrowLeft, CheckCircle2, Clock3, ExternalLink, ListChecks, Loader2, Pencil, Plus, RotateCcw, Target, Trash2, TrendingUp } from "lucide-react"; import { ArrowLeft, CheckCircle2, Clock3, ExternalLink, ListChecks, Loader2, Pencil, Plus, Repeat, RotateCcw, Target, Trash2, TrendingUp } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useAuthAccess } from "@/contexts/AuthAccessContext"; import { useAuthAccess } from "@/contexts/AuthAccessContext";
import { fechamentoCompetenciasService } 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";
function formatHoras(minutos: number | null): string { function formatHoras(minutos: number | null): string {
@@ -105,6 +105,10 @@ function formatDateTime(value: string | null): string {
}); });
} }
function formatCompetenciaMesAno(item: Pick<CompetenciaItem, "mes" | "ano">): string {
return `${String(item.mes).padStart(2, "0")}/${item.ano}`;
}
type FechamentoDetalhesLocationState = { type FechamentoDetalhesLocationState = {
competenciaId?: string; competenciaId?: string;
status?: "em_aberto" | "fechado"; status?: "em_aberto" | "fechado";
@@ -141,6 +145,13 @@ export default function FechamentoDetalhes() {
const [deletingTarefa, setDeletingTarefa] = useState<FechamentoTarefaItem | null>(null); const [deletingTarefa, setDeletingTarefa] = useState<FechamentoTarefaItem | null>(null);
const [isReprocessAsanaOpen, setIsReprocessAsanaOpen] = useState(false); const [isReprocessAsanaOpen, setIsReprocessAsanaOpen] = useState(false);
const [reprocessandoAsana, setReprocessandoAsana] = useState(false); const [reprocessandoAsana, setReprocessandoAsana] = useState(false);
const [isAlterarCompetenciaOpen, setIsAlterarCompetenciaOpen] = useState(false);
const [loadingCompetenciasAbertas, setLoadingCompetenciasAbertas] = useState(false);
const [savingAlteracaoCompetencia, setSavingAlteracaoCompetencia] = useState(false);
const [competenciasAbertas, setCompetenciasAbertas] = useState<CompetenciaItem[]>([]);
const [tarefaAlteracaoCompetencia, setTarefaAlteracaoCompetencia] = useState<FechamentoTarefaItem | null>(null);
const [competenciaDestinoId, setCompetenciaDestinoId] = useState("");
const [motivoAlteracaoCompetencia, setMotivoAlteracaoCompetencia] = useState("");
const [isEditarOpen, setIsEditarOpen] = useState(false); const [isEditarOpen, setIsEditarOpen] = useState(false);
const [savingEdicao, setSavingEdicao] = useState(false); const [savingEdicao, setSavingEdicao] = useState(false);
const [editingTarefa, setEditingTarefa] = useState<FechamentoTarefaItem | null>(null); const [editingTarefa, setEditingTarefa] = useState<FechamentoTarefaItem | null>(null);
@@ -153,6 +164,7 @@ export default function FechamentoDetalhes() {
const isFechado = fechamentoStatus === "fechado"; const isFechado = fechamentoStatus === "fechado";
const isCompetenciaConcluida = competenciaStatus === "concluido"; const isCompetenciaConcluida = competenciaStatus === "concluido";
const isReadonly = Boolean(initialState?.readonlyView) || isFechado || isCompetenciaConcluida; const isReadonly = Boolean(initialState?.readonlyView) || isFechado || isCompetenciaConcluida;
const isAdmin = me?.papel === "admin";
const totais = useMemo(() => { const totais = useMemo(() => {
const aprovadas = tarefas.filter((t) => t.estaRevisada); const aprovadas = tarefas.filter((t) => t.estaRevisada);
@@ -172,6 +184,10 @@ export default function FechamentoDetalhes() {
const pontuacaoTotalLabel = toPontuacaoInput(totais.pontos); const pontuacaoTotalLabel = toPontuacaoInput(totais.pontos);
const isValorEditado = pontuacaoPagaInput.trim() !== pontuacaoTotalLabel; const isValorEditado = pontuacaoPagaInput.trim() !== pontuacaoTotalLabel;
const todasTarefasRevisadas = tarefas.length > 0 && tarefas.every((tarefa) => tarefa.estaRevisada); const todasTarefasRevisadas = tarefas.length > 0 && tarefas.every((tarefa) => tarefa.estaRevisada);
const competenciasDestino = useMemo(
() => competenciasAbertas.filter((item) => item.id !== competenciaId),
[competenciasAbertas, competenciaId],
);
const loadTarefas = async () => { const loadTarefas = async () => {
try { try {
@@ -498,6 +514,77 @@ export default function FechamentoDetalhes() {
setIsEditarOpen(true); setIsEditarOpen(true);
}; };
const carregarCompetenciasAbertas = async () => {
try {
setLoadingCompetenciasAbertas(true);
const competencias = await fechamentoCompetenciasService.listarCompetencias({ status: "em_aberto" });
setCompetenciasAbertas(competencias);
return competencias;
} catch (error) {
const message = error instanceof Error ? error.message : "Erro ao carregar competências em aberto.";
toast.error(message);
return [] as CompetenciaItem[];
} finally {
setLoadingCompetenciasAbertas(false);
}
};
const openAlterarCompetencia = async (tarefa: FechamentoTarefaItem) => {
if (!isAdmin) {
toast.error("Apenas administradores podem alterar competência.");
return;
}
if (isCompetenciaConcluida) {
toastBloqueioCompetenciaConcluida();
return;
}
if (isFechado) {
toastBloqueioFechado();
return;
}
const competencias = await carregarCompetenciasAbertas();
const opcoes = competencias.filter((item) => item.id !== competenciaId);
if (opcoes.length === 0) {
toast.error("Não há outra competência em aberto para mover a tarefa.");
return;
}
setTarefaAlteracaoCompetencia(tarefa);
setCompetenciaDestinoId(opcoes[0]?.id ?? "");
setMotivoAlteracaoCompetencia("");
setIsAlterarCompetenciaOpen(true);
};
const handleConfirmarAlteracaoCompetencia = async () => {
if (!tarefaAlteracaoCompetencia) return;
if (!me?.id) {
toast.error("Não foi possível identificar o usuário para registrar a alteração.");
return;
}
if (!competenciaDestinoId) {
toast.error("Selecione a competência de destino.");
return;
}
try {
setSavingAlteracaoCompetencia(true);
await fechamentoFechamentosService.alterarCompetenciaTarefa(fechamentoId, tarefaAlteracaoCompetencia.id, {
competenciaDestinoId,
alteradoPorId: me.id,
motivo: motivoAlteracaoCompetencia,
});
toast.success("Competência da tarefa alterada com sucesso.");
setIsAlterarCompetenciaOpen(false);
setTarefaAlteracaoCompetencia(null);
setCompetenciaDestinoId("");
setMotivoAlteracaoCompetencia("");
await loadTarefas();
} catch (error) {
const message = error instanceof Error ? error.message : "Erro ao alterar competência da tarefa.";
toast.error(message);
} finally {
setSavingAlteracaoCompetencia(false);
}
};
const handleSalvarEdicao = async () => { const handleSalvarEdicao = async () => {
if (!editingTarefa) return; if (!editingTarefa) return;
if (!me?.id) { if (!me?.id) {
@@ -804,6 +891,18 @@ export default function FechamentoDetalhes() {
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
<span className="ml-1">Editar</span> <span className="ml-1">Editar</span>
</Button> </Button>
{isAdmin && tarefa.tipo === "tarefa" ? (
<Button
type="button"
size="sm"
variant="ghost"
onClick={() => void openAlterarCompetencia(tarefa)}
disabled={isReadonly || savingAlteracaoCompetencia}
>
<Repeat className="h-4 w-4" />
<span className="ml-1">Alterar competência</span>
</Button>
) : null}
{tarefa.tipo === "bonus" || tarefa.tipo === "desconto" ? ( {tarefa.tipo === "bonus" || tarefa.tipo === "desconto" ? (
<Button <Button
type="button" type="button"
@@ -901,6 +1000,87 @@ export default function FechamentoDetalhes() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Dialog
open={isAlterarCompetenciaOpen}
onOpenChange={(open) => {
setIsAlterarCompetenciaOpen(open);
if (!open) {
setTarefaAlteracaoCompetencia(null);
setMotivoAlteracaoCompetencia("");
setCompetenciaDestinoId("");
}
}}
>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Alterar competência da tarefa</DialogTitle>
<DialogDescription>
Mova a tarefa para outra competência em aberto. O histórico da alteração ficará registrado.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>Competência atual</Label>
<p className="rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
{competenciaId
? (() => {
const atual = competenciasAbertas.find((item) => item.id === competenciaId);
if (atual) return formatCompetenciaMesAno(atual);
return "Competência atual do fechamento";
})()
: "Competência atual do fechamento"}
</p>
</div>
<div className="space-y-2">
<Label htmlFor="competencia-destino">Competência destino</Label>
<Select
value={competenciaDestinoId}
onValueChange={setCompetenciaDestinoId}
disabled={loadingCompetenciasAbertas || savingAlteracaoCompetencia}
>
<SelectTrigger id="competencia-destino">
<SelectValue placeholder="Selecione a competência de destino" />
</SelectTrigger>
<SelectContent>
{competenciasDestino.map((item) => (
<SelectItem key={item.id} value={item.id}>
{formatCompetenciaMesAno(item)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="motivo-alteracao-competencia">Motivo (opcional)</Label>
<Input
id="motivo-alteracao-competencia"
value={motivoAlteracaoCompetencia}
onChange={(e) => setMotivoAlteracaoCompetencia(e.target.value)}
placeholder="Ex.: tarefa entregue no mês seguinte por atraso de baixa"
maxLength={500}
disabled={savingAlteracaoCompetencia}
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
className="hover:bg-muted/60 hover:text-foreground"
onClick={() => setIsAlterarCompetenciaOpen(false)}
disabled={savingAlteracaoCompetencia}
>
Cancelar
</Button>
<Button
onClick={() => void handleConfirmarAlteracaoCompetencia()}
disabled={savingAlteracaoCompetencia || !competenciaDestinoId}
>
{savingAlteracaoCompetencia ? "Alterando..." : "Confirmar alteração"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={isConcluirOpen} onOpenChange={setIsConcluirOpen}> <Dialog open={isConcluirOpen} onOpenChange={setIsConcluirOpen}>
<DialogContent className="sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
+24
View File
@@ -95,6 +95,7 @@ export type ExportarPlanilhaResponse = {
type ApiErrorShape = { type ApiErrorShape = {
error?: { error?: {
code?: string;
message?: string; message?: string;
}; };
}; };
@@ -309,6 +310,29 @@ class FechamentoFechamentosService {
this.handleAxiosError(error, "Erro ao reprocessar tarefas do Asana."); this.handleAxiosError(error, "Erro ao reprocessar tarefas do Asana.");
} }
} }
async alterarCompetenciaTarefa(
fechamentoId: string,
tarefaId: string,
input: { competenciaDestinoId: string; alteradoPorId: string; motivo?: string },
): Promise<FechamentoTarefaItem> {
try {
const headers = await buildCommanderHeaders();
const baseUrl = resolveCommanderBaseUrl();
const response = await axios.post<PatchTarefaResponse>(
`${baseUrl}/fechamentos/${fechamentoId}/tarefas/${tarefaId}/alterar-competencia`,
{
competencia_destino_id: input.competenciaDestinoId,
alterado_por_id: input.alteradoPorId,
...(input.motivo?.trim() ? { motivo: input.motivo.trim() } : {}),
},
{ headers },
);
return response.data.data;
} catch (error) {
this.handleAxiosError(error, "Erro ao alterar competência da tarefa.");
}
}
} }
export const fechamentoFechamentosService = new FechamentoFechamentosService(); export const fechamentoFechamentosService = new FechamentoFechamentosService();