feat(expenses): implement responsive dialog for expense management and create expense provider
- 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.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { useState } from "react";
|
||||
import { useExpensesContext } from "../ExpensesProvider";
|
||||
import { PeriodicExpensesTable } from "./PeriodicExpensesTable";
|
||||
import { PeriodicExpenseForm } from "./PeriodicExpenseForm";
|
||||
import { GenerateCurrentMonthDialog } from "./GenerateCurrentMonthDialog";
|
||||
import {
|
||||
ResponsiveDialog,
|
||||
ResponsiveDialogContent,
|
||||
ResponsiveDialogHeader,
|
||||
ResponsiveDialogTitle,
|
||||
} from "@/components/ui/responsive-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
import type { PeriodicExpense } from "@/lib/api";
|
||||
|
||||
export function PeriodicExpensesTabContent() {
|
||||
const {
|
||||
periodicExpenses,
|
||||
isLoadingPeriodic,
|
||||
createPeriodicExpense,
|
||||
updatePeriodicExpense,
|
||||
deletePeriodicExpense,
|
||||
generateMonthlyExpense,
|
||||
} = useExpensesContext();
|
||||
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editingItem, setEditingItem] = useState<PeriodicExpense | null>(null);
|
||||
const [generateDialog, setGenerateDialog] = useState<{
|
||||
open: boolean;
|
||||
description: string;
|
||||
periodicExpenseId: number;
|
||||
}>({ open: false, description: "", periodicExpenseId: 0 });
|
||||
|
||||
function openCreate() {
|
||||
setEditingItem(null);
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function openEdit(item: PeriodicExpense) {
|
||||
setEditingItem(item);
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
setDialogOpen(false);
|
||||
setEditingItem(null);
|
||||
}
|
||||
|
||||
async function handleSubmit(data: { description: string; defaultDueDay: number; defaultAmount: number; periods: number[] }) {
|
||||
if (editingItem) {
|
||||
await updatePeriodicExpense(editingItem.id, data);
|
||||
} else {
|
||||
const numericData = {
|
||||
description: data.description,
|
||||
defaultDueDay: data.defaultDueDay,
|
||||
defaultAmount: data.defaultAmount,
|
||||
periods: data.periods,
|
||||
};
|
||||
const result = await createPeriodicExpense(numericData);
|
||||
if (result.currentMonthApplicable) {
|
||||
setGenerateDialog({
|
||||
open: true,
|
||||
description: result.description,
|
||||
periodicExpenseId: result.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
async function handleConfirmGenerate() {
|
||||
await generateMonthlyExpense(generateDialog.periodicExpenseId);
|
||||
setGenerateDialog({ open: false, description: "", periodicExpenseId: 0 });
|
||||
}
|
||||
|
||||
function handleSkipGenerate() {
|
||||
setGenerateDialog({ open: false, description: "", periodicExpenseId: 0 });
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
if (confirm("¿Eliminar este gasto periódico?")) {
|
||||
await deletePeriodicExpense(id);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Administrá tus gastos recurrentes.
|
||||
</p>
|
||||
<Button onClick={openCreate} size="sm">
|
||||
<Plus className="size-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isLoadingPeriodic ? (
|
||||
<p className="text-sm text-muted-foreground">Cargando...</p>
|
||||
) : (
|
||||
<PeriodicExpensesTable
|
||||
data={periodicExpenses}
|
||||
onEdit={openEdit}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ResponsiveDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<ResponsiveDialogContent>
|
||||
<ResponsiveDialogHeader>
|
||||
<ResponsiveDialogTitle>
|
||||
{editingItem ? "Editar gasto periódico" : "Nuevo gasto periódico"}
|
||||
</ResponsiveDialogTitle>
|
||||
</ResponsiveDialogHeader>
|
||||
<PeriodicExpenseForm
|
||||
initialData={editingItem ?? undefined}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={closeDialog}
|
||||
/>
|
||||
</ResponsiveDialogContent>
|
||||
</ResponsiveDialog>
|
||||
|
||||
<GenerateCurrentMonthDialog
|
||||
open={generateDialog.open}
|
||||
onOpenChange={(open) => setGenerateDialog((prev) => ({ ...prev, open }))}
|
||||
description={generateDialog.description}
|
||||
onConfirm={handleConfirmGenerate}
|
||||
onSkip={handleSkipGenerate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user