Skip to content

Commit

Permalink
feat: food combo backend integration
Browse files Browse the repository at this point in the history
  • Loading branch information
iamtrazy committed Jul 25, 2024
1 parent 8ae5362 commit c2ab903
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 65 deletions.
21 changes: 20 additions & 1 deletion src/api/useCombos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,24 @@ export const useCombos = () => {
}
};

return { getAllCombos, addCombo, getComboById };
const updateCombo = async (id: string, data: FoodCombo) => {
try {
const response = await fetch(`${API_URL}/foodCombo/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${getToken()}`,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Failed to update combo');
}
} catch (error: any) {
console.error(error);
throw new Error(error);
}
};

return { getAllCombos, addCombo, getComboById, updateCombo };
};
125 changes: 74 additions & 51 deletions src/components/BranchManager/Forms/DynamicForm/EditFoodCombo.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { FC, useEffect, useState } from 'react';
import { z } from 'zod';
import { useForm, SubmitHandler, useFieldArray } from 'react-hook-form';
import { useForm, SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import ImageInput from '@components/BranchManager/Inputs/ImageInput';
import { Button } from '@nextui-org/react';
import MultiSelect from '@components/BranchManager/Forms/MultiCheckBox';
import { Combo } from '@/types/mock_combo';
import { toast } from 'react-toastify';
import MultiSelect from '@components/BranchManager/Forms/MultiSelectSearch';
import { FoodCombo } from '@/types/combo';
import { useNavigate } from 'react-router-dom';
import { useUpload } from '@/api/useUpload';
import { useCombos } from '@/api/useCombos';
import { useFoods } from '@/api/useFoods';
import { Food } from '@/types/food';
import Combos from '@/pages/BranchManager/FoodCombos';

type ComboPicker = {
key: string;
label: string;
};

const FormSchema = z.object({
name: z.string().min(1, { message: 'Item name is required' }),
Expand All @@ -25,8 +34,16 @@ const FormSchema = z.object({

type FormSchemaType = z.infer<typeof FormSchema>;

function ComboEditForm({ id }: { id: string | undefined }) {
function ComboEditForm({ id = '' }: { id?: string }) {
const Navigate = useNavigate();
const { updateCombo } = useCombos();
const { getComboById } = useCombos();
const { uploadImage } = useUpload();
const { getAllFoods } = useFoods();

const [foods, setFoods] = useState<ComboPicker[]>([]);
const [item, setItem] = useState<FoodCombo | null>(null);

const { register, handleSubmit, formState, setValue } =
useForm<FormSchemaType>({
resolver: zodResolver(FormSchema),
Expand All @@ -42,40 +59,62 @@ function ComboEditForm({ id }: { id: string | undefined }) {
}
}, [errors]);

const getCombo = async () => {
try {
const data: FoodCombo = await getComboById(id);
if (data) {
setItem(data);
setValue('name', data.name);
setValue('description', data.description || '');
setValue('price', data.price);
setValue('image', data.image);
}
} catch (error: any) {
console.error(error);
}
};

useEffect(() => {
getCombo();
}, [id]);

const getFoods = async () => {
try {
const foods = await getAllFoods();
if (foods) {
const foodOptions = foods.map((food: Food) => ({
key: food.id,
label: food.name,
}));
setFoods(foodOptions);
}
} catch (error) {
console.error('Error getting categories:', error);
}
};

useEffect(() => {
getFoods();
}, []);

const onSubmit: SubmitHandler<FormSchemaType> = async (data) => {
let imageUrl = data.image;

if (imageFile) {
const formData = new FormData();
formData.append('file', imageFile);

try {
const uploadUrl = (import.meta as any).env.VITE_UPLOAD_URL;
const response = await fetch(`${uploadUrl}`, {
method: 'POST',
body: formData,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});

if (response.ok) {
const result = await response.json();
imageUrl = result.fileUrl;
const respose = await uploadImage(imageFile);
if (respose) {
imageUrl = respose.fileUrl;
setValue('image', imageUrl);
} else {
console.error('Image upload failed');
return;
throw new Error('Failed to upload image');
}
} catch (error) {
console.error('Error uploading image:', error);
return;
}
}

const transformedData = {
const transformedData: FoodCombo = {
cafeId: 'cafe 1',
available: 0,
deleted: 0,
Expand All @@ -85,34 +124,16 @@ function ComboEditForm({ id }: { id: string | undefined }) {
};

try {
const apiUrl = (import.meta as any).env.VITE_API_URL;
const response = await fetch(`${apiUrl}/foodCombo`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(transformedData),
});

if (response.ok) {
toast('Food combo added successfully', { type: 'success' });
Navigate('/branch-manager/food-combos');
} else {
toast('Failed to add food item', { type: 'error' });
console.error('Failed to add food combo', response.statusText);
}
updateCombo(id, transformedData);
Navigate('/branch-manager/food-combos');
} catch (error) {
console.error('Error adding food combo', error);
toast('Failed to add food combo', { type: 'error' });
console.error('Error adding food combo:', error);
}
};

const [foodIds] = useState<Combo[]>([
{ key: 'Pizza', label: 'Pizza' },
{ key: 'Milk-Shake', label: 'Milk-Shake' },
{ key: 'Other', label: 'Other' },
]);

if (!item || !foods.length) {
return <div>Loading...</div>;
}
return (
<div className="flex flex-col gap-4">
<div className="rounded-lg border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-[#000000]">
Expand Down Expand Up @@ -170,7 +191,7 @@ function ComboEditForm({ id }: { id: string | undefined }) {
<label className="mb-3 block text-black dark:text-white">
<span className="block mb-1 text-gray-600">Food items</span>
<MultiSelect
categories={foodIds}
categories={foods}
register={register}
fieldname="foodIds"
setValue={setValue}
Expand All @@ -186,6 +207,8 @@ function ComboEditForm({ id }: { id: string | undefined }) {
fieldname="image"
register={register}
setImageFile={setImageFile}
urlPreview={item.image}
height={'h-150'}
/>
</label>
<div className="flex justify-center gap-12 mt-16">
Expand Down
13 changes: 11 additions & 2 deletions src/components/BranchManager/Forms/DynamicForm/EditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { Food } from '@/types/food';
import { useCategories } from '@/api/useCategories';
import { Category } from '@/types/category';

type CategoryPicker = {
key: string;
label: string;
};

const FormSchema = z.object({
name: z.string().min(1, { message: 'Item name is required' }),
category: z.array(z.string()).min(1, { message: 'Category is required' }),
Expand Down Expand Up @@ -51,7 +56,7 @@ function EditForm({ id = '' }: { id?: string }) {
const { getFoodById } = useFoods();
const { uploadImage } = useUpload();
const { getAllCategories } = useCategories();
const [categories, setCategories] = useState<Category[]>([]);
const [categories, setCategories] = useState<CategoryPicker[]>([]);
const [item, setItem] = useState<Food | null>(null);

const { register, control, handleSubmit, formState, setValue } =
Expand Down Expand Up @@ -108,7 +113,11 @@ function EditForm({ id = '' }: { id?: string }) {
try {
const categories = await getAllCategories();
if (categories) {
setCategories(categories);
const categoryOptions = categories.map((category: Category) => ({
key: category.id,
label: category.name,
}));
setCategories(categoryOptions);
}
} catch (error) {
console.error('Error getting categories:', error);
Expand Down
34 changes: 23 additions & 11 deletions src/components/BranchManager/Forms/MultiCheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { FC, useState, useEffect } from 'react';
import { Select, SelectItem, Chip, SelectedItems } from '@nextui-org/react';
import { Category } from '@/types/category';
import { UseFormRegister, UseFormSetValue } from 'react-hook-form';

type ComboPicker = {
key: string;
label: string;
};

interface AppProps {
categories: Category[];
categories: ComboPicker[];
fieldname: string;
register: UseFormRegister<any>;
setValue: UseFormSetValue<any>;
Expand All @@ -26,11 +30,15 @@ const getRandomColor = () => {
const App: FC<AppProps> = ({ categories, register, fieldname, setValue }) => {
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
const [chipColors, setChipColors] = useState<{ [key: string]: string }>({});
let value = Array.from(selectedKeys);
const [selectedLabels, setSelectedLabels] = useState<string[]>([]);

useEffect(() => {
setValue(fieldname, Array.from(selectedKeys));
}, [selectedKeys, setValue, fieldname]);
const labels = Array.from(selectedKeys).map(
(key) => categories.find((category) => category.key === key)?.label || '',
);
setSelectedLabels(labels);
setValue(fieldname, labels);
}, [selectedKeys, setValue, fieldname, categories]);

const handleSelectionChange = (keys: Set<string> | any) => {
const newKeys = keys instanceof Set ? keys : new Set(keys);
Expand Down Expand Up @@ -63,12 +71,12 @@ const App: FC<AppProps> = ({ categories, register, fieldname, setValue }) => {
base: 'max-w-full',
trigger: 'min-h-12 py-2',
}}
renderValue={(items: SelectedItems<Category>) => (
renderValue={(items: SelectedItems<ComboPicker>) => (
<div className="flex flex-wrap gap-2">
{items.map((item) => (
<Chip
key={item.key}
className={`${chipColors[item.key]} bg-opacity-25`}
className={`${chipColors[String(item.key)]} bg-opacity-25`}
>
{item.textValue}
</Chip>
Expand All @@ -81,14 +89,18 @@ const App: FC<AppProps> = ({ categories, register, fieldname, setValue }) => {
{(category) => (
<SelectItem
className="dark:bg-[#000000] bg-white rounded-sm border border-solid w-full dark:text-white text-[#000000]"
key={category.id}
value={category.name}
key={category.key}
value={category.key}
>
{category.name}
{category.label}
</SelectItem>
)}
</Select>
<input type="hidden" value={value} {...register(fieldname)} />
<input
type="hidden"
value={selectedLabels.join(',')}
{...register(fieldname)}
/>
</div>
</div>
</div>
Expand Down

0 comments on commit c2ab903

Please sign in to comment.