diff options
Diffstat (limited to 'src/components/expense-form.tsx')
| -rw-r--r-- | src/components/expense-form.tsx | 388 |
1 files changed, 193 insertions, 195 deletions
diff --git a/src/components/expense-form.tsx b/src/components/expense-form.tsx index 0f677e6..bdee9d0 100644 --- a/src/components/expense-form.tsx +++ b/src/components/expense-form.tsx @@ -1,12 +1,14 @@ 'use client' -import { - createExpenseAction, - deleteExpenseAction, - updateExpenseAction, -} from '@/app/groups/[groupId]/expenses/actions' import { AsyncButton } from '@/components/async-button' import { SubmitButton } from '@/components/submit-button' import { Button } from '@/components/ui/button' +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card' import { Checkbox } from '@/components/ui/checkbox' import { Form, @@ -28,15 +30,17 @@ import { import { getExpense, getGroup } from '@/lib/api' import { ExpenseFormValues, expenseFormSchema } from '@/lib/schemas' import { zodResolver } from '@hookform/resolvers/zod' -import { useRouter, useSearchParams } from 'next/navigation' +import { useSearchParams } from 'next/navigation' import { useForm } from 'react-hook-form' export type Props = { group: NonNullable<Awaited<ReturnType<typeof getGroup>>> expense?: NonNullable<Awaited<ReturnType<typeof getExpense>>> + onSubmit: (values: ExpenseFormValues) => Promise<void> + onDelete?: () => Promise<void> } -export function ExpenseForm({ group, expense }: Props) { +export function ExpenseForm({ group, expense, onSubmit, onDelete }: Props) { const isCreate = expense === undefined const searchParams = useSearchParams() const form = useForm<ExpenseFormValues>({ @@ -61,210 +65,204 @@ export function ExpenseForm({ group, expense }: Props) { } : { title: '', amount: 0, paidFor: [], isReimbursement: false }, }) - const router = useRouter() return ( <Form {...form}> - <form - onSubmit={form.handleSubmit(async (values) => { - if (expense) { - await updateExpenseAction(group.id, expense.id, values) - } else { - await createExpenseAction(group.id, values) - } - router.back() - })} - > - <div className="grid grid-cols-1 sm:grid-cols-2 gap-6"> - <FormField - control={form.control} - name="title" - render={({ field }) => ( - <FormItem className="order-1"> - <FormLabel>Expense title</FormLabel> - <FormControl> - <Input - placeholder="Monday evening restaurant" - className="text-base" - {...field} - /> - </FormControl> - <FormDescription> - Enter a description for the expense. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> - - <FormField - control={form.control} - name="paidBy" - render={({ field }) => ( - <FormItem className="order-3 sm:order-2"> - <FormLabel>Paid by</FormLabel> - <Select - onValueChange={field.onChange} - defaultValue={field.value} - > - <SelectTrigger> - <SelectValue placeholder="Select a participant" /> - </SelectTrigger> - <SelectContent> - {group.participants.map(({ id, name }) => ( - <SelectItem key={id} value={id}> - {name} - </SelectItem> - ))} - </SelectContent> - </Select> - <FormDescription> - Select the participant who paid the expense. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> - - <FormField - control={form.control} - name="amount" - render={({ field }) => ( - <FormItem className="order-2 sm:order-3"> - <FormLabel>Amount</FormLabel> - <div className="flex items-baseline gap-2"> - <span>{group.currency}</span> + <form onSubmit={form.handleSubmit((values) => onSubmit(values))}> + <Card> + <CardHeader> + <CardTitle> + {isCreate ? <>Create expense</> : <>Edit expense</>} + </CardTitle> + </CardHeader> + <CardContent className="grid grid-cols-1 sm:grid-cols-2 gap-6"> + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem className="order-1"> + <FormLabel>Expense title</FormLabel> <FormControl> <Input - className="text-base max-w-[120px]" - type="number" - inputMode="decimal" - step={0.01} - placeholder="0.00" + placeholder="Monday evening restaurant" + className="text-base" {...field} /> </FormControl> - </div> - <FormMessage /> - - <FormField - control={form.control} - name="isReimbursement" - render={({ field }) => ( - <FormItem className="flex flex-row gap-2 items-center space-y-0 pt-2"> - <FormControl> - <Checkbox - checked={field.value} - onCheckedChange={field.onChange} - /> - </FormControl> - <div> - <FormLabel>This is a reimbursement</FormLabel> - </div> - </FormItem> - )} - /> - </FormItem> - )} - /> + <FormDescription> + Enter a description for the expense. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> - <FormField - control={form.control} - name="paidFor" - render={() => ( - <FormItem className="order-5"> - <div className="mb-4"> - <FormLabel> - Paid for - <Button - variant="link" - type="button" - className="-m-2" - onClick={() => { - const paidFor = form.getValues().paidFor - const allSelected = - paidFor.length === group.participants.length - const newPairFor = allSelected - ? [] - : group.participants.map((p) => p.id) - form.setValue('paidFor', newPairFor, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }) - }} - > - {form.getValues().paidFor.length === - group.participants.length ? ( - <>Select none</> - ) : ( - <>Select all</> - )} - </Button> - </FormLabel> + <FormField + control={form.control} + name="paidBy" + render={({ field }) => ( + <FormItem className="order-3 sm:order-2"> + <FormLabel>Paid by</FormLabel> + <Select + onValueChange={field.onChange} + defaultValue={field.value} + > + <SelectTrigger> + <SelectValue placeholder="Select a participant" /> + </SelectTrigger> + <SelectContent> + {group.participants.map(({ id, name }) => ( + <SelectItem key={id} value={id}> + {name} + </SelectItem> + ))} + </SelectContent> + </Select> <FormDescription> - Select who the expense was paid for. + Select the participant who paid the expense. </FormDescription> - </div> - {group.participants.map(({ id, name }) => ( + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="amount" + render={({ field }) => ( + <FormItem className="order-2 sm:order-3"> + <FormLabel>Amount</FormLabel> + <div className="flex items-baseline gap-2"> + <span>{group.currency}</span> + <FormControl> + <Input + className="text-base max-w-[120px]" + type="number" + inputMode="decimal" + step={0.01} + placeholder="0.00" + {...field} + /> + </FormControl> + </div> + <FormMessage /> + <FormField - key={id} control={form.control} - name="paidFor" - render={({ field }) => { - return ( - <FormItem - key={id} - className="flex flex-row items-start space-x-3 space-y-0" - > - <FormControl> - <Checkbox - checked={field.value?.includes(id)} - onCheckedChange={(checked) => { - return checked - ? field.onChange([...field.value, id]) - : field.onChange( - field.value?.filter( - (value) => value !== id, - ), - ) - }} - /> - </FormControl> - <FormLabel className="text-sm font-normal"> - {name} - </FormLabel> - </FormItem> - ) - }} + name="isReimbursement" + render={({ field }) => ( + <FormItem className="flex flex-row gap-2 items-center space-y-0 pt-2"> + <FormControl> + <Checkbox + checked={field.value} + onCheckedChange={field.onChange} + /> + </FormControl> + <div> + <FormLabel>This is a reimbursement</FormLabel> + </div> + </FormItem> + )} /> - ))} - <FormMessage /> - </FormItem> - )} - /> - </div> + </FormItem> + )} + /> - <div className="mt-6 flex gap-2"> - <SubmitButton - loadingContent={isCreate ? <>Creating…</> : <>Saving…</>} - > - {isCreate ? <>Create</> : <>Save</>} - </SubmitButton> - {!isCreate && ( - <AsyncButton - type="button" - variant="destructive" - loadingContent="Deleting…" - action={async () => { - await deleteExpenseAction(group.id, expense.id) - router.back() - }} + <FormField + control={form.control} + name="paidFor" + render={() => ( + <FormItem className="order-5"> + <div className="mb-4"> + <FormLabel> + Paid for + <Button + variant="link" + type="button" + className="-m-2" + onClick={() => { + const paidFor = form.getValues().paidFor + const allSelected = + paidFor.length === group.participants.length + const newPairFor = allSelected + ? [] + : group.participants.map((p) => p.id) + form.setValue('paidFor', newPairFor, { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }) + }} + > + {form.getValues().paidFor.length === + group.participants.length ? ( + <>Select none</> + ) : ( + <>Select all</> + )} + </Button> + </FormLabel> + <FormDescription> + Select who the expense was paid for. + </FormDescription> + </div> + {group.participants.map(({ id, name }) => ( + <FormField + key={id} + control={form.control} + name="paidFor" + render={({ field }) => { + return ( + <FormItem + key={id} + className="flex flex-row items-start space-x-3 space-y-0" + > + <FormControl> + <Checkbox + checked={field.value?.includes(id)} + onCheckedChange={(checked) => { + return checked + ? field.onChange([...field.value, id]) + : field.onChange( + field.value?.filter( + (value) => value !== id, + ), + ) + }} + /> + </FormControl> + <FormLabel className="text-sm font-normal"> + {name} + </FormLabel> + </FormItem> + ) + }} + /> + ))} + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + + <CardFooter className="gap-2"> + <SubmitButton + loadingContent={isCreate ? <>Creating…</> : <>Saving…</>} > - Delete - </AsyncButton> - )} - </div> + {isCreate ? <>Create</> : <>Save</>} + </SubmitButton> + {!isCreate && onDelete && ( + <AsyncButton + type="button" + variant="destructive" + loadingContent="Deleting…" + action={onDelete} + > + Delete + </AsyncButton> + )} + </CardFooter> + </Card> </form> </Form> ) |
