From 4aaeebc28ce44946c04a7944cf286ecddfcf5b19 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:00:29 +0000 Subject: [PATCH] Implement account alerts features - Filter recurring accounts by logged-in user's email. - Add payment status tags based on month/year filter. - Add sent alert tags based on month/year filter. - Combine month/year and user filters. --- .../avisos/ContasRecorrentesList.tsx | 296 ++++++++++++------ src/components/avisos/FiltroMesAno.tsx | 83 +++++ src/components/avisos/StatusTags.tsx | 59 ++++ src/integrations/supabase/types.ts | 44 +++ 4 files changed, 386 insertions(+), 96 deletions(-) create mode 100644 src/components/avisos/FiltroMesAno.tsx create mode 100644 src/components/avisos/StatusTags.tsx diff --git a/src/components/avisos/ContasRecorrentesList.tsx b/src/components/avisos/ContasRecorrentesList.tsx index 004cf26..c8446a9 100644 --- a/src/components/avisos/ContasRecorrentesList.tsx +++ b/src/components/avisos/ContasRecorrentesList.tsx @@ -10,6 +10,8 @@ import { Edit, Trash2, Calendar, Clock, DollarSign, Bell } from 'lucide-react'; import { toast } from 'sonner'; import { format } from 'date-fns'; import { ptBR } from 'date-fns/locale'; +import FiltroMesAno from './FiltroMesAno'; +import StatusTags from './StatusTags'; interface ContaRecorrente { id: string; @@ -21,29 +23,108 @@ interface ContaRecorrente { dias_antecedencia: number; ativo: boolean; created_at: string; + email_usuario: string | null; +} + +interface StatusPagamento { + status: string | null; + valor_pago: number | null; + data_pagamento: string | null; +} + +interface AvisoEnviado { + data_aviso: string; } const ContasRecorrentesList = () => { - const { user } = useAuthStore(); + const { user, session } = useAuthStore(); const queryClient = useQueryClient(); const [deletingId, setDeletingId] = useState(null); const [hoveredCard, setHoveredCard] = useState(null); + + // Estado para filtro de mês/ano - inicializa com mês/ano atual + const dataAtual = new Date(); + const [mesAno, setMesAno] = useState({ + mes: dataAtual.getMonth() + 1, + ano: dataAtual.getFullYear() + }); + + // Buscar email do usuário logado + const userEmail = session?.user?.email || user?.email; const { data: contas, isLoading } = useQuery({ - queryKey: ['contas-recorrentes', user?.id], + queryKey: ['contas-recorrentes', userEmail], queryFn: async () => { - if (!user?.id) return []; + if (!userEmail) return []; const { data, error } = await supabase .from('contas_recorrentes') .select('*') - .eq('user_id', user.id) + .eq('email_usuario', userEmail) .order('created_at', { ascending: false }); if (error) throw error; return data as ContaRecorrente[]; }, - enabled: !!user?.id + enabled: !!userEmail + }); + + // Buscar status de pagamento para todas as contas do período selecionado + const { data: statusPagamentos } = useQuery({ + queryKey: ['status-pagamentos', mesAno.mes, mesAno.ano, contas?.map(c => c.id)], + queryFn: async () => { + if (!contas || contas.length === 0) return {}; + + const { data, error } = await supabase + .from('status_pagamento_mensal') + .select('conta_id, status, valor_pago, data_pagamento') + .in('conta_id', contas.map(c => c.id)) + .eq('mes', mesAno.mes) + .eq('ano', mesAno.ano); + + if (error) throw error; + + // Converter array em objeto indexado por conta_id + const statusMap: Record = {}; + data?.forEach(status => { + statusMap[status.conta_id] = status; + }); + + return statusMap; + }, + enabled: !!contas && contas.length > 0 + }); + + // Buscar avisos enviados para todas as contas do período selecionado + const { data: avisosEnviados } = useQuery({ + queryKey: ['avisos-enviados', mesAno.mes, mesAno.ano, contas?.map(c => c.id)], + queryFn: async () => { + if (!contas || contas.length === 0) return {}; + + // Criar range de datas para o mês/ano selecionado + const inicioMes = new Date(mesAno.ano, mesAno.mes - 1, 1); + const fimMes = new Date(mesAno.ano, mesAno.mes, 0); + + const { data, error } = await supabase + .from('avisos_enviados') + .select('conta_id, data_aviso') + .in('conta_id', contas.map(c => c.id)) + .gte('data_aviso', inicioMes.toISOString().split('T')[0]) + .lte('data_aviso', fimMes.toISOString().split('T')[0]); + + if (error) throw error; + + // Converter array em objeto indexado por conta_id (pega o primeiro aviso do mês) + const avisosMap: Record = {}; + data?.forEach(aviso => { + if (!avisosMap[aviso.conta_id]) { + avisosMap[aviso.conta_id] = aviso; + } + }); + + return avisosMap; + }, + enabled: !!contas && contas.length > 0 }); const handleDelete = async (id: string) => { @@ -102,110 +183,133 @@ const ContasRecorrentesList = () => { ); } - if (!contas || contas.length === 0) { + if (!userEmail) { return ( Suas Contas Recorrentes - Você ainda não cadastrou nenhuma conta recorrente + Você precisa estar logado para ver suas contas - -
- -

Clique em "Nova Conta" para começar

-
-
); } return ( -
-

Suas Contas Recorrentes

- -
- {contas.map((conta) => ( -
setHoveredCard(conta.id)} - onMouseLeave={() => setHoveredCard(null)} - > - {hoveredCard === conta.id && ( -
- )} - - - -
-
- {conta.nome_conta} - {conta.descricao && ( - {conta.descricao} - )} -
- - {conta.ativo ? 'Ativo' : 'Inativo'} - -
-
- - -
-
- - Valor: - {formatCurrency(conta.valor)} -
- -
- - Vencimento: - Todo dia {conta.dia_vencimento} -
- -
- - Horário: - {conta.hora_aviso} -
- -
- - Antecedência: - {conta.dias_antecedencia} dia(s) -
-
+
+ {/* Filtro de Mês/Ano */} + -
- + {/* Lista de Contas */} + {!contas || contas.length === 0 ? ( + + + Suas Contas Recorrentes + Você ainda não cadastrou nenhuma conta recorrente + + +
+ +

Clique em "Nova Conta" para começar

+
+
+
+ ) : ( +
+

+ Suas Contas Recorrentes - {mesAno.mes.toString().padStart(2, '0')}/{mesAno.ano} +

+ +
+ {contas.map((conta) => ( +
setHoveredCard(conta.id)} + onMouseLeave={() => setHoveredCard(null)} + > + {hoveredCard === conta.id && ( +
+ )} + + + +
+
+ {conta.nome_conta} + {conta.descricao && ( + {conta.descricao} + )} +
+ + {conta.ativo ? 'Ativo' : 'Inativo'} + +
+
- -
- - + + {/* Tags de Status */} + + +
+
+ + Valor: + {formatCurrency(conta.valor)} +
+ +
+ + Vencimento: + Todo dia {conta.dia_vencimento} +
+ +
+ + Horário: + {conta.hora_aviso} +
+ +
+ + Antecedência: + {conta.dias_antecedencia} dia(s) +
+
+ +
+ + + +
+
+ +
+ ))}
- ))} -
+
+ )}
); }; diff --git a/src/components/avisos/FiltroMesAno.tsx b/src/components/avisos/FiltroMesAno.tsx new file mode 100644 index 0000000..26cb984 --- /dev/null +++ b/src/components/avisos/FiltroMesAno.tsx @@ -0,0 +1,83 @@ + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Calendar } from 'lucide-react'; + +interface FiltroMesAnoProps { + mesAno: { mes: number; ano: number }; + onMesAnoChange: (mesAno: { mes: number; ano: number }) => void; +} + +const FiltroMesAno = ({ mesAno, onMesAnoChange }: FiltroMesAnoProps) => { + const meses = [ + { value: 1, label: 'Janeiro' }, + { value: 2, label: 'Fevereiro' }, + { value: 3, label: 'Março' }, + { value: 4, label: 'Abril' }, + { value: 5, label: 'Maio' }, + { value: 6, label: 'Junho' }, + { value: 7, label: 'Julho' }, + { value: 8, label: 'Agosto' }, + { value: 9, label: 'Setembro' }, + { value: 10, label: 'Outubro' }, + { value: 11, label: 'Novembro' }, + { value: 12, label: 'Dezembro' } + ]; + + const anos = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - 2 + i); + + return ( + + + + + Filtrar por Período + + + +
+
+ + +
+ +
+ + +
+
+
+
+ ); +}; + +export default FiltroMesAno; diff --git a/src/components/avisos/StatusTags.tsx b/src/components/avisos/StatusTags.tsx new file mode 100644 index 0000000..075c4e4 --- /dev/null +++ b/src/components/avisos/StatusTags.tsx @@ -0,0 +1,59 @@ + +import React from 'react'; +import { Badge } from '@/components/ui/badge'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; + +interface StatusPagamento { + status: string | null; + valor_pago: number | null; + data_pagamento: string | null; +} + +interface AvisoEnviado { + data_aviso: string; +} + +interface StatusTagsProps { + statusPagamento: StatusPagamento | null; + avisoEnviado: AvisoEnviado | null; +} + +const StatusTags = ({ statusPagamento, avisoEnviado }: StatusTagsProps) => { + const formatCurrency = (value: number | null) => { + if (value === null) return 'R$ 0,00'; + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' + }).format(value); + }; + + const formatDate = (dateString: string) => { + return format(new Date(dateString), 'dd/MM/yyyy', { locale: ptBR }); + }; + + return ( +
+ {/* Tag de Status de Pagamento */} + {statusPagamento?.status === 'Pago' ? ( + + Pago: {formatCurrency(statusPagamento.valor_pago)} em{' '} + {statusPagamento.data_pagamento ? formatDate(statusPagamento.data_pagamento) : 'Data não informada'} + + ) : ( + + Pendente no mês selecionado + + )} + + {/* Tag de Aviso Enviado */} + {avisoEnviado && ( + + Aviso enviado em {formatDate(avisoEnviado.data_aviso)} + + )} +
+ ); +}; + +export default StatusTags; diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 98553e5..40b9010 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -647,6 +647,50 @@ export type Database = { }, ] } + status_pagamento_mensal: { + Row: { + ano: number + conta_id: string | null + created_at: string | null + data_pagamento: string | null + id: string + login: string | null + mes: number + status: string | null + valor_pago: number | null + } + Insert: { + ano: number + conta_id?: string | null + created_at?: string | null + data_pagamento?: string | null + id?: string + login?: string | null + mes: number + status?: string | null + valor_pago?: number | null + } + Update: { + ano?: number + conta_id?: string | null + created_at?: string | null + data_pagamento?: string | null + id?: string + login?: string | null + mes?: number + status?: string | null + valor_pago?: number | null + } + Relationships: [ + { + foreignKeyName: "status_pagamento_mensal_conta_id_fkey" + columns: ["conta_id"] + isOneToOne: false + referencedRelation: "contas_recorrentes" + referencedColumns: ["id"] + }, + ] + } transacoes: { Row: { categoria: string | null