nova função de alterar task de competencia
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user