- Added ResponsiveDialog component for handling responsive dialogs in the UI. - Created ExpensesProvider to manage state and API interactions for expenses. - Developed ExpensesTabContent to display expenses with filtering options and dialogs for new and pay expense actions. - Implemented ExpensesTable for rendering expense data in a tabular format with actions. - Added dialogs for creating new expenses and paying existing ones with form validation. - Introduced PeriodicExpenseForm for managing periodic expenses with month selection. - Created PeriodicExpensesTabContent to manage and display periodic expenses with create/edit functionality. - Added GenerateCurrentMonthDialog for confirming monthly expense generation. - Implemented PeriodicExpensesTable for displaying periodic expenses with edit and delete actions.
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useForm, Controller } from "react-hook-form";
|
|
import { z } from "zod";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import type { Expense } from "@/lib/api";
|
|
import { useExpensesContext } from "../ExpensesProvider";
|
|
import { DatePicker } from "@/components/ui/date-picker";
|
|
import {
|
|
ResponsiveDialog,
|
|
ResponsiveDialogContent,
|
|
ResponsiveDialogHeader,
|
|
ResponsiveDialogTitle,
|
|
ResponsiveDialogFooter,
|
|
} from "@/components/ui/responsive-dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
const paySchema = z.object({
|
|
amountPayed: z.number().positive("El monto debe ser mayor a 0"),
|
|
paymentDate: z.date({ message: "La fecha es obligatoria" }),
|
|
});
|
|
|
|
type PayFormValues = z.infer<typeof paySchema>;
|
|
|
|
interface PayExpenseDialogProps {
|
|
expense: Expense | null;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}
|
|
|
|
export function PayExpenseDialog({ expense, open, onOpenChange }: PayExpenseDialogProps) {
|
|
const { payExpense } = useExpensesContext();
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
control,
|
|
formState: { errors, isSubmitting },
|
|
reset,
|
|
} = useForm<PayFormValues>({
|
|
resolver: zodResolver(paySchema),
|
|
defaultValues: {
|
|
amountPayed: 0,
|
|
paymentDate: undefined,
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (expense) {
|
|
reset({
|
|
amountPayed: Number(expense.amount),
|
|
paymentDate: new Date(),
|
|
});
|
|
}
|
|
}, [expense, reset]);
|
|
|
|
function handleClose(open: boolean) {
|
|
onOpenChange(open);
|
|
if (!open) reset();
|
|
}
|
|
|
|
async function onSubmit(data: PayFormValues) {
|
|
if (!expense) return;
|
|
await payExpense(expense.id, {
|
|
amountPayed: data.amountPayed,
|
|
paymentDate: data.paymentDate.toISOString(),
|
|
});
|
|
handleClose(false);
|
|
}
|
|
|
|
if (!expense) return null;
|
|
|
|
return (
|
|
<ResponsiveDialog open={open} onOpenChange={handleClose}>
|
|
<ResponsiveDialogContent>
|
|
<ResponsiveDialogHeader>
|
|
<ResponsiveDialogTitle>Registrar pago</ResponsiveDialogTitle>
|
|
</ResponsiveDialogHeader>
|
|
|
|
<div className="flex flex-col gap-1 rounded-lg bg-muted px-3 py-2 text-sm">
|
|
<span className="font-medium">{expense.description}</span>
|
|
<span className="text-muted-foreground">
|
|
Monto original: ${Number(expense.amount).toLocaleString("es-AR", { minimumFractionDigits: 2 })}
|
|
</span>
|
|
</div>
|
|
|
|
<form id="pay-form" onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
|
|
<div className="flex flex-col gap-1.5">
|
|
<label htmlFor="amountPayed" className="text-sm font-medium">
|
|
Monto pagado
|
|
</label>
|
|
<input
|
|
id="amountPayed"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
{...register("amountPayed", { valueAsNumber: true })}
|
|
className="h-8 rounded-lg border border-input bg-background px-2.5 text-sm outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
|
|
/>
|
|
{errors.amountPayed && (
|
|
<span className="text-xs text-destructive">{errors.amountPayed.message}</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="text-sm font-medium">
|
|
Fecha de pago
|
|
</label>
|
|
<Controller
|
|
control={control}
|
|
name="paymentDate"
|
|
render={({ field }) => (
|
|
<DatePicker
|
|
value={field.value}
|
|
onChange={field.onChange}
|
|
placeholder="Seleccionar fecha"
|
|
/>
|
|
)}
|
|
/>
|
|
{errors.paymentDate && (
|
|
<span className="text-xs text-destructive">{errors.paymentDate.message}</span>
|
|
)}
|
|
</div>
|
|
</form>
|
|
|
|
<ResponsiveDialogFooter>
|
|
<Button variant="outline" onClick={() => handleClose(false)} disabled={isSubmitting}>
|
|
Cancelar
|
|
</Button>
|
|
<Button type="submit" form="pay-form" disabled={isSubmitting}>
|
|
Confirmar pago
|
|
</Button>
|
|
</ResponsiveDialogFooter>
|
|
</ResponsiveDialogContent>
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|