diff --git a/src/components/AddBudgetForm/AddBudgetForm.tsx b/src/components/AddBudgetForm/AddBudgetForm.tsx index 18204bc..dea8a96 100644 --- a/src/components/AddBudgetForm/AddBudgetForm.tsx +++ b/src/components/AddBudgetForm/AddBudgetForm.tsx @@ -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} /> diff --git a/src/components/AddBudgetForm/resolver.ts b/src/components/AddBudgetForm/resolver.ts index db7e2a5..cdee7aa 100644 --- a/src/components/AddBudgetForm/resolver.ts +++ b/src/components/AddBudgetForm/resolver.ts @@ -4,19 +4,12 @@ import { z } from 'zod'; import { NewBudgetModel } from '../../models/budget'; -const budgetFormSchema = z - .object({ - name: z.string(), - limit: z.string(), - startDate: z.date().optional(), - endDate: z.date().optional(), - }) - .transform((formValues) => ({ - ...formValues, - startDate: formValues.startDate, - endDate: formValues.endDate, - limit: parseInt(formValues.limit), - })); +const budgetFormSchema = z.object({ + name: z.string(), + limit: z.number(), + startDate: z.date().optional(), + endDate: z.date().optional(), +}); export const resolver: Resolver = async (values: unknown) => { const parseResult = budgetFormSchema.safeParse(values); diff --git a/src/components/AddExpenseForm.tsx b/src/components/AddExpenseForm/AddExpenseForm.tsx similarity index 82% rename from src/components/AddExpenseForm.tsx rename to src/components/AddExpenseForm/AddExpenseForm.tsx index b170878..d633e58 100644 --- a/src/components/AddExpenseForm.tsx +++ b/src/components/AddExpenseForm/AddExpenseForm.tsx @@ -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(); + } = useForm({ 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} /> @@ -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 diff --git a/src/components/AddExpenseForm/resolver.ts b/src/components/AddExpenseForm/resolver.ts new file mode 100644 index 0000000..ea14d08 --- /dev/null +++ b/src/components/AddExpenseForm/resolver.ts @@ -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 = 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: {}, + }; + } +}; diff --git a/src/components/BudgetDetail/BudgetDetail.tsx b/src/components/BudgetDetail/BudgetDetail.tsx index 6cf38b1..070fb19 100644 --- a/src/components/BudgetDetail/BudgetDetail.tsx +++ b/src/components/BudgetDetail/BudgetDetail.tsx @@ -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 = ({ id }) => {

Expenses

-
    + {expenses.map((expense) => ( -
  • - {dateToFormattedDay(expense.date)} - {expense.description} -
  • + + + } + secondary={dateToFormattedDay(expense.date)} + /> + ))} -
+
diff --git a/src/components/BudgetDetail/ExpenseDescription.module.scss b/src/components/BudgetDetail/ExpenseDescription.module.scss new file mode 100644 index 0000000..3efe9fb --- /dev/null +++ b/src/components/BudgetDetail/ExpenseDescription.module.scss @@ -0,0 +1,4 @@ +.Wrapper { + display: flex; + justify-content: space-between; +} diff --git a/src/components/BudgetDetail/ExpenseDescription.tsx b/src/components/BudgetDetail/ExpenseDescription.tsx new file mode 100644 index 0000000..cd66207 --- /dev/null +++ b/src/components/BudgetDetail/ExpenseDescription.tsx @@ -0,0 +1,11 @@ +import styles from './ExpenseDescription.module.scss'; + +type ExpenseDescriptionProps = { amount: number; description: string }; +export const ExpenseDescription = ({ amount, description }: ExpenseDescriptionProps) => { + return ( +
+
{description}
+
{amount}€
+
+ ); +}; diff --git a/src/components/BudgetItem/BudgetItem.tsx b/src/components/BudgetItem/BudgetItem.tsx index 1bd66ec..8171e7b 100644 --- a/src/components/BudgetItem/BudgetItem.tsx +++ b/src/components/BudgetItem/BudgetItem.tsx @@ -17,7 +17,7 @@ type BudgetItemProps = { budget: BudgetModel }; export const BudgetItem: FC = ({ 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 = ({ budget }) => { {name} - - + + {isValidDateRange && ( - +

{datesToDayRange(startDate, endDate)}

)}