Add delete meta option to menu

Adds a delete option to the meta menu for deleting registered metas.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-22 21:54:38 +00:00
parent 002836773d
commit 9a2f18c74e
2 changed files with 150 additions and 69 deletions

View File

@ -1,90 +1,83 @@
import { format } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import { Trophy, Flag, FlagOff } from 'lucide-react';
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardFooter } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Button } from '@/components/ui/button';
import { ResultadoMeta } from '@/types/financialTypes';
import { Check, Edit2, Trash2 } from 'lucide-react';
interface MetaProgressCardProps {
meta: ResultadoMeta;
onEditClick?: () => void;
onEditClick: () => void;
onDeleteClick: () => void;
}
export function MetaProgressCard({ meta, onEditClick }: MetaProgressCardProps) {
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
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';
}
};
export function MetaProgressCard({ meta, onEditClick, onDeleteClick }: MetaProgressCardProps) {
// Format the month name in Portuguese
const mesFormatado = format(new Date(meta.ano, meta.mes - 1, 1), 'MMMM', { locale: ptBR });
const mesCapitalizado = mesFormatado.charAt(0).toUpperCase() + mesFormatado.slice(1);
return (
<Card className="overflow-hidden">
<CardHeader className={`pb-2 ${meta.meta_batida ? 'bg-green-50' : 'bg-gray-50'}`}>
<CardTitle className="flex items-center justify-between">
<span className="capitalize">{getMesNome(meta.mes)} {meta.ano}</span>
<div className="flex items-center">
{getStatusIcon()}
<CardContent className="p-4">
<div className="flex justify-between items-center mb-3">
<div>
<h3 className="text-lg font-medium">{mesCapitalizado}</h3>
<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>
</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 className="space-y-1">
<div className="h-2 w-full">
<Progress
value={meta.percentual_atingido}
max={100}
className={getProgressColor()}
/>
<div className="mb-4">
<div className="flex justify-between mb-1">
<span className="text-sm">Meta de Economia</span>
<span className="text-sm font-semibold">R$ {meta.valor_meta.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
</div>
<div className="flex justify-between text-xs">
<span>0%</span>
<span className="font-medium">{meta.percentual_atingido.toFixed(0)}% atingido</span>
<span>100%</span>
<div className="flex justify-between">
<span className="text-sm">Economia Real</span>
<span className="text-sm font-semibold">R$ {meta.economia_real.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
</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>
{onEditClick && (
<CardFooter className="bg-muted/50 pt-1 px-4 pb-2">
<button
onClick={onEditClick}
className="text-sm text-muted-foreground hover:text-primary w-full text-center"
>
Editar meta
</button>
</CardFooter>
)}
<CardFooter className={`p-3 ${meta.meta_batida ? 'bg-green-100 dark:bg-green-900/20' : 'bg-muted/50'}`}>
<div className="flex items-center w-full justify-between">
<span className="text-sm font-medium">
{meta.meta_batida ? 'Meta atingida!' : 'Meta em andamento'}
</span>
{meta.meta_batida && <Check className="h-4 w-4 text-green-600 dark:text-green-500" />}
</div>
</CardFooter>
</Card>
);
}

View File

@ -9,7 +9,7 @@ import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { ResultadoMeta } from '@/types/financialTypes';
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 {
Dialog,
@ -18,6 +18,16 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
const MetasPage = () => {
const { toast } = useToast();
@ -25,8 +35,11 @@ const MetasPage = () => {
const [resultados, setResultados] = useState<ResultadoMeta[]>([]);
const [isLoading, setIsLoading] = useState(true);
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 [isDeletingMeta, setIsDeletingMeta] = useState(false);
useEffect(() => {
// Obter userId armazenado no localStorage
@ -72,7 +85,7 @@ const MetasPage = () => {
const meta = await getMeta(userId, mes, ano);
if (meta) {
setValorMetaAtual(meta.valor_meta);
setMetaParaEditar({ mes, ano });
setMetaParaEditar({ mes, ano, id: meta.id });
setIsDialogOpen(true);
}
} 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 = () => {
setMetaParaEditar(null);
setValorMetaAtual(0);
@ -161,6 +207,20 @@ const MetasPage = () => {
key={`${meta.ano}-${meta.mes}`}
meta={meta}
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>
@ -191,6 +251,34 @@ const MetasPage = () => {
/>
</DialogContent>
</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>
</Layout>
);