Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/nowait-admin/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ body,
mask-image: linear-gradient(to right, black 80%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, black 80%, transparent 100%);
}

textarea {
resize: none;
}
44 changes: 42 additions & 2 deletions apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import NoticeEditor from "./components/NoticeEditor";
import editOrderIcon from "../../assets/edit_order_icon.svg";
import ToggleSwitch from "../AdminHome/components/ToggleSwitch";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import MenuModal from "./components/menuModal";

const BoothSection = ({
boothName,
Expand Down Expand Up @@ -162,6 +163,24 @@ const dummyMenus = [
const MenuSection = () => {
const [editMode, setEditMode] = useState(false);
const [menus, setMenus] = useState(dummyMenus);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [selectedMenu, setSelectedMenu] = useState<any>(null);

const openEditModal = (menu: any) => {
setSelectedMenu(menu);
setIsEditModalOpen(true);
};

const handleAddMenu = (newMenu: any) => {
setMenus((prev) => [...prev, newMenu]);
};

const handleEditMenu = (updated: any) => {
setMenus((prev) =>
prev.map((menu) => (menu.name === selectedMenu.name ? updated : menu))
);
};

const toggleSoldOut = (index: number) => {
const updatedMenus = [...menus];
Expand Down Expand Up @@ -189,7 +208,10 @@ const MenuSection = () => {
>
{editMode ? "편집 완료" : "순서 편집"}
</button>
<button className="text-sm px-4 py-2 bg-black-5 text-black-80 rounded-[8px]">
<button
className="text-sm px-4 py-2 bg-black-5 text-black-80 rounded-[8px]"
onClick={() => setIsAddModalOpen(true)}
>
메뉴 추가 +
</button>
</div>
Expand Down Expand Up @@ -226,7 +248,10 @@ const MenuSection = () => {
{...(editMode ? provided.dragHandleProps : {})}
>
<div className="flex items-center gap-4">
<div className="w-[48px] h-[48px] bg-black-5 rounded-md flex items-center justify-center overflow-hidden">
<div
className="w-[48px] h-[48px] bg-black-5 rounded-md flex items-center justify-center overflow-hidden"
onClick={() => !editMode && openEditModal(menu)}
>
<img
src={placeholderIcon}
className="w-6 h-6"
Expand Down Expand Up @@ -268,6 +293,21 @@ const MenuSection = () => {
</Droppable>
</DragDropContext>
</div>
{isAddModalOpen && (
<MenuModal
isEdit={false}
onClose={() => setIsAddModalOpen(false)}
onSubmit={handleAddMenu}
/>
)}
{isEditModalOpen && selectedMenu && (
<MenuModal
isEdit={true}
initialData={selectedMenu}
onClose={() => setIsEditModalOpen(false)}
onSubmit={handleEditMenu}
/>
)}
</div>
);
};
Expand Down
191 changes: 191 additions & 0 deletions apps/nowait-admin/src/pages/AdminBooth/components/menuModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { useState } from "react";
import placeholderIcon from "../../../assets/image_placeholder.svg";
import closeIcon from "../../../assets/close.svg";
interface MenuModalProps {
isEdit: boolean;
initialData?: {
name: string;
adminName: string;
price: string;
description: string;
isRepresentative: boolean;
image?: File;
};
onClose: () => void;
onSubmit: (data: any) => void;
}

const MenuModal = ({
isEdit,
initialData,
onClose,
onSubmit,
}: MenuModalProps) => {
const [name, setName] = useState(initialData?.name || "");
const [adminName, setAdminName] = useState(initialData?.adminName || "");
const [price, setPrice] = useState(initialData?.price || "");
const [description, setDescription] = useState(
initialData?.description || ""
);
const [isRepresentative, setIsRepresentative] = useState(
initialData?.isRepresentative || false
);
const [image, setImage] = useState<File | null>(initialData?.image || null);

const handleSubmit = () => {
onSubmit({ name, adminName, price, description, isRepresentative, image });
onClose();
};

return (
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
<div className="bg-white w-[500px] h-[754px] rounded-[20px] p-[30px] relative">
<div className="flex justify-between w-full">
<h2 className="text-title-20-bold mb-[30px]">
{isEdit ? "메뉴 편집하기" : "새 메뉴 추가하기"}
</h2>
<img
src={closeIcon}
alt="닫기"
className="w-5 h-5"
onClick={onClose}
/>
</div>

{/* 대표 메뉴 토글 */}
<div className="flex justify-between items-center mb-[30px]">
<span className="text-title-20-bold">대표 메뉴 설정</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={isRepresentative}
onChange={() => setIsRepresentative((prev) => !prev)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:bg-black transition-all"></div>
<div className="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition peer-checked:translate-x-5"></div>
</label>
</div>

{/* 메뉴명 */}
<div className="mb-[30px] flex gap-[20px]">
<div className="flex flex-col w-full ">
<label className="block text-sm font-medium mb-3">메뉴명</label>
<div className="relative w-full">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
maxLength={20}
className="w-full h-[52px] border border-[#DDDDDD] bg-black-5 bg-black-5 focus:bg-white px-4 py-2 border rounded-lg text-sm"
placeholder="메뉴명을 입력해주세요"
/>
<p
className={`absolute top-1/2 -translate-y-1/2 right-4 text-13-regular text-gray-400 `}
>
<span
className={`${
name.length > 0 ? "text-black" : "text-gray-400"
}`}
>
{name.length}
</span>{" "}
/ 20
</p>
{/* 이미지 업로드 */}
</div>
</div>
<label className="min-w-[86px] min-h-[86px] bg-black-5 border border-[#DDDDDD] rounded-lg flex items-center justify-center cursor-pointer overflow-hidden">
<input
type="file"
className="hidden"
onChange={(e) => e.target.files && setImage(e.target.files[0])}
/>
{image ? (
<img
src={URL.createObjectURL(image)}
alt="업로드된 이미지"
className="w-full h-full object-cover"
/>
) : (
<img src={placeholderIcon} className="w-6 h-6" alt="업로드" />
)}
</label>
</div>

{/* 관리자용 메뉴명 */}
<div className="mb-[30px] relative">
<label className="block text-sm font-medium mb-3">
관리자용 메뉴명
</label>
<div className="relative">
<input
type="text"
value={adminName}
onChange={(e) => setAdminName(e.target.value)}
maxLength={10}
className="w-full h-[52px] border border-[#DDDDDD] bg-black-5 bg-black-5 focus:bg-white px-4 py-2 border rounded-lg text-sm"
placeholder="주문 확인에 용이한 메뉴명으로 설정해주세요."
/>
<p className="absolute top-1/2 -translate-y-1/2 right-4 text-13-regular text-gray-400">
<span
className={` ${
adminName.length > 0 ? "text-black" : "text-gray-400"
}`}
>
{adminName.length}
</span>{" "}
/ 10
</p>
</div>
</div>

{/* 가격 */}
<div className="mb-[30px]">
<label className="block text-sm font-medium mb-3">가격</label>
<input
type="text"
value={price}
onChange={(e) => setPrice(e.target.value)}
className="w-full h-[52px] border border-[#DDDDDD] bg-black-5 bg-black-5 focus:bg-white px-4 py-2 border rounded-lg text-sm"
placeholder="가격을 입력해주세요"
/>
</div>

{/* 메뉴 소개 */}
<div className="mb-[30px] relative">
<label className="block text-sm font-medium mb-3">메뉴 소개</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
maxLength={250}
className="w-full border border-[#DDDDDD] bg-black-5 bg-black-5 focus:bg-white h-[120px] px-4 py-2 border rounded-lg text-sm h-24"
placeholder="메뉴 소개를 입력해주세요."
/>
<p className="absolute bottom-[12px] right-4 text-right text-xs text-gray-400">
<span
className={`${
description.length > 0 ? "text-black" : "text-gray-400"
}`}
>
{description.length}
</span>{" "}
/ 250
</p>
</div>

{/* 하단 버튼 */}
<div className="flex">
<button
onClick={handleSubmit}
className="w-full h-[48px] px-3 py-[10px] rounded-[10px] bg-black text-white text-sm"
>
{isEdit ? "저장하기" : "메뉴 추가하기"}
</button>
</div>
</div>
</div>
);
};

export default MenuModal;