Refactor: Split TransactionForm into smaller components
Refactors TransactionForm.tsx into smaller, reusable components and a custom hook to improve code organization and maintainability.
This commit is contained in:
parent
1cb5be29cc
commit
e6d6f5c2b3
@ -1,42 +1,13 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import * as z from 'zod';
|
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { Transaction } from '@/types/financialTypes';
|
import { Transaction } from '@/types/financialTypes';
|
||||||
import { updateTransacao } from '@/services/transacaoService';
|
import { Form } from '@/components/ui/form';
|
||||||
|
import { useTransactionForm } from '@/hooks/useTransactionForm';
|
||||||
const transactionSchema = z.object({
|
import { TransactionTextField } from '@/components/transaction/TransactionTextField';
|
||||||
estabelecimento: z.string().min(1, { message: 'Estabelecimento é obrigatório' }),
|
import { TransactionTypeField } from '@/components/transaction/TransactionTypeField';
|
||||||
valor: z.string().min(1, { message: 'Valor é obrigatório' }),
|
import { TransactionDateField } from '@/components/transaction/TransactionDateField';
|
||||||
detalhes: z.string().min(1, { message: 'Detalhes são obrigatórios' }),
|
import { GroupSelectField } from '@/components/transaction/GroupSelectField';
|
||||||
categoria: z.string().min(1, { message: 'Categoria é obrigatória' }),
|
import { TransactionFormActions } from '@/components/transaction/TransactionFormActions';
|
||||||
tipo: z.string().min(1, { message: 'Tipo é obrigatório' }),
|
|
||||||
quando: z.string().min(1, { message: 'Data é obrigatória' }),
|
|
||||||
grupo_id: z.string().nullable().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
type TransactionFormValues = z.infer<typeof transactionSchema>;
|
|
||||||
|
|
||||||
interface TransactionFormProps {
|
interface TransactionFormProps {
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
@ -55,305 +26,69 @@ export function TransactionForm({
|
|||||||
transaction = null,
|
transaction = null,
|
||||||
isEditing = false
|
isEditing = false
|
||||||
}: TransactionFormProps) {
|
}: TransactionFormProps) {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const {
|
||||||
const [grupos, setGrupos] = useState<{remote_jid: string, nome_grupo: string | null}[]>([]);
|
form,
|
||||||
const { toast } = useToast();
|
grupos,
|
||||||
const navigate = useNavigate();
|
isSubmitting,
|
||||||
|
onSubmit,
|
||||||
|
fetchGrupos
|
||||||
|
} = useTransactionForm(
|
||||||
|
onSuccess,
|
||||||
|
onCancel,
|
||||||
|
defaultTipo,
|
||||||
|
transaction,
|
||||||
|
isEditing
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch user groups when the form loads
|
// Fetch user groups when the form loads
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGrupos = async () => {
|
|
||||||
const userEmail = localStorage.getItem('userEmail');
|
|
||||||
if (!userEmail) {
|
|
||||||
console.error('Email do usuário não encontrado');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedEmail = userEmail.trim().toLowerCase();
|
|
||||||
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from('grupos_whatsapp')
|
|
||||||
.select('remote_jid, nome_grupo')
|
|
||||||
.eq('login', normalizedEmail);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('Erro ao buscar grupos:', error);
|
|
||||||
} else if (data) {
|
|
||||||
setGrupos(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchGrupos();
|
fetchGrupos();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Set up form with default values or transaction data if editing
|
|
||||||
const form = useForm<TransactionFormValues>({
|
|
||||||
resolver: zodResolver(transactionSchema),
|
|
||||||
defaultValues: {
|
|
||||||
estabelecimento: transaction?.estabelecimento || '',
|
|
||||||
valor: transaction ? String(Math.abs(transaction.valor)) : '',
|
|
||||||
detalhes: transaction?.detalhes || '',
|
|
||||||
categoria: transaction?.categoria || '',
|
|
||||||
tipo: transaction?.tipo as 'receita' | 'despesa' || defaultTipo,
|
|
||||||
quando: transaction?.quando ?
|
|
||||||
new Date(transaction.quando).toISOString().split('T')[0] :
|
|
||||||
new Date().toISOString().split('T')[0],
|
|
||||||
grupo_id: transaction?.grupo_id || grupoId || null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function onSubmit(data: TransactionFormValues) {
|
|
||||||
setIsSubmitting(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get user email from localStorage - principal identifier now
|
|
||||||
const userEmail = localStorage.getItem('userEmail');
|
|
||||||
|
|
||||||
if (!userEmail) {
|
|
||||||
toast({
|
|
||||||
title: "Erro no formulário",
|
|
||||||
description: "Email do usuário não encontrado",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the email (lowercase and trim spaces)
|
|
||||||
const normalizedEmail = userEmail.trim().toLowerCase();
|
|
||||||
|
|
||||||
// Get user ID from localStorage - kept for compatibility
|
|
||||||
const userId = localStorage.getItem('userId') || '';
|
|
||||||
|
|
||||||
const valorNumerico = parseFloat(data.valor.replace(',', '.'));
|
|
||||||
|
|
||||||
if (isNaN(valorNumerico)) {
|
|
||||||
toast({
|
|
||||||
title: "Erro no formulário",
|
|
||||||
description: "O valor precisa ser um número válido",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the value based on transaction type
|
|
||||||
const valorFinal = data.tipo.toLowerCase() === 'receita'
|
|
||||||
? Math.abs(valorNumerico)
|
|
||||||
: Math.abs(valorNumerico);
|
|
||||||
|
|
||||||
console.log(`Salvando transação para usuário: ${normalizedEmail} (ID: ${userId})`);
|
|
||||||
|
|
||||||
// Handle editing vs creating new transaction
|
|
||||||
if (isEditing && transaction) {
|
|
||||||
// Update existing transaction
|
|
||||||
const updatedTransaction = {
|
|
||||||
...transaction,
|
|
||||||
estabelecimento: data.estabelecimento,
|
|
||||||
valor: valorFinal,
|
|
||||||
detalhes: data.detalhes,
|
|
||||||
categoria: data.categoria,
|
|
||||||
tipo: data.tipo,
|
|
||||||
quando: data.quando,
|
|
||||||
grupo_id: data.grupo_id || null
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateTransacao(updatedTransaction);
|
|
||||||
toast({
|
|
||||||
title: "Transação atualizada",
|
|
||||||
description: "Transação atualizada com sucesso",
|
|
||||||
});
|
|
||||||
onSuccess();
|
|
||||||
} else {
|
|
||||||
// Create new transaction
|
|
||||||
// Prepare transaction data including login and grupo_id
|
|
||||||
const transactionData = {
|
|
||||||
user: userId, // Kept for compatibility
|
|
||||||
login: normalizedEmail, // Main field for identification
|
|
||||||
estabelecimento: data.estabelecimento,
|
|
||||||
valor: valorFinal,
|
|
||||||
detalhes: data.detalhes,
|
|
||||||
categoria: data.categoria,
|
|
||||||
tipo: data.tipo,
|
|
||||||
quando: data.quando,
|
|
||||||
grupo_id: data.grupo_id || null
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Dados da transação a serem salvos:', transactionData);
|
|
||||||
|
|
||||||
// With RLS disabled, we can insert directly to the fixed table
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('transacoes')
|
|
||||||
.insert([transactionData]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('Erro ao salvar transação:', error);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Erro ao salvar",
|
|
||||||
description: `Não foi possível salvar a transação: ${error.message}`,
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: "Transação salva",
|
|
||||||
description: "Transação registrada com sucesso",
|
|
||||||
});
|
|
||||||
onSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro ao processar formulário:', error);
|
|
||||||
toast({
|
|
||||||
title: "Erro inesperado",
|
|
||||||
description: "Ocorreu um erro ao processar sua solicitação",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
<TransactionTextField
|
||||||
control={form.control}
|
form={form}
|
||||||
name="estabelecimento"
|
name="estabelecimento"
|
||||||
render={({ field }) => (
|
label="Estabelecimento"
|
||||||
<FormItem>
|
placeholder="Nome do estabelecimento"
|
||||||
<FormLabel>Estabelecimento</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Nome do estabelecimento" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<TransactionTextField
|
||||||
control={form.control}
|
form={form}
|
||||||
name="valor"
|
name="valor"
|
||||||
render={({ field }) => (
|
label="Valor"
|
||||||
<FormItem>
|
placeholder="0,00"
|
||||||
<FormLabel>Valor</FormLabel>
|
type="text"
|
||||||
<FormControl>
|
inputMode="decimal"
|
||||||
<Input
|
|
||||||
placeholder="0,00"
|
|
||||||
{...field}
|
|
||||||
type="text"
|
|
||||||
inputMode="decimal"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<TransactionTypeField form={form} />
|
||||||
control={form.control}
|
|
||||||
name="tipo"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Tipo</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Selecione o tipo" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="despesa">Despesa</SelectItem>
|
|
||||||
<SelectItem value="receita">Receita</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
<TransactionTextField
|
||||||
control={form.control}
|
form={form}
|
||||||
name="categoria"
|
name="categoria"
|
||||||
render={({ field }) => (
|
label="Categoria"
|
||||||
<FormItem>
|
placeholder="Categoria"
|
||||||
<FormLabel>Categoria</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Categoria" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<TransactionTextField
|
||||||
control={form.control}
|
form={form}
|
||||||
name="detalhes"
|
name="detalhes"
|
||||||
render={({ field }) => (
|
label="Detalhes"
|
||||||
<FormItem>
|
placeholder="Detalhes da transação"
|
||||||
<FormLabel>Detalhes</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Detalhes da transação" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<TransactionDateField form={form} />
|
||||||
control={form.control}
|
|
||||||
name="quando"
|
<GroupSelectField form={form} grupos={grupos} />
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
<TransactionFormActions
|
||||||
<FormLabel>Data</FormLabel>
|
onCancel={onCancel}
|
||||||
<FormControl>
|
isSubmitting={isSubmitting}
|
||||||
<Input type="date" {...field} />
|
defaultTipo={defaultTipo}
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{grupos.length > 0 && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="grupo_id"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Grupo WhatsApp (opcional)</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value || undefined}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Selecione um grupo (opcional)" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">Nenhum grupo</SelectItem>
|
|
||||||
{grupos.map((grupo) => (
|
|
||||||
<SelectItem key={grupo.remote_jid} value={grupo.remote_jid}>
|
|
||||||
{grupo.nome_grupo || grupo.remote_jid}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 pt-2">
|
|
||||||
<Button variant="outline" type="button" onClick={onCancel} disabled={isSubmitting}>
|
|
||||||
Cancelar
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" disabled={isSubmitting} className={defaultTipo === 'receita' ? 'bg-finance-green hover:bg-finance-green/90' : ''}>
|
|
||||||
{isSubmitting ? "Salvando..." : "Salvar"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
45
src/components/transaction/GroupSelectField.tsx
Normal file
45
src/components/transaction/GroupSelectField.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
import { FormControl, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { UseFormReturn } from "react-hook-form";
|
||||||
|
import { TransactionFormValues } from "@/hooks/useTransactionForm";
|
||||||
|
|
||||||
|
interface GroupSelectFieldProps {
|
||||||
|
form: UseFormReturn<TransactionFormValues>;
|
||||||
|
grupos: {remote_jid: string, nome_grupo: string | null}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupSelectField({ form, grupos }: GroupSelectFieldProps) {
|
||||||
|
if (grupos.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Grupo WhatsApp (opcional)</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => form.setValue("grupo_id", value === "none" ? null : value)}
|
||||||
|
value={form.getValues("grupo_id") || undefined}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione um grupo (opcional)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">Nenhum grupo</SelectItem>
|
||||||
|
{grupos.map((grupo) => (
|
||||||
|
<SelectItem key={grupo.remote_jid} value={grupo.remote_jid}>
|
||||||
|
{grupo.nome_grupo || grupo.remote_jid}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/components/transaction/TransactionDateField.tsx
Normal file
24
src/components/transaction/TransactionDateField.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
import { FormControl, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { UseFormReturn } from "react-hook-form";
|
||||||
|
import { TransactionFormValues } from "@/hooks/useTransactionForm";
|
||||||
|
|
||||||
|
interface TransactionDateFieldProps {
|
||||||
|
form: UseFormReturn<TransactionFormValues>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransactionDateField({ form }: TransactionDateFieldProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Data</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
{...form.register("quando")}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
src/components/transaction/TransactionFormActions.tsx
Normal file
34
src/components/transaction/TransactionFormActions.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
interface TransactionFormActionsProps {
|
||||||
|
onCancel: () => void;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
defaultTipo?: 'receita' | 'despesa';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransactionFormActions({
|
||||||
|
onCancel,
|
||||||
|
isSubmitting,
|
||||||
|
defaultTipo = 'despesa'
|
||||||
|
}: TransactionFormActionsProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-end space-x-2 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className={defaultTipo === 'receita' ? 'bg-finance-green hover:bg-finance-green/90' : ''}
|
||||||
|
>
|
||||||
|
{isSubmitting ? "Salvando..." : "Salvar"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/components/transaction/TransactionTextField.tsx
Normal file
38
src/components/transaction/TransactionTextField.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
import { FormControl, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { UseFormReturn } from "react-hook-form";
|
||||||
|
import { TransactionFormValues } from "@/hooks/useTransactionForm";
|
||||||
|
|
||||||
|
interface TransactionTextFieldProps {
|
||||||
|
form: UseFormReturn<TransactionFormValues>;
|
||||||
|
name: keyof TransactionFormValues;
|
||||||
|
label: string;
|
||||||
|
placeholder: string;
|
||||||
|
type?: string;
|
||||||
|
inputMode?: "text" | "numeric" | "decimal" | "tel" | "search" | "email" | "url" | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransactionTextField({
|
||||||
|
form,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
type = "text",
|
||||||
|
inputMode
|
||||||
|
}: TransactionTextFieldProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...form.register(name)}
|
||||||
|
type={type}
|
||||||
|
inputMode={inputMode}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/components/transaction/TransactionTypeField.tsx
Normal file
38
src/components/transaction/TransactionTypeField.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
import { FormControl, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { UseFormReturn } from "react-hook-form";
|
||||||
|
import { TransactionFormValues } from "@/hooks/useTransactionForm";
|
||||||
|
|
||||||
|
interface TransactionTypeFieldProps {
|
||||||
|
form: UseFormReturn<TransactionFormValues>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransactionTypeField({ form }: TransactionTypeFieldProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Tipo</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => form.setValue("tipo", value)}
|
||||||
|
defaultValue={form.getValues("tipo")}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione o tipo" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="despesa">Despesa</SelectItem>
|
||||||
|
<SelectItem value="receita">Receita</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
191
src/hooks/useTransactionForm.ts
Normal file
191
src/hooks/useTransactionForm.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import * as z from 'zod';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { Transaction } from '@/types/financialTypes';
|
||||||
|
import { updateTransacao } from '@/services/transacaoService';
|
||||||
|
|
||||||
|
const transactionSchema = z.object({
|
||||||
|
estabelecimento: z.string().min(1, { message: 'Estabelecimento é obrigatório' }),
|
||||||
|
valor: z.string().min(1, { message: 'Valor é obrigatório' }),
|
||||||
|
detalhes: z.string().min(1, { message: 'Detalhes são obrigatórios' }),
|
||||||
|
categoria: z.string().min(1, { message: 'Categoria é obrigatória' }),
|
||||||
|
tipo: z.string().min(1, { message: 'Tipo é obrigatório' }),
|
||||||
|
quando: z.string().min(1, { message: 'Data é obrigatória' }),
|
||||||
|
grupo_id: z.string().nullable().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TransactionFormValues = z.infer<typeof transactionSchema>;
|
||||||
|
|
||||||
|
export const useTransactionForm = (
|
||||||
|
onSuccess: () => void,
|
||||||
|
onCancel: () => void,
|
||||||
|
defaultTipo: 'receita' | 'despesa' = 'despesa',
|
||||||
|
transaction: Transaction | null = null,
|
||||||
|
isEditing: boolean = false
|
||||||
|
) => {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [grupos, setGrupos] = useState<{remote_jid: string, nome_grupo: string | null}[]>([]);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// Fetch user groups when the form loads
|
||||||
|
const fetchGrupos = async () => {
|
||||||
|
const userEmail = localStorage.getItem('userEmail');
|
||||||
|
if (!userEmail) {
|
||||||
|
console.error('Email do usuário não encontrado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedEmail = userEmail.trim().toLowerCase();
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('grupos_whatsapp')
|
||||||
|
.select('remote_jid, nome_grupo')
|
||||||
|
.eq('login', normalizedEmail);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Erro ao buscar grupos:', error);
|
||||||
|
} else if (data) {
|
||||||
|
setGrupos(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up form with default values or transaction data if editing
|
||||||
|
const form = useForm<TransactionFormValues>({
|
||||||
|
resolver: zodResolver(transactionSchema),
|
||||||
|
defaultValues: {
|
||||||
|
estabelecimento: transaction?.estabelecimento || '',
|
||||||
|
valor: transaction ? String(Math.abs(transaction.valor)) : '',
|
||||||
|
detalhes: transaction?.detalhes || '',
|
||||||
|
categoria: transaction?.categoria || '',
|
||||||
|
tipo: transaction?.tipo as 'receita' | 'despesa' || defaultTipo,
|
||||||
|
quando: transaction?.quando ?
|
||||||
|
new Date(transaction.quando).toISOString().split('T')[0] :
|
||||||
|
new Date().toISOString().split('T')[0],
|
||||||
|
grupo_id: transaction?.grupo_id || null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onSubmit(data: TransactionFormValues) {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user email from localStorage - principal identifier now
|
||||||
|
const userEmail = localStorage.getItem('userEmail');
|
||||||
|
|
||||||
|
if (!userEmail) {
|
||||||
|
toast({
|
||||||
|
title: "Erro no formulário",
|
||||||
|
description: "Email do usuário não encontrado",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the email (lowercase and trim spaces)
|
||||||
|
const normalizedEmail = userEmail.trim().toLowerCase();
|
||||||
|
|
||||||
|
// Get user ID from localStorage - kept for compatibility
|
||||||
|
const userId = localStorage.getItem('userId') || '';
|
||||||
|
|
||||||
|
const valorNumerico = parseFloat(data.valor.replace(',', '.'));
|
||||||
|
|
||||||
|
if (isNaN(valorNumerico)) {
|
||||||
|
toast({
|
||||||
|
title: "Erro no formulário",
|
||||||
|
description: "O valor precisa ser um número válido",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the value based on transaction type
|
||||||
|
const valorFinal = data.tipo.toLowerCase() === 'receita'
|
||||||
|
? Math.abs(valorNumerico)
|
||||||
|
: Math.abs(valorNumerico);
|
||||||
|
|
||||||
|
console.log(`Salvando transação para usuário: ${normalizedEmail} (ID: ${userId})`);
|
||||||
|
|
||||||
|
// Handle editing vs creating new transaction
|
||||||
|
if (isEditing && transaction) {
|
||||||
|
// Update existing transaction
|
||||||
|
const updatedTransaction = {
|
||||||
|
...transaction,
|
||||||
|
estabelecimento: data.estabelecimento,
|
||||||
|
valor: valorFinal,
|
||||||
|
detalhes: data.detalhes,
|
||||||
|
categoria: data.categoria,
|
||||||
|
tipo: data.tipo,
|
||||||
|
quando: data.quando,
|
||||||
|
grupo_id: data.grupo_id || null
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateTransacao(updatedTransaction);
|
||||||
|
toast({
|
||||||
|
title: "Transação atualizada",
|
||||||
|
description: "Transação atualizada com sucesso",
|
||||||
|
});
|
||||||
|
onSuccess();
|
||||||
|
} else {
|
||||||
|
// Create new transaction
|
||||||
|
// Prepare transaction data including login and grupo_id
|
||||||
|
const transactionData = {
|
||||||
|
user: userId, // Kept for compatibility
|
||||||
|
login: normalizedEmail, // Main field for identification
|
||||||
|
estabelecimento: data.estabelecimento,
|
||||||
|
valor: valorFinal,
|
||||||
|
detalhes: data.detalhes,
|
||||||
|
categoria: data.categoria,
|
||||||
|
tipo: data.tipo,
|
||||||
|
quando: data.quando,
|
||||||
|
grupo_id: data.grupo_id || null
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Dados da transação a serem salvos:', transactionData);
|
||||||
|
|
||||||
|
// With RLS disabled, we can insert directly to the fixed table
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('transacoes')
|
||||||
|
.insert([transactionData]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Erro ao salvar transação:', error);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Erro ao salvar",
|
||||||
|
description: `Não foi possível salvar a transação: ${error.message}`,
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Transação salva",
|
||||||
|
description: "Transação registrada com sucesso",
|
||||||
|
});
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao processar formulário:', error);
|
||||||
|
toast({
|
||||||
|
title: "Erro inesperado",
|
||||||
|
description: "Ocorreu um erro ao processar sua solicitação",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
grupos,
|
||||||
|
isSubmitting,
|
||||||
|
onSubmit,
|
||||||
|
fetchGrupos
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { transactionSchema };
|
||||||
Loading…
Reference in New Issue
Block a user