Add delete meta option to menu
Adds a delete option to the meta menu for deleting registered metas.
This commit is contained in:
parent
002836773d
commit
9a2f18c74e
@ -1,90 +1,83 @@
|
|||||||
|
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ptBR } from 'date-fns/locale';
|
import { ptBR } from 'date-fns/locale';
|
||||||
import { Trophy, Flag, FlagOff } from 'lucide-react';
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { ResultadoMeta } from '@/types/financialTypes';
|
import { ResultadoMeta } from '@/types/financialTypes';
|
||||||
|
import { Check, Edit2, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
interface MetaProgressCardProps {
|
interface MetaProgressCardProps {
|
||||||
meta: ResultadoMeta;
|
meta: ResultadoMeta;
|
||||||
onEditClick?: () => void;
|
onEditClick: () => void;
|
||||||
|
onDeleteClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MetaProgressCard({ meta, onEditClick }: MetaProgressCardProps) {
|
export function MetaProgressCard({ meta, onEditClick, onDeleteClick }: MetaProgressCardProps) {
|
||||||
const formatCurrency = (value: number) => {
|
// Format the month name in Portuguese
|
||||||
return new Intl.NumberFormat('pt-BR', {
|
const mesFormatado = format(new Date(meta.ano, meta.mes - 1, 1), 'MMMM', { locale: ptBR });
|
||||||
style: 'currency',
|
const mesCapitalizado = mesFormatado.charAt(0).toUpperCase() + mesFormatado.slice(1);
|
||||||
currency: 'BRL',
|
|
||||||
}).format(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMesNome = (mes: number) => {
|
|
||||||
const date = new Date(2000, mes - 1, 1);
|
|
||||||
return format(date, 'MMMM', { locale: ptBR });
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusIcon = () => {
|
|
||||||
if (meta.meta_batida) {
|
|
||||||
return <Trophy className="h-8 w-8 text-yellow-500" />;
|
|
||||||
} else {
|
|
||||||
return <FlagOff className="h-8 w-8 text-gray-400" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProgressColor = () => {
|
|
||||||
if (meta.meta_batida) {
|
|
||||||
return 'bg-green-500';
|
|
||||||
} else if (meta.percentual_atingido >= 75) {
|
|
||||||
return 'bg-yellow-500';
|
|
||||||
} else {
|
|
||||||
return 'bg-red-500';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
<CardHeader className={`pb-2 ${meta.meta_batida ? 'bg-green-50' : 'bg-gray-50'}`}>
|
<CardContent className="p-4">
|
||||||
<CardTitle className="flex items-center justify-between">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<span className="capitalize">{getMesNome(meta.mes)} {meta.ano}</span>
|
<div>
|
||||||
<div className="flex items-center">
|
<h3 className="text-lg font-medium">{mesCapitalizado}</h3>
|
||||||
{getStatusIcon()}
|
<p className="text-sm text-muted-foreground">{meta.ano}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={onEditClick}
|
||||||
|
>
|
||||||
|
<Edit2 className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Editar</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||||
|
onClick={onDeleteClick}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Deletar</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="pt-4 space-y-4">
|
|
||||||
<div className="flex justify-between text-sm text-muted-foreground mb-1">
|
|
||||||
<span>Meta: {formatCurrency(meta.valor_meta)}</span>
|
|
||||||
<span>Economia: {formatCurrency(meta.economia_real)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="mb-4">
|
||||||
<div className="h-2 w-full">
|
<div className="flex justify-between mb-1">
|
||||||
<Progress
|
<span className="text-sm">Meta de Economia</span>
|
||||||
value={meta.percentual_atingido}
|
<span className="text-sm font-semibold">R$ {meta.valor_meta.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
|
||||||
max={100}
|
|
||||||
className={getProgressColor()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between">
|
||||||
<span>0%</span>
|
<span className="text-sm">Economia Real</span>
|
||||||
<span className="font-medium">{meta.percentual_atingido.toFixed(0)}% atingido</span>
|
<span className="text-sm font-semibold">R$ {meta.economia_real.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
|
||||||
<span>100%</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between mb-1">
|
||||||
|
<span className="text-sm font-semibold">Progresso</span>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{meta.percentual_atingido.toFixed(0)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={meta.percentual_atingido} className="h-2" />
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
{onEditClick && (
|
<CardFooter className={`p-3 ${meta.meta_batida ? 'bg-green-100 dark:bg-green-900/20' : 'bg-muted/50'}`}>
|
||||||
<CardFooter className="bg-muted/50 pt-1 px-4 pb-2">
|
<div className="flex items-center w-full justify-between">
|
||||||
<button
|
<span className="text-sm font-medium">
|
||||||
onClick={onEditClick}
|
{meta.meta_batida ? 'Meta atingida!' : 'Meta em andamento'}
|
||||||
className="text-sm text-muted-foreground hover:text-primary w-full text-center"
|
</span>
|
||||||
>
|
{meta.meta_batida && <Check className="h-4 w-4 text-green-600 dark:text-green-500" />}
|
||||||
Editar meta
|
</div>
|
||||||
</button>
|
</CardFooter>
|
||||||
</CardFooter>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { ResultadoMeta } from '@/types/financialTypes';
|
import { ResultadoMeta } from '@/types/financialTypes';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { calcularResultadosMetas, getMeta } from '@/services/metasService';
|
import { calcularResultadosMetas, getMeta, deletarMeta } from '@/services/metasService';
|
||||||
import { PlusCircle } from 'lucide-react';
|
import { PlusCircle } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -18,6 +18,16 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
|
|
||||||
const MetasPage = () => {
|
const MetasPage = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -25,8 +35,11 @@ const MetasPage = () => {
|
|||||||
const [resultados, setResultados] = useState<ResultadoMeta[]>([]);
|
const [resultados, setResultados] = useState<ResultadoMeta[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [metaParaEditar, setMetaParaEditar] = useState<{ mes: number, ano: number } | null>(null);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
|
const [metaParaEditar, setMetaParaEditar] = useState<{ mes: number, ano: number, id?: string } | null>(null);
|
||||||
|
const [metaParaDeletar, setMetaParaDeletar] = useState<{ mes: number, ano: number, id: string } | null>(null);
|
||||||
const [valorMetaAtual, setValorMetaAtual] = useState(0);
|
const [valorMetaAtual, setValorMetaAtual] = useState(0);
|
||||||
|
const [isDeletingMeta, setIsDeletingMeta] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Obter userId armazenado no localStorage
|
// Obter userId armazenado no localStorage
|
||||||
@ -72,7 +85,7 @@ const MetasPage = () => {
|
|||||||
const meta = await getMeta(userId, mes, ano);
|
const meta = await getMeta(userId, mes, ano);
|
||||||
if (meta) {
|
if (meta) {
|
||||||
setValorMetaAtual(meta.valor_meta);
|
setValorMetaAtual(meta.valor_meta);
|
||||||
setMetaParaEditar({ mes, ano });
|
setMetaParaEditar({ mes, ano, id: meta.id });
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -80,6 +93,39 @@ const MetasPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeletarMeta = (mes: number, ano: number, id: string) => {
|
||||||
|
setMetaParaDeletar({ mes, ano, id });
|
||||||
|
setIsDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmarDeletarMeta = async () => {
|
||||||
|
if (!metaParaDeletar?.id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsDeletingMeta(true);
|
||||||
|
await deletarMeta(metaParaDeletar.id);
|
||||||
|
|
||||||
|
// Atualizar lista de metas após deleção
|
||||||
|
await loadData(userId);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Meta excluída',
|
||||||
|
description: `A meta de ${format(new Date(metaParaDeletar.ano, metaParaDeletar.mes - 1, 1), 'MMMM', { locale: ptBR })} ${metaParaDeletar.ano} foi excluída com sucesso.`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao deletar meta:', error);
|
||||||
|
toast({
|
||||||
|
title: 'Erro ao deletar meta',
|
||||||
|
description: 'Não foi possível excluir a meta',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsDeletingMeta(false);
|
||||||
|
setIsDeleteDialogOpen(false);
|
||||||
|
setMetaParaDeletar(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleNovaMeta = () => {
|
const handleNovaMeta = () => {
|
||||||
setMetaParaEditar(null);
|
setMetaParaEditar(null);
|
||||||
setValorMetaAtual(0);
|
setValorMetaAtual(0);
|
||||||
@ -161,6 +207,20 @@ const MetasPage = () => {
|
|||||||
key={`${meta.ano}-${meta.mes}`}
|
key={`${meta.ano}-${meta.mes}`}
|
||||||
meta={meta}
|
meta={meta}
|
||||||
onEditClick={() => handleEditarMeta(meta.mes, meta.ano)}
|
onEditClick={() => handleEditarMeta(meta.mes, meta.ano)}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
// Aqui precisamos buscar o ID primeiro pois resultadoMeta não tem o ID
|
||||||
|
// Vamos usar o metaParaEditar.id que já foi carregado se o usuário clicou em editar antes
|
||||||
|
if (metaParaEditar && metaParaEditar.mes === meta.mes && metaParaEditar.ano === meta.ano && metaParaEditar.id) {
|
||||||
|
handleDeletarMeta(meta.mes, meta.ano, metaParaEditar.id);
|
||||||
|
} else {
|
||||||
|
// Buscar o ID antes de deletar
|
||||||
|
getMeta(userId, meta.mes, meta.ano).then(metaCompleta => {
|
||||||
|
if (metaCompleta && metaCompleta.id) {
|
||||||
|
handleDeletarMeta(meta.mes, meta.ano, metaCompleta.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -191,6 +251,34 @@ const MetasPage = () => {
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
open={isDeleteDialogOpen}
|
||||||
|
onOpenChange={setIsDeleteDialogOpen}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Confirmar exclusão
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{metaParaDeletar && (
|
||||||
|
`Tem certeza que deseja excluir a meta de ${format(new Date(metaParaDeletar.ano, metaParaDeletar.mes - 1, 1), 'MMMM', { locale: ptBR })} de ${metaParaDeletar.ano}? Esta ação não pode ser desfeita.`
|
||||||
|
)}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={isDeletingMeta}>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={confirmarDeletarMeta}
|
||||||
|
disabled={isDeletingMeta}
|
||||||
|
className="bg-destructive hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
{isDeletingMeta ? 'Deletando...' : 'Deletar Meta'}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user