133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { DateField } from "@/components/shared/DateField";
|
|
import { Field, inputClass } from "@/components/shared/Field";
|
|
import { Select } from "@/components/shared/Select";
|
|
import {
|
|
SimpleFormActions,
|
|
SimpleFormReview,
|
|
SimpleFormStepIndicator,
|
|
} from "@/components/shared/SimpleFormSteps";
|
|
import { TitleCombobox } from "@/components/shared/TitleCombobox";
|
|
import { formatItalianDate, todayIso } from "@/lib/date/formatDate";
|
|
import { incomeTypeLabel } from "@/lib/finance/labels";
|
|
import {
|
|
euroFormatter,
|
|
formatMoneyInput,
|
|
parseMoneyInput,
|
|
} from "@/lib/finance/money";
|
|
import type { Income, PaymentTitle } from "@/types/finance";
|
|
|
|
export function IncomeForm({
|
|
titles,
|
|
editing,
|
|
defaultDate = todayIso(),
|
|
onSubmit,
|
|
onCreateTitle,
|
|
onCancel,
|
|
saving = false,
|
|
}: {
|
|
titles: PaymentTitle[];
|
|
editing?: Income;
|
|
defaultDate?: string;
|
|
onSubmit: (data: object) => Promise<void>;
|
|
onCreateTitle?: (name: string) => Promise<void>;
|
|
onCancel?: () => void;
|
|
saving?: boolean;
|
|
}) {
|
|
const [review, setReview] = useState(false);
|
|
const [title, setTitle] = useState(editing?.title ?? "");
|
|
const [amount, setAmount] = useState(formatMoneyInput(editing?.amount));
|
|
const [type, setType] = useState(editing?.type ?? "salary");
|
|
const [date, setDate] = useState(editing?.date ?? defaultDate);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
setReview(false);
|
|
setTitle(editing?.title ?? "");
|
|
setAmount(formatMoneyInput(editing?.amount));
|
|
setType(editing?.type ?? "salary");
|
|
setDate(editing?.date ?? defaultDate);
|
|
setError("");
|
|
}, [defaultDate, editing]);
|
|
|
|
function payload() {
|
|
if (!title.trim()) throw new Error("Inserisci il titolo dell'entrata");
|
|
if (!date) throw new Error("Inserisci la data dell'entrata");
|
|
return { title, amount: parseMoneyInput(amount), type, date };
|
|
}
|
|
|
|
return (
|
|
<form
|
|
onSubmit={async (event) => {
|
|
event.preventDefault();
|
|
try {
|
|
setError("");
|
|
const data = payload();
|
|
if (!review) {
|
|
setReview(true);
|
|
return;
|
|
}
|
|
await onSubmit(data);
|
|
} catch (err) {
|
|
setError(
|
|
err instanceof Error ? err.message : "Salvataggio non riuscito",
|
|
);
|
|
}
|
|
}}
|
|
>
|
|
<SimpleFormStepIndicator review={review} />
|
|
{review ? (
|
|
<SimpleFormReview
|
|
rows={[
|
|
{
|
|
label: "Importo",
|
|
value: euroFormatter.format(parseMoneyInput(amount)),
|
|
},
|
|
{ label: "Tipo", value: incomeTypeLabel(type) },
|
|
{ label: "Data", value: formatItalianDate(date) },
|
|
]}
|
|
title={title}
|
|
/>
|
|
) : (
|
|
<div className="grid gap-3 md:grid-cols-4">
|
|
<TitleCombobox
|
|
titles={titles}
|
|
type="income"
|
|
value={title}
|
|
onChange={setTitle}
|
|
onCreate={onCreateTitle}
|
|
/>
|
|
<Field label="Importo">
|
|
<input
|
|
className={inputClass}
|
|
value={amount}
|
|
onChange={(event) => setAmount(event.target.value)}
|
|
/>
|
|
</Field>
|
|
<Select
|
|
label="Tipo"
|
|
value={type}
|
|
onChange={(value) => setType(value as typeof type)}
|
|
options={[
|
|
{ value: "salary", label: "Stipendio" },
|
|
{ value: "recurring", label: "Ricorrente" },
|
|
{ value: "one_time", label: "Una tantum" },
|
|
{ value: "other", label: "Altro" },
|
|
]}
|
|
/>
|
|
<DateField label="Data" name="date" value={date} onChange={setDate} />
|
|
</div>
|
|
)}
|
|
{error ? <p className="mt-4 text-sm text-rose-300">{error}</p> : null}
|
|
<SimpleFormActions
|
|
onBack={() => setReview(false)}
|
|
onCancel={onCancel}
|
|
review={review}
|
|
saving={saving}
|
|
/>
|
|
</form>
|
|
);
|
|
}
|