Skip to content

Commit

Permalink
feat: categories (#9)
Browse files Browse the repository at this point in the history
* refactor: menu items

* feat: categories design

* feat: add & remove categories

* feat: implemented categories

* refactor: products to items

* feat: watch category name

* refactor: item placeholders

* feat: updated pdf
  • Loading branch information
StereoPT authored Jun 26, 2024
1 parent d77d273 commit f59a449
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 113 deletions.
31 changes: 7 additions & 24 deletions src/components/FormInput/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Input } from 'react-daisyui';
import { FieldErrors, FieldValues, useFormContext } from 'react-hook-form';
import { useFormContext } from 'react-hook-form';

import { cn } from '@/utils/cn';

Expand All @@ -11,36 +11,21 @@ type FormInputProps = {
size?: 'xs' | 'sm' | 'md' | 'lg';
};

const getErrors = (name: string, errors: FieldErrors<FieldValues>) => {
if (name.includes('.')) {
const [array, index, field] = name.split('.');
// TODO: Check this later
// @ts-ignore
return errors?.[array]?.[index]?.[field];
} else {
return errors?.[name];
}
};

const FormInput = ({
name,
label,
placeholder,
required = false,
size = 'md',
}: FormInputProps) => {
const {
register,
formState: { errors },
} = useFormContext();

const inputError = getErrors(name, errors);
const { register, getFieldState, formState } = useFormContext();
const { error } = getFieldState(name, formState);

return (
<label htmlFor={name} className="form-control w-full">
{label && (
<div className="label">
<span className={cn('label-text', inputError && 'text-error')}>
<span className={cn('label-text', error && 'text-error')}>
{label}
{required && <span className="ml-1 text-red-700">*</span>}
</span>
Expand All @@ -50,14 +35,12 @@ const FormInput = ({
id={name}
placeholder={placeholder}
size={size}
color={inputError ? 'error' : 'neutral'}
color={error ? 'error' : 'neutral'}
{...register(name)}
/>
{inputError && (
{error && (
<div className="label">
<span className="label-text-alt text-error">
{inputError?.message}
</span>
<span className="label-text-alt text-error">{error?.message}</span>
</div>
)}
</label>
Expand Down
81 changes: 20 additions & 61 deletions src/components/MenuBuilder/MenuBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,50 @@
import {
FormProvider,
SubmitHandler,
useFieldArray,
useForm,
} from 'react-hook-form';
import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { MenuFormInputs, menuFormSchema } from '@/schemas/menuForm';

import { useSetAtom } from 'jotai';
import { menuAtom } from '@/store/menu.atom';

import MenuHeader from './MenuHeader/MenuHeader';
import MenuItem from './MenuItem/MenuItem';

import { menuAtom } from '@/store/menu.atom';

import { MenuFormInputs, menuFormSchema } from '@/schemas/menuForm';
import { Button, Card } from 'react-daisyui';
import { FiPlus } from 'react-icons/fi';

import { useModal } from '@/hooks/useModal';
import MenuModal from './MenuModal/MenuModal';
import MenuCategories from './MenuCategories/MenuCategories';

const MenuBuilder = () => {
const setMenuAtom = useSetAtom(menuAtom);

const {
modal: menuModal,
openModal,
closeModal,
} = useModal({
children: <MenuModal closeModal={() => closeModal()} />,
});

const methods = useForm<MenuFormInputs>({
resolver: yupResolver(menuFormSchema),
defaultValues: {
products: [{}],
categories: [{ items: [{}] }],
},
});

const { handleSubmit, reset, control } = methods;
const { handleSubmit, reset } = methods;

const { fields, append, move, remove } = useFieldArray({
control,
name: 'products',
const {
modal: menuModal,
openModal,
closeModal,
} = useModal({
children: <MenuModal closeModal={() => closeModal()} />,
});

const addNewItem = () => {
append({
name: '',
description: '',
price: 0,
});
};

const resetForm = () => {
reset();
};

const onFormSubmit: SubmitHandler<MenuFormInputs> = (values) => {
setMenuAtom(values);
openModal();
};

const onFormReset = () => {
reset();
};

return (
<>
<Card className="border border-base-200 border-opacity-20 bg-base-100 shadow-lg w-[560px]">
<Card className="border border-base-200 border-opacity-20 bg-base-100 shadow-lg w-[600px]">
<Card.Body>
<h2 className="card-title">Create Menu</h2>
<p className="mb-4">Fill the form to create your personal menu.</p>
Expand All @@ -73,31 +54,9 @@ const MenuBuilder = () => {
onSubmit={handleSubmit(onFormSubmit)}>
<div className="flex flex-col gap-8 w-full">
<MenuHeader />
<div className="flex flex-col gap-2">
<div className="flex justify-end px-2">
<Button
type="button"
size="sm"
shape="square"
color="primary"
onClick={addNewItem}>
<FiPlus size={20} />
</Button>
</div>
{fields.map((field, index) => {
return (
<MenuItem
key={field.id}
itemIndex={index}
itemAmount={fields.length}
removeItem={remove}
moveItem={move}
/>
);
})}
</div>
<MenuCategories />
<div className="card-actions justify-end">
<Button type="button" color="neutral" onClick={resetForm}>
<Button type="button" color="neutral" onClick={onFormReset}>
Reset
</Button>
<Button type="submit" color="primary">
Expand Down
50 changes: 50 additions & 0 deletions src/components/MenuBuilder/MenuCategories/MenuCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useFieldArray, useFormContext } from 'react-hook-form';

import { Button, Join } from 'react-daisyui';

import { FiPlus } from 'react-icons/fi';
import MenuCategory from './MenuCategory';

const MenuCategories = () => {
const { control } = useFormContext();
const { fields, append, remove } = useFieldArray({
control,
name: 'categories',
});

const addNewCategory = () => {
append({
name: '',
items: [{}],
});
};

return (
<div className="flex flex-col gap-2">
<div className="flex justify-end">
<Button
type="button"
size="sm"
color="primary"
startIcon={<FiPlus size={20} />}
onClick={addNewCategory}>
Add Category
</Button>
</div>
<Join className="w-full" vertical={true}>
{fields.map((field, index) => {
return (
<MenuCategory
key={field.id}
categoryIndex={index}
categoryAmount={fields.length}
removeCategory={remove}
/>
);
})}
</Join>
</div>
);
};

export default MenuCategories;
53 changes: 53 additions & 0 deletions src/components/MenuBuilder/MenuCategories/MenuCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { UseFieldArrayRemove, useWatch } from 'react-hook-form';
import { Accordion, Button } from 'react-daisyui';

import MenuItems from '../MenuItems/MenuItems';
import FormInput from '@/components/FormInput/FormInput';

import { FiTrash2 } from 'react-icons/fi';

type MenuCategoryProps = {
categoryIndex: number;
categoryAmount: number;
removeCategory: UseFieldArrayRemove;
};

const MenuCategory = ({
categoryIndex,
categoryAmount,
removeCategory,
}: MenuCategoryProps) => {
const categoryName = useWatch({ name: `categories.${categoryIndex}.name` });

return (
<Accordion
className="border border-base-500 join-item"
icon="arrow"
defaultChecked>
<Accordion.Title className="text-lg font-medium">
{categoryName || 'Category Name'}
</Accordion.Title>
<Accordion.Content className="flex flex-col gap-8">
<div className="flex justify-between gap-2">
<FormInput
name={`categories.${categoryIndex}.name`}
placeholder="Category Name"
size="sm"
/>
<Button
type="button"
size="sm"
shape="square"
color="error"
disabled={categoryAmount <= 1}
onClick={() => removeCategory(categoryIndex)}>
<FiTrash2 size={20} />
</Button>
</div>
<MenuItems category={`categories.${categoryIndex}`} />
</Accordion.Content>
</Accordion>
);
};

export default MenuCategory;
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { UseFieldArrayMove, UseFieldArrayRemove } from 'react-hook-form';
import { FiArrowDown, FiArrowUp, FiTrash2 } from 'react-icons/fi';

type MenuItemProps = {
category: string;
itemIndex: number;
itemAmount: number;
removeItem: UseFieldArrayRemove;
moveItem: UseFieldArrayMove;
};

const MenuItem = ({
category,
itemIndex,
itemAmount,
removeItem,
Expand Down Expand Up @@ -41,22 +43,22 @@ const MenuItem = ({
<div className="flex flex-col gap-2 basis-2/3">
<div className="w-full">
<FormInput
name={`products.${itemIndex}.name`}
placeholder="Product Name"
name={`${category}.items.${itemIndex}.name`}
placeholder="Item Name"
size="sm"
/>
</div>
<div className="w-full">
<FormInput
name={`products.${itemIndex}.description`}
placeholder="Product Description"
name={`${category}.items.${itemIndex}.description`}
placeholder="Item Description"
size="sm"
/>
</div>
</div>
<div className="basis-1/3">
<FormInput
name={`products.${itemIndex}.price`}
name={`${category}.items.${itemIndex}.price`}
placeholder="Price"
size="sm"
/>
Expand Down
55 changes: 55 additions & 0 deletions src/components/MenuBuilder/MenuItems/MenuItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Button } from 'react-daisyui';
import { FiPlus } from 'react-icons/fi';

import { useFieldArray, useFormContext } from 'react-hook-form';

import MenuItem from './MenuItem';

type MenuItemsProps = {
category: string;
};

const MenuItems = ({ category }: MenuItemsProps) => {
const { control } = useFormContext();
const { fields, append, move, remove } = useFieldArray({
control,
name: `${category}.items`,
});

const addNewItem = () => {
append({
name: '',
description: '',
price: 0,
});
};

return (
<div className="flex flex-col gap-2">
<div className="flex justify-end">
<Button
type="button"
size="sm"
color="primary"
startIcon={<FiPlus size={20} />}
onClick={addNewItem}>
Add Item
</Button>
</div>
{fields.map((field, index) => {
return (
<MenuItem
key={field.id}
category={category}
itemIndex={index}
itemAmount={fields.length}
removeItem={remove}
moveItem={move}
/>
);
})}
</div>
);
};

export default MenuItems;
Loading

0 comments on commit f59a449

Please sign in to comment.