nova função de alterar task de competencia
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, 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 { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
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";
|
||||
|
||||
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 = {
|
||||
competenciaId?: string;
|
||||
status?: "em_aberto" | "fechado";
|
||||
@@ -141,6 +145,13 @@ export default function FechamentoDetalhes() {
|
||||
const [deletingTarefa, setDeletingTarefa] = useState<FechamentoTarefaItem | null>(null);
|
||||
const [isReprocessAsanaOpen, setIsReprocessAsanaOpen] = 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 [savingEdicao, setSavingEdicao] = useState(false);
|
||||
const [editingTarefa, setEditingTarefa] = useState<FechamentoTarefaItem | null>(null);
|
||||
@@ -153,6 +164,7 @@ export default function FechamentoDetalhes() {
|
||||
const isFechado = fechamentoStatus === "fechado";
|
||||
const isCompetenciaConcluida = competenciaStatus === "concluido";
|
||||
const isReadonly = Boolean(initialState?.readonlyView) || isFechado || isCompetenciaConcluida;
|
||||
const isAdmin = me?.papel === "admin";
|
||||
|
||||
const totais = useMemo(() => {
|
||||
const aprovadas = tarefas.filter((t) => t.estaRevisada);
|
||||
@@ -172,6 +184,10 @@ export default function FechamentoDetalhes() {
|
||||
const pontuacaoTotalLabel = toPontuacaoInput(totais.pontos);
|
||||
const isValorEditado = pontuacaoPagaInput.trim() !== pontuacaoTotalLabel;
|
||||
const todasTarefasRevisadas = tarefas.length > 0 && tarefas.every((tarefa) => tarefa.estaRevisada);
|
||||
const competenciasDestino = useMemo(
|
||||
() => competenciasAbertas.filter((item) => item.id !== competenciaId),
|
||||
[competenciasAbertas, competenciaId],
|
||||
);
|
||||
|
||||
const loadTarefas = async () => {
|
||||
try {
|
||||
@@ -498,6 +514,77 @@ export default function FechamentoDetalhes() {
|
||||
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 () => {
|
||||
if (!editingTarefa) return;
|
||||
if (!me?.id) {
|
||||
@@ -804,6 +891,18 @@ export default function FechamentoDetalhes() {
|
||||
<Pencil className="h-4 w-4" />
|
||||
<span className="ml-1">Editar</span>
|
||||
</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" ? (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -901,6 +1000,87 @@ export default function FechamentoDetalhes() {
|
||||
</DialogContent>
|
||||
</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}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -95,6 +95,7 @@ export type ExportarPlanilhaResponse = {
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
@@ -309,6 +310,29 @@ class FechamentoFechamentosService {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user