added resolver for add expense

This commit is contained in:
florian.kaulfersch 2023-04-05 14:25:40 +02:00
parent e97a3b68cf
commit 3e82564694
8 changed files with 74 additions and 30 deletions

View file

@ -29,6 +29,7 @@ export const AddBudgetForm = () => {
variant="standard"
type="text"
label="Budget name"
required
{...register('name', { required: true })}
error={!!errors.name}
/>
@ -40,7 +41,8 @@ export const AddBudgetForm = () => {
variant="standard"
type="number"
label="Limit"
{...register('limit', { min: 0, required: true })}
required
{...register('limit', { min: 0, required: true, valueAsNumber: true })}
error={!!errors.limit}
/>
</Grid>

View file

@ -4,19 +4,12 @@ import { z } from 'zod';
import { NewBudgetModel } from '../../models/budget';
const budgetFormSchema = z
.object({
const budgetFormSchema = z.object({
name: z.string(),
limit: z.string(),
limit: z.number(),
startDate: z.date().optional(),
endDate: z.date().optional(),
})
.transform<NewBudgetModel>((formValues) => ({
...formValues,
startDate: formValues.startDate,
endDate: formValues.endDate,
limit: parseInt(formValues.limit),
}));
});
export const resolver: Resolver<NewBudgetModel> = async (values: unknown) => {
const parseResult = budgetFormSchema.safeParse(values);

View file

@ -1,9 +1,10 @@
import { Button, Grid, TextField } from '@mui/material';
import { SubmitHandler, useForm } from 'react-hook-form';
import { DatePicker } from './DatePicker/DatePicker';
import { useAddExpense } from '../hooks/useAddExpense';
import { NewExpenseModel } from '../models/expense';
import { resolver } from './resolver';
import { useAddExpense } from '../../hooks/useAddExpense';
import { NewExpenseModel } from '../../models/expense';
import { DatePicker } from '../DatePicker/DatePicker';
type AddExpenseFormProps = {
id: string;
@ -14,9 +15,9 @@ export const AddExpenseForm = ({ id }: AddExpenseFormProps) => {
register,
handleSubmit,
reset,
formState: { errors },
formState: { errors, isDirty, isValid },
control,
} = useForm<NewExpenseModel>();
} = useForm<NewExpenseModel>({ resolver, defaultValues: { date: new Date() } });
const { mutate: addExpense, isLoading } = useAddExpense(id);
@ -38,6 +39,7 @@ export const AddExpenseForm = ({ id }: AddExpenseFormProps) => {
type="text"
label="Description"
{...register('description', { required: true })}
error={!!errors.description}
/>
</Grid>
<Grid item xs={12}>
@ -57,7 +59,7 @@ export const AddExpenseForm = ({ id }: AddExpenseFormProps) => {
type="submit"
size="large"
variant="contained"
disabled={isLoading || Object.keys(errors).length > 0}
disabled={isLoading || Object.keys(errors).length > 0 || !isDirty || !isValid}
>
Add Expense
</Button>

View file

@ -0,0 +1,25 @@
import { Resolver } from 'react-hook-form';
import { z } from 'zod';
import { NewExpenseModel } from '../../models/expense';
const expenseFormSchema = z.object({
description: z.string(),
amount: z.number(),
date: z.date(),
});
export const resolver: Resolver<NewExpenseModel> = async (values: unknown) => {
const parseResult = expenseFormSchema.safeParse(values);
if (!parseResult.success) {
return {
values: parseResult.error,
errors: { root: { message: parseResult.error.message } },
};
} else {
return {
values: parseResult.data,
errors: {},
};
}
};

View file

@ -1,15 +1,17 @@
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons/faArrowLeft';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { List, ListItem, ListItemText } from '@mui/material';
import is from '@sindresorhus/is';
import Link from 'next/link';
import { FunctionComponent } from 'react';
import styles from './BudgetDetail.module.scss';
import { ExpenseDescription } from './ExpenseDescription';
import { datesToDayRange, dateToFormattedDay } from '../../helpers/date';
import { useBudgetSummaryQuery } from '../../hooks/useBudgetSummaryQuery';
import { Card } from '../_design/Card/Card';
import { DetailWithTitle } from '../_design/DetailWithTitle/DetailWithTitle';
import { AddExpenseForm } from '../AddExpenseForm';
import { AddExpenseForm } from '../AddExpenseForm/AddExpenseForm';
type BudgetDetailProps = {
id: string;
@ -45,13 +47,18 @@ export const BudgetDetail: FunctionComponent<BudgetDetailProps> = ({ id }) => {
</Card>
<Card>
<h3>Expenses</h3>
<ul>
<List>
{expenses.map((expense) => (
<li key={expense.id}>
{dateToFormattedDay(expense.date)} - {expense.description}
</li>
<ListItem key={expense.id}>
<ListItemText
primary={
<ExpenseDescription amount={expense.amount} description={expense.description} />
}
secondary={dateToFormattedDay(expense.date)}
/>
</ListItem>
))}
</ul>
</List>
</Card>
</div>
<div className={styles.DetailsContainerRight}>

View file

@ -0,0 +1,4 @@
.Wrapper {
display: flex;
justify-content: space-between;
}

View file

@ -0,0 +1,11 @@
import styles from './ExpenseDescription.module.scss';
type ExpenseDescriptionProps = { amount: number; description: string };
export const ExpenseDescription = ({ amount, description }: ExpenseDescriptionProps) => {
return (
<div className={styles.Wrapper}>
<div>{description}</div>
<div>{amount}</div>
</div>
);
};

View file

@ -17,7 +17,7 @@ type BudgetItemProps = { budget: BudgetModel };
export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
const { id, spent, name, limit, startDate, endDate } = budget;
const remainingBudget = 100 - Math.trunc((100 * spent) / limit);
const spentBudget = Math.trunc((100 * spent) / limit);
const isValidDateRange = is.date(startDate) && is.date(endDate) && endDate > startDate;
@ -28,12 +28,12 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
{name} <FontAwesomeIcon icon={faCoins} />
</h3>
<DetailWithTitle title={'Remaining Budget'}>
<ProgressBar percentage={remainingBudget} />
<DetailWithTitle title="Spent Budget">
<ProgressBar percentage={spentBudget} />
</DetailWithTitle>
{isValidDateRange && (
<DetailWithTitle title={'Period'}>
<DetailWithTitle title="Period">
<p>{datesToDayRange(startDate, endDate)}</p>
</DetailWithTitle>
)}