@@ -101,9 +147,13 @@ const NavItem = ({ to, icon, label, compact }: NavItemProps) => {
to={to}
end
className={({ isActive }) =>
- `flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold justify-center ${
+ `flex items-center rounded-lg text-sm font-semibold ${
isActive ? "bg-gray-100 text-black" : "text-gray-400"
- } ${compact ? "justify-center" : "justify-start"}`
+ } ${
+ compact
+ ? "justify-center w-10 h-10"
+ : "justify-start px-4 py-2 gap-[8px]"
+ }`
}
>
{icon}
diff --git a/apps/nowait-admin/src/components/MobileAdminNav.tsx b/apps/nowait-admin/src/components/MobileAdminNav.tsx
index 50effa91..3c761d86 100644
--- a/apps/nowait-admin/src/components/MobileAdminNav.tsx
+++ b/apps/nowait-admin/src/components/MobileAdminNav.tsx
@@ -1,16 +1,35 @@
import waitIcon from "../assets/Waiting.svg"; // 대기 아이콘 등
+import waitIconActive from "../assets/waitIconActive.svg";
import orderIcon from "../assets/Order.svg";
+import orderIconActive from "../assets/orderIconActive.svg";
import statIcon from "../assets/Statistics.svg";
+import statIconActive from "../assets/statIconActive.svg";
import boothIcon from "../assets/Tent.svg";
+import boothIconActive from "../assets/boothIconActive.svg";
import profileImg from "../assets/profile.png"; // 사용자 이미지
import cancelIcon from "../assets/Cancel.svg";
import { useLocation, useNavigate } from "react-router";
const menuItems = [
- { label: "대기", icon: waitIcon, path: "/admin" },
- { label: "주문", icon: orderIcon, path: "/admin/orders" },
- { label: "통계", icon: statIcon, path: "/admin/analytics" },
- { label: "부스 관리", icon: boothIcon, path: "/admin/booth" },
+ { label: "대기", icon: waitIcon, activeIcon: waitIconActive, path: "/admin" },
+ {
+ label: "주문",
+ icon: orderIcon,
+ activeIcon: orderIconActive,
+ path: "/admin/orders",
+ },
+ {
+ label: "통계",
+ icon: statIcon,
+ activeIcon: statIconActive,
+ path: "/admin/analytics",
+ },
+ {
+ label: "부스 관리",
+ icon: boothIcon,
+ activeIcon: boothIconActive,
+ path: "/admin/booth",
+ },
];
const MobileAdminNav = ({ onClose }: { onClose: () => void }) => {
@@ -28,19 +47,22 @@ const MobileAdminNav = ({ onClose }: { onClose: () => void }) => {
{/* 메뉴 목록 */}
- {menuItems.map(({ label, icon, path }) => {
+ {menuItems.map(({ label, icon, activeIcon, path }) => {
const isActive = pathname === path;
return (
- navigate(path)}
>
-
+ {isActive ? (
+
+ ) : (
+
+ )}
+
{label}
);
@@ -48,12 +70,7 @@ const MobileAdminNav = ({ onClose }: { onClose: () => void }) => {
{/* 하단 - 로그아웃 */}
-
-

+
diff --git a/apps/nowait-admin/src/components/MobileMenuBar.tsx b/apps/nowait-admin/src/components/MobileMenuBar.tsx
index 1642d2be..4e4a6c29 100644
--- a/apps/nowait-admin/src/components/MobileMenuBar.tsx
+++ b/apps/nowait-admin/src/components/MobileMenuBar.tsx
@@ -9,7 +9,7 @@ const MobileMenuBar = () => {
return (
{/* 좌측: 로고 + 텍스트 */}
-
+
diff --git a/apps/nowait-admin/src/global.css b/apps/nowait-admin/src/global.css
index b35048d8..4dcc4ebb 100644
--- a/apps/nowait-admin/src/global.css
+++ b/apps/nowait-admin/src/global.css
@@ -65,3 +65,62 @@ body,
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
+/* 에디터 관련(글 입력시 아웃라인 제거) */
+.ProseMirror:focus {
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+/* toggle버튼 */
+.toggle-btn .switch {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 24px;
+}
+
+.toggle-btn .switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.toggle-btn .slider {
+ position: absolute;
+ cursor: pointer;
+ inset: 0;
+ background-color: #d3d8df;
+ transition: all 0.4s ease;
+ border-radius: 34px;
+}
+
+.toggle-btn .slider::before {
+ position: absolute;
+ content: "";
+ width: 18px;
+ height: 18px;
+ left: 3px;
+ top: 3px;
+ background-color: white;
+ transition: all 0.4s ease;
+ border-radius: 50%;
+ box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 4px 0px;
+}
+
+.toggle-btn input:checked + .slider {
+ background-color: #000000;
+}
+
+.toggle-btn input:checked + .slider::before {
+ transform: translateX(16px);
+}
+
+.toggle-btn .slider.round {
+ border-radius: 34px;
+}
+
+/* 라운드 버튼 넘칠때 그라데이션 효과 */
+.mask-fade-right {
+ mask-image: linear-gradient(to right, black 80%, transparent 100%);
+ -webkit-mask-image: linear-gradient(to right, black 80%, transparent 100%);
+}
diff --git a/apps/nowait-admin/src/layout/AdminLayout.tsx b/apps/nowait-admin/src/layout/AdminLayout.tsx
index c76d1736..17ff3d1d 100644
--- a/apps/nowait-admin/src/layout/AdminLayout.tsx
+++ b/apps/nowait-admin/src/layout/AdminLayout.tsx
@@ -2,6 +2,7 @@ import { Outlet } from "react-router-dom";
import AdminSidebar from "../components/AdminSidebar";
import MobileMenuBar from "../components/MobileMenuBar";
import { useWindowWidth } from "../hooks/useWindowWidth";
+import { useLocation } from "react-router-dom";
const AdminLayout = () => {
const width = useWindowWidth();
@@ -9,20 +10,24 @@ const AdminLayout = () => {
const isCompact = width < 1024;
const isMobile = width <= 431;
+ const { pathname } = useLocation();
+ const isBoothPage = pathname === "/admin/booth";
+
return (
{isMobile ?
:
}
diff --git a/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx b/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx
index 2abb3351..3894f554 100644
--- a/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx
+++ b/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx
@@ -1,5 +1,160 @@
-const AdminBooth = () => {
- return
부스 관리 페이지 입니다
;
+import { useState } from "react";
+import booth_thumbnail from "../../assets/booth_thumbnail.svg";
+import editIcon from "../../assets/edit_icon.svg";
+import placeholderIcon from "../../assets/image_placeholder.svg";
+import { useWindowWidth } from "../../hooks/useWindowWidth";
+import OperatingTimeSelector from "./components/OperatingTimeSelector";
+import NoticeEditor from "./components/NoticeEditor";
+
+const BoothForm = () => {
+ const width = useWindowWidth();
+ const isTablet = width >= 768 && width <= 1024;
+ const [activeTab, setActiveTab] = useState<"booth" | "menu">("booth");
+ const [boothName, setBoothName] = useState("");
+ const [boothIntro, setBoothIntro] = useState("");
+ const [noticeTitle, setNoticeTitle] = useState("");
+ const [noticeContent, setNoticeContent] = useState("");
+ const [bannerImages, setBannerImages] = useState
([]);
+
+ return (
+
+ {/* 탭 */}
+
+
+
+
+
+ {/* 부스명 */}
+
+
+

+
+
+
+
+ 부스명
+
+
+ 컴퓨터공학과
+
+
+ setBoothName(e.target.value)}
+ placeholder="부스명을 입력해주세요"
+ className="w-full h-full border border-[#DDDDDD] rounded-xl px-4 py-2 text-sm"
+ />
+
+ {boothName.length} / 20
+
+
+
+
+
+ {/* 부스 소개 */}
+
+
+
+ 부스를 자유롭게 소개해주세요
+
+
+
+ {/* 배너 이미지 */}
+
+
+
+ 첫번째 이미지는 우리 부스를 대표하는 이미지로 설정돼요
+
+
+
+
+
+
+ {/* 공지사항 */}
+
+
+ {/* 버튼 */}
+
+
+
+
+
+ );
};
-export default AdminBooth;
+export default BoothForm;
diff --git a/apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx b/apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx
new file mode 100644
index 00000000..5f858c0b
--- /dev/null
+++ b/apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx
@@ -0,0 +1,84 @@
+import { useState } from "react";
+import { useEditor, EditorContent } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import Underline from "@tiptap/extension-underline";
+
+const MenuBar = ({ editor }: { editor: any }) => {
+ if (!editor) return null;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const NoticeEditor = () => {
+ const [title, setTitle] = useState("제목을 입력해주세요");
+
+ const editor = useEditor({
+ extensions: [StarterKit, Underline],
+ content: `내용을 입력해주세요`,
+ });
+
+ const handleSave = () => {
+ const html = editor?.getHTML();
+ console.log("제목:", title);
+ console.log("내용:", html);
+ // API 전송 가능
+ };
+
+ return (
+
+ setTitle(e.target.value)}
+ placeholder="공지 제목을 입력하세요"
+ className="w-full px-5 py-3 text-[#666666] bg-black-20 font-semibold rounded-t-xl outline-none"
+ />
+
+
+
+ );
+};
+
+export default NoticeEditor;
diff --git a/apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx b/apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx
new file mode 100644
index 00000000..f8f17651
--- /dev/null
+++ b/apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx
@@ -0,0 +1,95 @@
+import { useState } from "react";
+import dropIcon from "../../../assets/drop_down.svg";
+
+const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
+const minutes = ["00", "10", "20", "30", "40", "50"];
+
+const CustomSelect = ({
+ value,
+ onChange,
+ options,
+ placeholder,
+}: {
+ value: string;
+ onChange: (e: React.ChangeEvent) => void;
+ options: string[];
+ placeholder: string;
+}) => (
+
+
+
+

+
+
+);
+
+const OperatingTimeSelector = () => {
+ const [startHour, setStartHour] = useState("");
+ const [startMinute, setStartMinute] = useState("");
+ const [endHour, setEndHour] = useState("");
+ const [endMinute, setEndMinute] = useState("");
+
+ return (
+
+ {/* 제목 */}
+
+
+ 부스의 운영 시간을 설정해 주세요
+
+
+ {/* 선택 영역 */}
+
+ 시작
+
+ {/* 시작 시 */}
+ setStartHour(e.target.value)}
+ options={hours}
+ placeholder="시"
+ />
+
+ {/* 시작 분 */}
+ setStartMinute(e.target.value)}
+ options={minutes}
+ placeholder="분"
+ />
+
+ -
+
+ 종료
+
+ {/* 종료 시 */}
+ setEndHour(e.target.value)}
+ options={hours}
+ placeholder="시"
+ />
+
+ {/* 종료 분 */}
+ setEndMinute(e.target.value)}
+ options={minutes}
+ placeholder="분"
+ />
+
+
+ );
+};
+
+export default OperatingTimeSelector;
diff --git a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
index c1a37eb7..d5efe6f0 100644
--- a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
+++ b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
@@ -1,16 +1,13 @@
import { useEffect, useMemo, useState } from "react";
-import CardBox from "./components/CardBox";
import RoundTabButton from "./components/RoundTabButton";
import refreshIcon from "../../assets/refresh.svg";
import { WaitingCard } from "./components/WaitingCard";
import { useGetReservationList } from "../../hooks/useGetReservationList";
import on from "../../assets/on.svg";
import off from "../../assets/off.svg";
-import onIcon from "../../assets/toggleOn.svg"; // 켜짐 상태 이미지
-import offIcon from "../../assets/toggleOFF.svg";
-import { useWindowWidth } from "../../hooks/useWindowWidth";
import { useUpdateReservationStatus } from "../../hooks/useUpdateReservationStatus";
import ConfirmRemoveModal from "../../components/ConfirmRemoveModal";
+import ToggleSwitch from "./components/ToggleSwitch";
type WaitingStatus = "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED";
interface Reservation {
@@ -30,9 +27,9 @@ const AdminHome = () => {
const { mutate: updateStatus } = useUpdateReservationStatus();
const [showModal, setShowModal] = useState(false);
- const [activeTab, setActiveTab] = useState("전체 보기");
+ const [activeTab, setActiveTab] = useState("전체");
const storeId = 1; //현재는 임시로 mockdata씀
- const [isOn, setIsOn] = useState(false);
+ const [isOn, setIsOn] = useState(true);
const [reservations, setReservations] = useState([]);
const { data, isLoading, isError } = useGetReservationList(storeId);
@@ -55,7 +52,7 @@ const AdminHome = () => {
).length;
const tabLabels = [
- { label: "전체 보기" },
+ { label: "전체" },
{ label: "대기 중", count: waitingCount },
{ label: "호출 중", count: callingCount },
{ label: "입장 완료", count: confirmedCount },
@@ -71,7 +68,7 @@ const AdminHome = () => {
const filteredReservations = useMemo(() => {
const sorted = [...reservations].sort((a, b) => a.id - b.id);
- if (activeTab === "전체 보기") return reservations;
+ if (activeTab === "전체") return reservations;
const targetStatus = Object.entries(statusMap).find(
([, label]) => label === activeTab
@@ -181,20 +178,29 @@ const AdminHome = () => {
className="flex w-full [@media(min-width:375px)_and_(max-width:431px)]:justify-center m-0"
>
-
+
-
+
@@ -202,14 +208,14 @@ const AdminHome = () => {
{/* 대기자 목록
*/}
-
+
{tabLabels.map(({ label, count }) => (
setActiveTab(label)}
- count={label === "전체 보기" ? undefined : count}
+ count={label === "전체" ? undefined : count}
/>
))}
diff --git a/apps/nowait-admin/src/pages/AdminHome/components/ToggleSwitch.tsx b/apps/nowait-admin/src/pages/AdminHome/components/ToggleSwitch.tsx
new file mode 100644
index 00000000..a67a0373
--- /dev/null
+++ b/apps/nowait-admin/src/pages/AdminHome/components/ToggleSwitch.tsx
@@ -0,0 +1,17 @@
+interface ToggleSwitchProps {
+ isOn: boolean;
+ toggle: () => void;
+}
+
+const ToggleSwitch = ({ isOn, toggle }: ToggleSwitchProps) => {
+ return (
+
+
+
+ );
+};
+
+export default ToggleSwitch;
diff --git a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
index 3ca1fbdf..256a3b60 100644
--- a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
+++ b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
@@ -1,6 +1,7 @@
import CloseButton from "../../../components/closeButton";
import callIcon from "../../../assets/Call.svg";
import openDoorIcon from "../../../assets/door_open.svg";
+import alarmIcon from "../../../assets/alarm.svg";
import { useEffect, useState } from "react";
const totalDurationSec = 600;
@@ -90,7 +91,7 @@ export function WaitingCard({
{/* 입장인원 */}
-
입장인원
+
입장
{peopleCount}명
@@ -132,16 +133,24 @@ export function WaitingCard({
{status === "CALLING" &&
(isNoShow === true ? (
-
- 미입장
-
+ <>
+
+
+ >
) : (
<>
-
- ⏱ {elapsed}
+
+

{elapsed}