init
This commit is contained in:
		
							parent
							
								
									50a62794ed
								
							
						
					
					
						commit
						550865f643
					
				
					 31 changed files with 3335 additions and 272 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -34,3 +34,5 @@ yarn-error.log*
 | 
				
			||||||
# typescript
 | 
					# typescript
 | 
				
			||||||
*.tsbuildinfo
 | 
					*.tsbuildinfo
 | 
				
			||||||
next-env.d.ts
 | 
					next-env.d.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					order.sqlite3
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,3 @@
 | 
				
			||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Getting Started
 | 
					## Getting Started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
First, run the development server:
 | 
					First, run the development server:
 | 
				
			||||||
| 
						 | 
					@ -28,9 +26,3 @@ To learn more about Next.js, take a look at the following resources:
 | 
				
			||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
 | 
					- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
 | 
					You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Deploy on Vercel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +1,76 @@
 | 
				
			||||||
@tailwind base;
 | 
					@tailwind base;
 | 
				
			||||||
@tailwind components;
 | 
					  @tailwind components;
 | 
				
			||||||
@tailwind utilities;
 | 
					  @tailwind utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:root {
 | 
					  @layer base {
 | 
				
			||||||
  --foreground-rgb: 0, 0, 0;
 | 
					    :root {
 | 
				
			||||||
  --background-start-rgb: 214, 219, 220;
 | 
					      --background: 0 0% 100%;
 | 
				
			||||||
  --background-end-rgb: 255, 255, 255;
 | 
					      --foreground: 240 10% 3.9%;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media (prefers-color-scheme: dark) {
 | 
					      --card: 0 0% 100%;
 | 
				
			||||||
  :root {
 | 
					      --card-foreground: 240 10% 3.9%;
 | 
				
			||||||
    --foreground-rgb: 255, 255, 255;
 | 
					
 | 
				
			||||||
    --background-start-rgb: 0, 0, 0;
 | 
					      --popover: 0 0% 100%;
 | 
				
			||||||
    --background-end-rgb: 0, 0, 0;
 | 
					      --popover-foreground: 240 10% 3.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --primary: 240 5.9% 10%;
 | 
				
			||||||
 | 
					      --primary-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --secondary: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					      --secondary-foreground: 240 5.9% 10%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --muted: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					      --muted-foreground: 240 3.8% 46.1%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --accent: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					      --accent-foreground: 240 5.9% 10%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --destructive: 0 84.2% 60.2%;
 | 
				
			||||||
 | 
					      --destructive-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --border: 240 5.9% 90%;
 | 
				
			||||||
 | 
					      --input: 240 5.9% 90%;
 | 
				
			||||||
 | 
					      --ring: 240 10% 3.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --radius: 0.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .dark {
 | 
				
			||||||
 | 
					      --background: 240 10% 3.9%;
 | 
				
			||||||
 | 
					      --foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --card: 240 10% 3.9%;
 | 
				
			||||||
 | 
					      --card-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --popover: 240 10% 3.9%;
 | 
				
			||||||
 | 
					      --popover-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --primary: 0 0% 98%;
 | 
				
			||||||
 | 
					      --primary-foreground: 240 5.9% 10%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --secondary: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					      --secondary-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --muted: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					      --muted-foreground: 240 5% 64.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --accent: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					      --accent-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --destructive: 0 62.8% 30.6%;
 | 
				
			||||||
 | 
					      --destructive-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --border: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					      --input: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					      --ring: 240 4.9% 83.9%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					  @layer base {
 | 
				
			||||||
  color: rgb(var(--foreground-rgb));
 | 
					    * {
 | 
				
			||||||
  background: linear-gradient(
 | 
					      @apply border-border;
 | 
				
			||||||
      to bottom,
 | 
					    }
 | 
				
			||||||
      transparent,
 | 
					    body {
 | 
				
			||||||
      rgb(var(--background-end-rgb))
 | 
					      @apply bg-background text-foreground;
 | 
				
			||||||
    )
 | 
					    }
 | 
				
			||||||
    rgb(var(--background-start-rgb));
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@layer utilities {
 | 
					 | 
				
			||||||
  .text-balance {
 | 
					 | 
				
			||||||
    text-wrap: balance;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,12 @@
 | 
				
			||||||
import type { Metadata } from "next";
 | 
					import type { Metadata } from "next";
 | 
				
			||||||
import { Inter } from "next/font/google";
 | 
					import { Inter, Noto_Sans_KR } from "next/font/google";
 | 
				
			||||||
import "./globals.css";
 | 
					import "./globals.css";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inter = Inter({ subsets: ["latin"] });
 | 
					const NotoSansKR = Noto_Sans_KR({
 | 
				
			||||||
 | 
					   subsets: ["latin"],
 | 
				
			||||||
 | 
					  variable: "--font-sans",
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const metadata: Metadata = {
 | 
					export const metadata: Metadata = {
 | 
				
			||||||
  title: "Create Next App",
 | 
					  title: "Create Next App",
 | 
				
			||||||
| 
						 | 
					@ -15,8 +19,9 @@ export default function RootLayout({
 | 
				
			||||||
  children: React.ReactNode;
 | 
					  children: React.ReactNode;
 | 
				
			||||||
}>) {
 | 
					}>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <html lang="en">
 | 
					    <html lang="ko">
 | 
				
			||||||
      <body className={inter.className}>{children}</body>
 | 
					      <head />
 | 
				
			||||||
 | 
					      <body className={cn("min-h-dvh bg-background font-sans antialiased", NotoSansKR.variable)}>{children}</body>
 | 
				
			||||||
    </html>
 | 
					    </html>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								app/order/action.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/order/action.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					"use server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Order } from "@/hooks/useOrder";
 | 
				
			||||||
 | 
					import { saveOrder, Payment, cancelOrder, completeOrder } from "@/lib/db";
 | 
				
			||||||
 | 
					import { revalidatePath } from "next/cache";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function saveOrderApi(orders: Order[], payment: Payment){
 | 
				
			||||||
 | 
					    console.log(orders, "주문");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // db에 주문 저장
 | 
				
			||||||
 | 
					    const id = await saveOrder(orders, payment);
 | 
				
			||||||
 | 
					    // cache revaildation
 | 
				
			||||||
 | 
					    revalidatePath("/", "page");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        id,
 | 
				
			||||||
 | 
					        completed : false,
 | 
				
			||||||
 | 
					        orders: orders,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function cancelOrderApi(uid: string){
 | 
				
			||||||
 | 
					    await cancelOrder(uid);
 | 
				
			||||||
 | 
					    revalidatePath("/", "page");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function completeOrderApi(uid: string, completed: boolean = true){
 | 
				
			||||||
 | 
					    await completeOrder(uid, completed);
 | 
				
			||||||
 | 
					    revalidatePath("/", "page");
 | 
				
			||||||
 | 
					    revalidatePath("/stat", "page");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								app/order/page.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/order/page.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					"use server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { readMenu } from "../../lib/readCsv";
 | 
				
			||||||
 | 
					import OrderComponent from "@/components/order";
 | 
				
			||||||
 | 
					import NavMenu from "@/components/NavMenu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Order() {
 | 
				
			||||||
 | 
					    //read csv file
 | 
				
			||||||
 | 
					    const menu = await readMenu();
 | 
				
			||||||
 | 
					    const categories = [...(new Set(menu.map(item => item.category)))];
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="">
 | 
				
			||||||
 | 
					                <NavMenu />
 | 
				
			||||||
 | 
					            <div className="p-4">
 | 
				
			||||||
 | 
					                <OrderComponent menus={menu} categories={categories} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										123
									
								
								app/page.tsx
									
										
									
									
									
								
							
							
						
						
									
										123
									
								
								app/page.tsx
									
										
									
									
									
								
							| 
						 | 
					@ -1,113 +1,22 @@
 | 
				
			||||||
import Image from "next/image";
 | 
					import OrderCard from "@/components/orderCard";
 | 
				
			||||||
 | 
					import { loadOrder } from "@/lib/db";
 | 
				
			||||||
 | 
					import NavMenu from "@/components/NavMenu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Home() {
 | 
					export default async function Home() {
 | 
				
			||||||
 | 
					  const orders = await loadOrder();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
 | 
					    <main className="min-h-screen">
 | 
				
			||||||
      <div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
 | 
					        <NavMenu />
 | 
				
			||||||
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto  lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
 | 
					      {orders.length === 0 && (
 | 
				
			||||||
          Get started by editing 
 | 
					        <div className="flex items-center justify-center h-[calc(100vh-4rem)]">
 | 
				
			||||||
          <code className="font-mono font-bold">app/page.tsx</code>
 | 
					          <p className="text-muted-foreground text-2xl">주문이 없습니다.</p>
 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
        <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none">
 | 
					 | 
				
			||||||
          <a
 | 
					 | 
				
			||||||
            className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
 | 
					 | 
				
			||||||
            href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
 | 
					 | 
				
			||||||
            target="_blank"
 | 
					 | 
				
			||||||
            rel="noopener noreferrer"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            By{" "}
 | 
					 | 
				
			||||||
            <Image
 | 
					 | 
				
			||||||
              src="/vercel.svg"
 | 
					 | 
				
			||||||
              alt="Vercel Logo"
 | 
					 | 
				
			||||||
              className="dark:invert"
 | 
					 | 
				
			||||||
              width={100}
 | 
					 | 
				
			||||||
              height={24}
 | 
					 | 
				
			||||||
              priority
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </a>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      )}
 | 
				
			||||||
 | 
					      <section className="grid grid-cols-[repeat(auto-fill,_minmax(300px,_auto))] p-4 gap-2">
 | 
				
			||||||
      <div className="relative z-[-1] flex place-items-center before:absolute before:h-[300px] before:w-full before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 sm:before:w-[480px] sm:after:w-[240px] before:lg:h-[360px]">
 | 
					        {orders.map((order) => (
 | 
				
			||||||
        <Image
 | 
					          <OrderCard key={order.id} order={order} />
 | 
				
			||||||
          className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
 | 
					        ))}
 | 
				
			||||||
          src="/next.svg"
 | 
					      </section>
 | 
				
			||||||
          alt="Next.js Logo"
 | 
					 | 
				
			||||||
          width={180}
 | 
					 | 
				
			||||||
          height={37}
 | 
					 | 
				
			||||||
          priority
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className="mb-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
 | 
					 | 
				
			||||||
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
 | 
					 | 
				
			||||||
          target="_blank"
 | 
					 | 
				
			||||||
          rel="noopener noreferrer"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <h2 className="mb-3 text-2xl font-semibold">
 | 
					 | 
				
			||||||
            Docs{" "}
 | 
					 | 
				
			||||||
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
 | 
					 | 
				
			||||||
              ->
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="m-0 max-w-[30ch] text-sm opacity-50">
 | 
					 | 
				
			||||||
            Find in-depth information about Next.js features and API.
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
 | 
					 | 
				
			||||||
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
 | 
					 | 
				
			||||||
          target="_blank"
 | 
					 | 
				
			||||||
          rel="noopener noreferrer"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <h2 className="mb-3 text-2xl font-semibold">
 | 
					 | 
				
			||||||
            Learn{" "}
 | 
					 | 
				
			||||||
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
 | 
					 | 
				
			||||||
              ->
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="m-0 max-w-[30ch] text-sm opacity-50">
 | 
					 | 
				
			||||||
            Learn about Next.js in an interactive course with quizzes!
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
 | 
					 | 
				
			||||||
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
 | 
					 | 
				
			||||||
          target="_blank"
 | 
					 | 
				
			||||||
          rel="noopener noreferrer"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <h2 className="mb-3 text-2xl font-semibold">
 | 
					 | 
				
			||||||
            Templates{" "}
 | 
					 | 
				
			||||||
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
 | 
					 | 
				
			||||||
              ->
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="m-0 max-w-[30ch] text-sm opacity-50">
 | 
					 | 
				
			||||||
            Explore starter templates for Next.js.
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
 | 
					 | 
				
			||||||
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
 | 
					 | 
				
			||||||
          target="_blank"
 | 
					 | 
				
			||||||
          rel="noopener noreferrer"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <h2 className="mb-3 text-2xl font-semibold">
 | 
					 | 
				
			||||||
            Deploy{" "}
 | 
					 | 
				
			||||||
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
 | 
					 | 
				
			||||||
              ->
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="m-0 max-w-[30ch] text-balance text-sm opacity-50">
 | 
					 | 
				
			||||||
            Instantly deploy your Next.js site to a shareable URL with Vercel.
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </main>
 | 
					    </main>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										65
									
								
								app/stat/page.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/stat/page.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					import NavMenu from "@/components/NavMenu";
 | 
				
			||||||
 | 
					import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 | 
				
			||||||
 | 
					import { Order } from "@/hooks/useOrder";
 | 
				
			||||||
 | 
					import { loadOrder } from "@/lib/db";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function StatItem({title, value}: {title: string, value: string}) {
 | 
				
			||||||
 | 
					    return <div className="flex flex-col">
 | 
				
			||||||
 | 
					        <p className="text-muted-foreground text-sm">{title}</p>
 | 
				
			||||||
 | 
					        <p className="text-lg">{value}</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function sum (...args: number[]) {
 | 
				
			||||||
 | 
					    return args.reduce((acc, cur) => acc + cur, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ordersSum(orders: Order[]){
 | 
				
			||||||
 | 
					    return orders.reduce((acc, cur) => acc + cur.price * cur.quantity * 1000, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Stat() {
 | 
				
			||||||
 | 
					    const orders = await loadOrder();
 | 
				
			||||||
 | 
					    const completed_order= orders.filter(order => order.completed === 1);
 | 
				
			||||||
 | 
					    const cash_order = completed_order.filter(order => order.payment === "cash");
 | 
				
			||||||
 | 
					    const account_order = completed_order.filter(order => order.payment === "account");
 | 
				
			||||||
 | 
					    return <div className="">
 | 
				
			||||||
 | 
					        <NavMenu />
 | 
				
			||||||
 | 
					        <div className="p-4">
 | 
				
			||||||
 | 
					            <Card>
 | 
				
			||||||
 | 
					                <CardHeader>
 | 
				
			||||||
 | 
					                    <CardTitle>주문통계</CardTitle>
 | 
				
			||||||
 | 
					                </CardHeader>
 | 
				
			||||||
 | 
					                <CardContent>
 | 
				
			||||||
 | 
					                    <div className="grid grid-cols-2">
 | 
				
			||||||
 | 
					                        <StatItem title="총 주문 수" value={orders.length.toString()} />
 | 
				
			||||||
 | 
					                        <StatItem title="완료된 주문 수" value={completed_order.length.toString()} />
 | 
				
			||||||
 | 
					                        <StatItem title="현금 결제 주문 수" value={cash_order.length.toString()} />
 | 
				
			||||||
 | 
					                        <StatItem title="계좌 이체 주문 수" value={account_order.length.toString()} />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <hr className="my-2" />
 | 
				
			||||||
 | 
					                    <div className="grid grid-cols-2">
 | 
				
			||||||
 | 
					                        <StatItem title="총 매출" value={sum(...completed_order.map(order=> ordersSum(order.orders)))
 | 
				
			||||||
 | 
					                            .toLocaleString("ko-KR", {
 | 
				
			||||||
 | 
					                                style: "currency",
 | 
				
			||||||
 | 
					                                currency: "KRW",
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        } />
 | 
				
			||||||
 | 
					                        <StatItem title="현금 매출" value={sum(...cash_order.map(order=> ordersSum(order.orders)))
 | 
				
			||||||
 | 
					                            .toLocaleString("ko-KR", {
 | 
				
			||||||
 | 
					                                style: "currency",
 | 
				
			||||||
 | 
					                                currency: "KRW",
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        } />
 | 
				
			||||||
 | 
					                        <StatItem title="계좌 매출" value={sum(...account_order.map(order=> ordersSum(order.orders)))
 | 
				
			||||||
 | 
					                            .toLocaleString("ko-KR", {
 | 
				
			||||||
 | 
					                                style: "currency",
 | 
				
			||||||
 | 
					                                currency: "KRW",
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        } />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </CardContent>
 | 
				
			||||||
 | 
					            </Card>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								components.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								components.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://ui.shadcn.com/schema.json",
 | 
				
			||||||
 | 
					  "style": "new-york",
 | 
				
			||||||
 | 
					  "rsc": true,
 | 
				
			||||||
 | 
					  "tsx": true,
 | 
				
			||||||
 | 
					  "tailwind": {
 | 
				
			||||||
 | 
					    "config": "tailwind.config.ts",
 | 
				
			||||||
 | 
					    "css": "app/globals.css",
 | 
				
			||||||
 | 
					    "baseColor": "zinc",
 | 
				
			||||||
 | 
					    "cssVariables": true,
 | 
				
			||||||
 | 
					    "prefix": ""
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "aliases": {
 | 
				
			||||||
 | 
					    "components": "@/components",
 | 
				
			||||||
 | 
					    "utils": "@/lib/utils"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								components/NavMenu.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								components/NavMenu.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Link from "next/link";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    NavigationMenu,
 | 
				
			||||||
 | 
					    NavigationMenuItem,
 | 
				
			||||||
 | 
					    NavigationMenuLink,
 | 
				
			||||||
 | 
					    NavigationMenuList,
 | 
				
			||||||
 | 
					    navigationMenuTriggerStyle
 | 
				
			||||||
 | 
					} from "./ui/navigation-menu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function NavMenu() {
 | 
				
			||||||
 | 
					    return <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
 | 
				
			||||||
 | 
					        <div className="container flex h-14 max-w-screen-xl">
 | 
				
			||||||
 | 
					            <NavigationMenu>
 | 
				
			||||||
 | 
					                <NavigationMenuList>
 | 
				
			||||||
 | 
					                    <NavigationMenuItem>
 | 
				
			||||||
 | 
					                        <Link href="/" legacyBehavior passHref>
 | 
				
			||||||
 | 
					                            <NavigationMenuLink className={navigationMenuTriggerStyle()}>홈</NavigationMenuLink>
 | 
				
			||||||
 | 
					                        </Link>
 | 
				
			||||||
 | 
					                    </NavigationMenuItem>
 | 
				
			||||||
 | 
					                    <NavigationMenuItem>
 | 
				
			||||||
 | 
					                        <Link href="/order" legacyBehavior passHref>
 | 
				
			||||||
 | 
					                            <NavigationMenuLink className={navigationMenuTriggerStyle()}>주문</NavigationMenuLink>
 | 
				
			||||||
 | 
					                        </Link>
 | 
				
			||||||
 | 
					                    </NavigationMenuItem>
 | 
				
			||||||
 | 
					                    <NavigationMenuItem>
 | 
				
			||||||
 | 
					                        <Link href="/stat" legacyBehavior passHref>
 | 
				
			||||||
 | 
					                            <NavigationMenuLink className={navigationMenuTriggerStyle()}>통계</NavigationMenuLink>
 | 
				
			||||||
 | 
					                        </Link>
 | 
				
			||||||
 | 
					                    </NavigationMenuItem>
 | 
				
			||||||
 | 
					                </NavigationMenuList>
 | 
				
			||||||
 | 
					            </NavigationMenu>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								components/menu.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								components/menu.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					import { MenuItem } from "@/lib/readCsv";
 | 
				
			||||||
 | 
					import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
 | 
				
			||||||
 | 
					import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
 | 
				
			||||||
 | 
					import { Dispatch } from "react";
 | 
				
			||||||
 | 
					import { OrderAction } from "@/hooks/useOrder";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					import { Button } from "./ui/button";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Menu({
 | 
				
			||||||
 | 
					    menus,
 | 
				
			||||||
 | 
					    categories,
 | 
				
			||||||
 | 
					    dispatch
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    menus: MenuItem[];
 | 
				
			||||||
 | 
					    categories: string[];
 | 
				
			||||||
 | 
					    dispatch: Dispatch<OrderAction>
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const menu = menus;
 | 
				
			||||||
 | 
					    return <Tabs defaultValue={categories[0]}>
 | 
				
			||||||
 | 
					        <TabsList>
 | 
				
			||||||
 | 
					            {[...categories].map(category => (
 | 
				
			||||||
 | 
					                <TabsTrigger key={category} value={category}>{category}</TabsTrigger>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					        </TabsList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            [...categories].map(category => (
 | 
				
			||||||
 | 
					                <TabsContent key={category} value={category}>
 | 
				
			||||||
 | 
					                    <Card>
 | 
				
			||||||
 | 
					                        <CardHeader>
 | 
				
			||||||
 | 
					                            <CardTitle>{category}</CardTitle>
 | 
				
			||||||
 | 
					                        </CardHeader>
 | 
				
			||||||
 | 
					                        <CardContent>
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            <ul className="w-full flex flex-col">
 | 
				
			||||||
 | 
					                                <li className="flex gap-2">
 | 
				
			||||||
 | 
					                                    <span className="w-80 text-lg">Name</span>
 | 
				
			||||||
 | 
					                                    <span className="w-12 text-lg">HOT</span>
 | 
				
			||||||
 | 
					                                    <span className="w-12 text-lg">COLD</span>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                                <hr className=""/>
 | 
				
			||||||
 | 
					                                {menu.filter(item => item.category === category).map((item,index) => (
 | 
				
			||||||
 | 
					                                    <li key={item.name} className={cn("flex h-10 items-center gap-2", index % 2 === 0 ? "bg-accent" : "")}>
 | 
				
			||||||
 | 
					                                        <span  className="w-80">{item.name}</span>    
 | 
				
			||||||
 | 
					                                        <span className="w-12">{item.HOT}</span>
 | 
				
			||||||
 | 
					                                        <span className="w-12">{item.COLD}</span>
 | 
				
			||||||
 | 
					                                        <Button variant="outline" className="bg-sky-400 hover:bg-sky-500" 
 | 
				
			||||||
 | 
					                                        disabled={!item.COLD}
 | 
				
			||||||
 | 
					                                        onClick={() => {
 | 
				
			||||||
 | 
					                                            dispatch({ type: "ADD_ORDER", order: { 
 | 
				
			||||||
 | 
					                                                name: item.name, 
 | 
				
			||||||
 | 
					                                                HOT: false,
 | 
				
			||||||
 | 
					                                                price: item.COLD ?? 0,
 | 
				
			||||||
 | 
					                                                quantity: 1,
 | 
				
			||||||
 | 
					                                            }});
 | 
				
			||||||
 | 
					                                        }}
 | 
				
			||||||
 | 
					                                        >ICE Order</Button>
 | 
				
			||||||
 | 
					                                        <Button variant="outline" className="bg-red-400 hover:bg-red-500"
 | 
				
			||||||
 | 
					                                        disabled={!item.HOT}
 | 
				
			||||||
 | 
					                                        onClick={() => {
 | 
				
			||||||
 | 
					                                            dispatch({ type: "ADD_ORDER", order: { 
 | 
				
			||||||
 | 
					                                                name: item.name, 
 | 
				
			||||||
 | 
					                                                HOT: true,
 | 
				
			||||||
 | 
					                                                price: item.HOT ?? 0,
 | 
				
			||||||
 | 
					                                                quantity: 1,
 | 
				
			||||||
 | 
					                                            }});
 | 
				
			||||||
 | 
					                                        }}
 | 
				
			||||||
 | 
					                                        >HOT Order</Button>
 | 
				
			||||||
 | 
					                                    </li>
 | 
				
			||||||
 | 
					                                ))}
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                        </CardContent>
 | 
				
			||||||
 | 
					                    </Card>
 | 
				
			||||||
 | 
					                </TabsContent>
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </Tabs>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								components/order.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								components/order.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { MenuItem } from "@/lib/readCsv";
 | 
				
			||||||
 | 
					import { useOrder } from "../hooks/useOrder";
 | 
				
			||||||
 | 
					import Menu from "./menu";
 | 
				
			||||||
 | 
					import { Button } from "./ui/button";
 | 
				
			||||||
 | 
					import { Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "./ui/drawer";
 | 
				
			||||||
 | 
					import { useRouter } from "next/navigation";
 | 
				
			||||||
 | 
					import { saveOrderApi } from "@/app/order/action";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Order({
 | 
				
			||||||
 | 
					    menus,
 | 
				
			||||||
 | 
					    categories,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    menus: MenuItem[];
 | 
				
			||||||
 | 
					    categories: string[];
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const { state, dispatch } = useOrder();
 | 
				
			||||||
 | 
					    const router = useRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <Menu menus={menus} categories={categories} dispatch={dispatch} />
 | 
				
			||||||
 | 
					            <Drawer>
 | 
				
			||||||
 | 
					                <DrawerTrigger asChild>
 | 
				
			||||||
 | 
					                    <Button className="w-full mt-2 flex items-center">Order <span className="
 | 
				
			||||||
 | 
					            ml-1 flex items-center justify-center rounded-full h-6 px-1 text-center bg-background text-foreground">{state.orders.reduce(
 | 
				
			||||||
 | 
					                        (acc, order) => acc + order.quantity, 0)}</span></Button>
 | 
				
			||||||
 | 
					                </DrawerTrigger>
 | 
				
			||||||
 | 
					                <DrawerContent>
 | 
				
			||||||
 | 
					                    <DrawerHeader>
 | 
				
			||||||
 | 
					                        <DrawerTitle>다음 주문이 맞나요?</DrawerTitle>
 | 
				
			||||||
 | 
					                    </DrawerHeader>
 | 
				
			||||||
 | 
					                    <hr className="mx-4"/>
 | 
				
			||||||
 | 
					                    <ul className="flex flex-col gap-2 justify-center m-4">
 | 
				
			||||||
 | 
					                        {state.orders.map(order => (
 | 
				
			||||||
 | 
					                            <li key={`${order.HOT ? "HOT" : "COLD"} ${order.name}`} className="flex items-center">
 | 
				
			||||||
 | 
					                                <span className="w-12">{order.HOT ? "HOT " : "COLD"}</span>
 | 
				
			||||||
 | 
					                                <span className="flex-1">{order.name}</span>
 | 
				
			||||||
 | 
					                                <span className="flex-1">단위 가격{order.price * 1000}원</span>
 | 
				
			||||||
 | 
					                                <span className="flex-1">{order.quantity}개</span>
 | 
				
			||||||
 | 
					                                <span className="flex-1">총 가격 {order.price * order.quantity * 1000}원</span>
 | 
				
			||||||
 | 
					                                <div className="flex space-x-2">
 | 
				
			||||||
 | 
					                                    <Button variant="outline" 
 | 
				
			||||||
 | 
					                                    className="rounded-full"
 | 
				
			||||||
 | 
					                                    onClick={() => dispatch({ type: "ADD_ORDER", 
 | 
				
			||||||
 | 
					                                    order:{
 | 
				
			||||||
 | 
					                                        ...order,
 | 
				
			||||||
 | 
					                                        quantity: 1
 | 
				
			||||||
 | 
					                                    }})} >+1</Button>
 | 
				
			||||||
 | 
					                                    <Button variant="outline"
 | 
				
			||||||
 | 
					                                    className="rounded-full"
 | 
				
			||||||
 | 
					                                    onClick={() => dispatch({ type: "ADD_ORDER", 
 | 
				
			||||||
 | 
					                                    order: {
 | 
				
			||||||
 | 
					                                        ...order,
 | 
				
			||||||
 | 
					                                        quantity: -1
 | 
				
			||||||
 | 
					                                    }})}>-1</Button>
 | 
				
			||||||
 | 
					                                    <Button variant="outline" onClick={() => dispatch({ type: "REMOVE_ORDER", order })}>취소</Button>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        ))}
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                    <hr className="mx-4" />
 | 
				
			||||||
 | 
					                    <div className="flex mx-4 flex-col items-end">
 | 
				
			||||||
 | 
					                        <span className="text-sm text-muted-foreground">총 가격</span>
 | 
				
			||||||
 | 
					                        <span className="flex-1 text-lg">{state.orders.map(order => order.price * order.quantity)
 | 
				
			||||||
 | 
					                            .reduce((acc, price) => acc + price, 0) * 1000}원
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <DrawerFooter>
 | 
				
			||||||
 | 
					                        <DrawerClose asChild>
 | 
				
			||||||
 | 
					                            <Button onClick={async ()=>{
 | 
				
			||||||
 | 
					                                console.log("order complete", router);
 | 
				
			||||||
 | 
					                                const d = await saveOrderApi(state.orders, "account")
 | 
				
			||||||
 | 
					                                console.log(d);
 | 
				
			||||||
 | 
					                                console.log("a complete", router);
 | 
				
			||||||
 | 
					                                router.push("/");
 | 
				
			||||||
 | 
					                                
 | 
				
			||||||
 | 
					                            }}>계좌이체 주문하기</Button>
 | 
				
			||||||
 | 
					                        </DrawerClose>
 | 
				
			||||||
 | 
					                        <DrawerClose asChild>
 | 
				
			||||||
 | 
					                        <Button onClick={()=>{
 | 
				
			||||||
 | 
					                                saveOrderApi(state.orders, "cash").then(()=>{
 | 
				
			||||||
 | 
					                                    console.log("order complete", router);
 | 
				
			||||||
 | 
					                                    router.push("/");
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                            }}>현금 주문하기</Button>
 | 
				
			||||||
 | 
					                        </DrawerClose>
 | 
				
			||||||
 | 
					                    </DrawerFooter>
 | 
				
			||||||
 | 
					                </DrawerContent>
 | 
				
			||||||
 | 
					            </Drawer>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										63
									
								
								components/orderCard.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								components/orderCard.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { OrderStateColumns } from "@/lib/types";
 | 
				
			||||||
 | 
					import { cancelOrderApi, completeOrderApi } from "@/app/order/action";
 | 
				
			||||||
 | 
					import { Button } from "./ui/button";
 | 
				
			||||||
 | 
					import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
 | 
				
			||||||
 | 
					import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from "./ui/drawer";
 | 
				
			||||||
 | 
					import { Pencil2Icon, TrashIcon } from "@radix-ui/react-icons";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function OrderCard({
 | 
				
			||||||
 | 
					    order,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					    order: OrderStateColumns;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    return <Card className="flex flex-col h-60">
 | 
				
			||||||
 | 
					        <CardHeader className="relative">
 | 
				
			||||||
 | 
					            <div className="absolute right-2">
 | 
				
			||||||
 | 
					                <Drawer>
 | 
				
			||||||
 | 
					                    <DrawerTrigger asChild>
 | 
				
			||||||
 | 
					                        <Button variant="ghost" disabled={order.completed == 1}>
 | 
				
			||||||
 | 
					                            <TrashIcon />
 | 
				
			||||||
 | 
					                        </Button>
 | 
				
			||||||
 | 
					                    </DrawerTrigger>
 | 
				
			||||||
 | 
					                    <DrawerContent>
 | 
				
			||||||
 | 
					                        <DrawerHeader>
 | 
				
			||||||
 | 
					                            <DrawerTitle>
 | 
				
			||||||
 | 
					                                주문 취소
 | 
				
			||||||
 | 
					                            </DrawerTitle>
 | 
				
			||||||
 | 
					                            <DrawerDescription>
 | 
				
			||||||
 | 
					                                정말로 주문을 취소하시겠습니까?
 | 
				
			||||||
 | 
					                            </DrawerDescription>
 | 
				
			||||||
 | 
					                        </DrawerHeader>
 | 
				
			||||||
 | 
					                        <DrawerClose asChild>
 | 
				
			||||||
 | 
					                            <Button onClick={() => {
 | 
				
			||||||
 | 
					                                cancelOrderApi(order.id);
 | 
				
			||||||
 | 
					                            }}>취소</Button>
 | 
				
			||||||
 | 
					                        </DrawerClose>
 | 
				
			||||||
 | 
					                    </DrawerContent>
 | 
				
			||||||
 | 
					                </Drawer>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <CardTitle className={order.completed == 1 ? "text-muted-foreground" : ""} >{order.id.split("/")[1]}번 주문</CardTitle>
 | 
				
			||||||
 | 
					            <CardDescription>{order.completed ? "완료" : "대기중..."}</CardDescription>
 | 
				
			||||||
 | 
					        </CardHeader>
 | 
				
			||||||
 | 
					        <CardContent className="flex-1 overflow-scroll">
 | 
				
			||||||
 | 
					            <hr />
 | 
				
			||||||
 | 
					            {order.orders.map((item) => (
 | 
				
			||||||
 | 
					                <div key={item.name}>
 | 
				
			||||||
 | 
					                    <span>{item.HOT ? "HOT" : "COLD"} {item.name} {item.quantity}</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					        </CardContent>
 | 
				
			||||||
 | 
					        <CardFooter>
 | 
				
			||||||
 | 
					            <Button onClick={() => {
 | 
				
			||||||
 | 
					                if (order.completed) {
 | 
				
			||||||
 | 
					                    completeOrderApi(order.id, false);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    completeOrderApi(order.id);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }}>{order.completed == 0 ? "완료" : "완료 취소"}</Button>
 | 
				
			||||||
 | 
					        </CardFooter>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								components/ui/button.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								components/ui/button.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import { Slot } from "@radix-ui/react-slot"
 | 
				
			||||||
 | 
					import { cva, type VariantProps } from "class-variance-authority"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buttonVariants = cva(
 | 
				
			||||||
 | 
					  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          "bg-primary text-primary-foreground shadow hover:bg-primary/90",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
 | 
				
			||||||
 | 
					        outline:
 | 
				
			||||||
 | 
					          "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
 | 
				
			||||||
 | 
					        secondary:
 | 
				
			||||||
 | 
					          "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
 | 
				
			||||||
 | 
					        ghost: "hover:bg-accent hover:text-accent-foreground",
 | 
				
			||||||
 | 
					        link: "text-primary underline-offset-4 hover:underline",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      size: {
 | 
				
			||||||
 | 
					        default: "h-9 px-4 py-2",
 | 
				
			||||||
 | 
					        sm: "h-8 rounded-md px-3 text-xs",
 | 
				
			||||||
 | 
					        lg: "h-10 rounded-md px-8",
 | 
				
			||||||
 | 
					        icon: "h-9 w-9",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					      size: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ButtonProps
 | 
				
			||||||
 | 
					  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
 | 
				
			||||||
 | 
					    VariantProps<typeof buttonVariants> {
 | 
				
			||||||
 | 
					  asChild?: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
 | 
				
			||||||
 | 
					  ({ className, variant, size, asChild = false, ...props }, ref) => {
 | 
				
			||||||
 | 
					    const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Comp
 | 
				
			||||||
 | 
					        className={cn(buttonVariants({ variant, size, className }))}
 | 
				
			||||||
 | 
					        ref={ref}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					Button.displayName = "Button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Button, buttonVariants }
 | 
				
			||||||
							
								
								
									
										76
									
								
								components/ui/card.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								components/ui/card.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Card = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "rounded-xl border bg-card text-card-foreground shadow",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Card.displayName = "Card"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardHeader = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex flex-col space-y-1.5 p-6", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardHeader.displayName = "CardHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLHeadingElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <h3
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("font-semibold leading-none tracking-tight", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardTitle.displayName = "CardTitle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLParagraphElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <p
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardDescription.displayName = "CardDescription"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardContent = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardContent.displayName = "CardContent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardFooter = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex items-center p-6 pt-0", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardFooter.displayName = "CardFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
 | 
				
			||||||
							
								
								
									
										118
									
								
								components/ui/drawer.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								components/ui/drawer.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import { Drawer as DrawerPrimitive } from "vaul"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Drawer = ({
 | 
				
			||||||
 | 
					  shouldScaleBackground = true,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
 | 
				
			||||||
 | 
					  <DrawerPrimitive.Root
 | 
				
			||||||
 | 
					    shouldScaleBackground={shouldScaleBackground}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					Drawer.displayName = "Drawer"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerTrigger = DrawerPrimitive.Trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerPortal = DrawerPrimitive.Portal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerClose = DrawerPrimitive.Close
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerOverlay = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DrawerPrimitive.Overlay>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DrawerPrimitive.Overlay
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("fixed inset-0 z-50 bg-black/80", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DrawerPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DrawerPortal>
 | 
				
			||||||
 | 
					    <DrawerOverlay />
 | 
				
			||||||
 | 
					    <DrawerPrimitive.Content
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </DrawerPrimitive.Content>
 | 
				
			||||||
 | 
					  </DrawerPortal>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DrawerContent.displayName = "DrawerContent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerHeader = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					DrawerHeader.displayName = "DrawerHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerFooter = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    className={cn("mt-auto flex flex-col gap-2 p-4", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					DrawerFooter.displayName = "DrawerFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DrawerPrimitive.Title>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DrawerPrimitive.Title
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "text-lg font-semibold leading-none tracking-tight",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DrawerTitle.displayName = DrawerPrimitive.Title.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DrawerDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DrawerPrimitive.Description>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DrawerPrimitive.Description
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DrawerDescription.displayName = DrawerPrimitive.Description.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Drawer,
 | 
				
			||||||
 | 
					  DrawerPortal,
 | 
				
			||||||
 | 
					  DrawerOverlay,
 | 
				
			||||||
 | 
					  DrawerTrigger,
 | 
				
			||||||
 | 
					  DrawerClose,
 | 
				
			||||||
 | 
					  DrawerContent,
 | 
				
			||||||
 | 
					  DrawerHeader,
 | 
				
			||||||
 | 
					  DrawerFooter,
 | 
				
			||||||
 | 
					  DrawerTitle,
 | 
				
			||||||
 | 
					  DrawerDescription,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										128
									
								
								components/ui/navigation-menu.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								components/ui/navigation-menu.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,128 @@
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import { ChevronDownIcon } from "@radix-ui/react-icons"
 | 
				
			||||||
 | 
					import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
 | 
				
			||||||
 | 
					import { cva } from "class-variance-authority"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenu = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative z-10 flex max-w-max flex-1 items-center justify-center",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					    <NavigationMenuViewport />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuList = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.List>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.List
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "group flex flex-1 list-none items-center justify-center space-x-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuItem = NavigationMenuPrimitive.Item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const navigationMenuTriggerStyle = cva(
 | 
				
			||||||
 | 
					  "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Trigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(navigationMenuTriggerStyle(), "group", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}{" "}
 | 
				
			||||||
 | 
					    <ChevronDownIcon
 | 
				
			||||||
 | 
					      className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
 | 
				
			||||||
 | 
					      aria-hidden="true"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Content
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuLink = NavigationMenuPrimitive.Link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div className={cn("absolute left-0 top-full flex justify-center")}>
 | 
				
			||||||
 | 
					    <NavigationMenuPrimitive.Viewport
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuViewport.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuIndicator = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Indicator
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuIndicator.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Indicator.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  navigationMenuTriggerStyle,
 | 
				
			||||||
 | 
					  NavigationMenu,
 | 
				
			||||||
 | 
					  NavigationMenuList,
 | 
				
			||||||
 | 
					  NavigationMenuItem,
 | 
				
			||||||
 | 
					  NavigationMenuContent,
 | 
				
			||||||
 | 
					  NavigationMenuTrigger,
 | 
				
			||||||
 | 
					  NavigationMenuLink,
 | 
				
			||||||
 | 
					  NavigationMenuIndicator,
 | 
				
			||||||
 | 
					  NavigationMenuViewport,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								components/ui/tabs.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								components/ui/tabs.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as TabsPrimitive from "@radix-ui/react-tabs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Tabs = TabsPrimitive.Root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TabsList = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof TabsPrimitive.List>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <TabsPrimitive.List
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TabsList.displayName = TabsPrimitive.List.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TabsTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof TabsPrimitive.Trigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <TabsPrimitive.Trigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TabsContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof TabsPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <TabsPrimitive.Content
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TabsContent.displayName = TabsPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Tabs, TabsList, TabsTrigger, TabsContent }
 | 
				
			||||||
							
								
								
									
										69
									
								
								hooks/useOrder.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								hooks/useOrder.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					import { useReducer } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Order {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    quantity: number;
 | 
				
			||||||
 | 
					    HOT: boolean;
 | 
				
			||||||
 | 
					    price: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface OrderState {
 | 
				
			||||||
 | 
					    orders: Order[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initialState = {
 | 
				
			||||||
 | 
					    orders: [],
 | 
				
			||||||
 | 
					} as OrderState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type OrderAction =
 | 
				
			||||||
 | 
					    | { type: "ADD_ORDER"; order: Order }
 | 
				
			||||||
 | 
					    | { type: "REMOVE_ORDER"; order: Order }
 | 
				
			||||||
 | 
					    | { type: "UPDATE_ORDER"; order: Order };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function compareOrder(a: Order, b: Order) {
 | 
				
			||||||
 | 
					    if (a.name < b.name) {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (a.name > b.name) {
 | 
				
			||||||
 | 
					        return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (a.HOT && !b.HOT) {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!a.HOT && b.HOT) {
 | 
				
			||||||
 | 
					        return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function reducer(state: OrderState, action: OrderAction): OrderState {
 | 
				
			||||||
 | 
					    switch (action.type) {
 | 
				
			||||||
 | 
					        case "ADD_ORDER":
 | 
				
			||||||
 | 
					            if (state.orders.find(order => compareOrder(order, action.order) === 0)) {
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    orders: state.orders.map(order => compareOrder(order, action.order) === 0 ? {
 | 
				
			||||||
 | 
					                        ...order,
 | 
				
			||||||
 | 
					                        quantity: Math.max(order.quantity + action.order.quantity, 0)
 | 
				
			||||||
 | 
					                    } : order),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                orders: [...state.orders, action.order],
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        case "REMOVE_ORDER":
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                orders: state.orders.filter(order => compareOrder(order, action.order) !== 0),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        case "UPDATE_ORDER":
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                orders: state.orders.map(order => compareOrder(order, action.order) === 0 ? action.order : order),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useOrder() {
 | 
				
			||||||
 | 
					    const [state, dispatch] = useReducer(reducer, initialState);
 | 
				
			||||||
 | 
					    return { state, dispatch };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								lib/db.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								lib/db.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,97 @@
 | 
				
			||||||
 | 
					import { Order } from "@/hooks/useOrder";
 | 
				
			||||||
 | 
					import { Database, NewOrderState } from './types' // this is the Database interface we defined earlier
 | 
				
			||||||
 | 
					import SQLite from 'better-sqlite3'
 | 
				
			||||||
 | 
					import { Kysely, SqliteDialect } from 'kysely'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dialect = new SqliteDialect({
 | 
				
			||||||
 | 
					    database: new SQLite('order.sqlite3'),
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const db = new Kysely<Database>({
 | 
				
			||||||
 | 
					    dialect,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Payment = "account" | "cash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function requestOrderNumber() : Promise<number> {
 | 
				
			||||||
 | 
					    const date = new Date().toISOString().split("T")[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return await db.transaction().execute(async trx=> {
 | 
				
			||||||
 | 
					        const order_number = await trx.selectFrom("Order_Number")
 | 
				
			||||||
 | 
					            .where("id", "==", date)
 | 
				
			||||||
 | 
					            .selectAll()
 | 
				
			||||||
 | 
					            .executeTakeFirst();
 | 
				
			||||||
 | 
					        if (!order_number) {
 | 
				
			||||||
 | 
					            await trx.insertInto("Order_Number")
 | 
				
			||||||
 | 
					                .values({
 | 
				
			||||||
 | 
					                    id: date,
 | 
				
			||||||
 | 
					                    number: 1,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .execute();
 | 
				
			||||||
 | 
					            return 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const num = order_number.number + 1;
 | 
				
			||||||
 | 
					        await trx.updateTable("Order_Number")
 | 
				
			||||||
 | 
					            .set("number", num)
 | 
				
			||||||
 | 
					            .where("id", "==", date)
 | 
				
			||||||
 | 
					            .execute();
 | 
				
			||||||
 | 
					        return num;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function saveOrder(order: Order[], payment: Payment) {
 | 
				
			||||||
 | 
					    const date = new Date().toISOString().split("T")[0];
 | 
				
			||||||
 | 
					    const num = await requestOrderNumber();
 | 
				
			||||||
 | 
					    const id = `${date}/${num}`;
 | 
				
			||||||
 | 
					    await db.insertInto("Order_State")
 | 
				
			||||||
 | 
					        .values({
 | 
				
			||||||
 | 
					            id,
 | 
				
			||||||
 | 
					            orders: JSON.stringify(order),
 | 
				
			||||||
 | 
					            completed: 0,
 | 
				
			||||||
 | 
					            payment,
 | 
				
			||||||
 | 
					        } as NewOrderState)
 | 
				
			||||||
 | 
					        .execute();
 | 
				
			||||||
 | 
					    return id;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function loadOrder() {
 | 
				
			||||||
 | 
					    const orders = (await db.selectFrom("Order_State")
 | 
				
			||||||
 | 
					        .selectAll()
 | 
				
			||||||
 | 
					        .orderBy(["completed","id desc"])
 | 
				
			||||||
 | 
					        .execute())
 | 
				
			||||||
 | 
					        .map(order => ({
 | 
				
			||||||
 | 
					            ...order,
 | 
				
			||||||
 | 
					            orders: JSON.parse(order.orders as unknown as string) as Order[],
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    return orders;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function clearOrder() {
 | 
				
			||||||
 | 
					    await db.deleteFrom("Order_State")
 | 
				
			||||||
 | 
					        .execute();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function cancelOrder(order_id: string) {
 | 
				
			||||||
 | 
					    await db.deleteFrom("Order_State")
 | 
				
			||||||
 | 
					        .where("id", "==", order_id)
 | 
				
			||||||
 | 
					        .execute();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function completeOrder(uid: string, completed: boolean = true) {
 | 
				
			||||||
 | 
					    await db.updateTable("Order_State")
 | 
				
			||||||
 | 
					        .set("completed", completed ? 1 : 0)
 | 
				
			||||||
 | 
					        .where("id", "==", uid)
 | 
				
			||||||
 | 
					        .execute();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function loadOrderById(uid: string) {
 | 
				
			||||||
 | 
					    const order = await db.selectFrom("Order_State")
 | 
				
			||||||
 | 
					        .where("id", "==", uid)
 | 
				
			||||||
 | 
					        .selectAll()
 | 
				
			||||||
 | 
					        .executeTakeFirst();
 | 
				
			||||||
 | 
					    return order ? {
 | 
				
			||||||
 | 
					        ...order,
 | 
				
			||||||
 | 
					        orders: JSON.parse(order.orders as unknown as string) as Order[],
 | 
				
			||||||
 | 
					    } : null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								lib/readCsv.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								lib/readCsv.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					import { readFile } from "fs/promises";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MenuItem =  {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    HOT: number | null;
 | 
				
			||||||
 | 
					    COLD: number | null;
 | 
				
			||||||
 | 
					    category: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const readMenu = async () => {
 | 
				
			||||||
 | 
					    const file = await readFile('public/menu.csv', 'utf-8');
 | 
				
			||||||
 | 
					    const [headersRaw, ...rows] = file.split('\n').map(row => row.trim()).filter(row => row.length > 0);
 | 
				
			||||||
 | 
					    const headers = headersRaw.split(',').map(header => header.trim());
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const data = rows.map(row => {
 | 
				
			||||||
 | 
					        const items = row.split(',');
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            name: items[0].trim(),
 | 
				
			||||||
 | 
					            HOT: Number.parseFloat(items[1].trim()) || null,
 | 
				
			||||||
 | 
					            COLD: Number.parseFloat(items[2].trim()) || null,
 | 
				
			||||||
 | 
					            category: items[3].trim(),
 | 
				
			||||||
 | 
					        } satisfies MenuItem;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return data;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										34
									
								
								lib/types.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/types.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    ColumnType,
 | 
				
			||||||
 | 
					    Generated,
 | 
				
			||||||
 | 
					    Insertable,
 | 
				
			||||||
 | 
					    JSONColumnType,
 | 
				
			||||||
 | 
					    Selectable,
 | 
				
			||||||
 | 
					    Updateable
 | 
				
			||||||
 | 
					  } from 'kysely';
 | 
				
			||||||
 | 
					import { Order } from "@/hooks/useOrder";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface OrderState {
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    orders: JSONColumnType<Order[]>;
 | 
				
			||||||
 | 
					    completed: 0 | 1;
 | 
				
			||||||
 | 
					    payment: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type NewOrderState = Insertable<OrderState>;
 | 
				
			||||||
 | 
					export type OrderStateColumns = Selectable<OrderState>;
 | 
				
			||||||
 | 
					export type OrderStateUpdate = Updateable<OrderState>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface OrderNumber {
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    number: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type NewOrderNumber = Insertable<OrderNumber>;
 | 
				
			||||||
 | 
					export type OrderNumberColumns = Selectable<OrderNumber>;
 | 
				
			||||||
 | 
					export type OrderNumberUpdate = Updateable<OrderNumber>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Database {
 | 
				
			||||||
 | 
					    Order_State: OrderState;
 | 
				
			||||||
 | 
					    Order_Number: OrderNumber;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								lib/utils.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								lib/utils.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { type ClassValue, clsx } from "clsx"
 | 
				
			||||||
 | 
					import { twMerge } from "tailwind-merge"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function cn(...inputs: ClassValue[]) {
 | 
				
			||||||
 | 
					  return twMerge(clsx(inputs))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								migrate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								migrate.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import * as path from 'path'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { promises as fs } from 'fs'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Kysely,
 | 
				
			||||||
 | 
					  Migrator,
 | 
				
			||||||
 | 
					  SqliteDialect,
 | 
				
			||||||
 | 
					  FileMigrationProvider,
 | 
				
			||||||
 | 
					} from 'kysely'
 | 
				
			||||||
 | 
					import SQLite from "better-sqlite3";
 | 
				
			||||||
 | 
					import { Database } from './lib/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function migrateToLatest() {
 | 
				
			||||||
 | 
					    const __dirname = path.resolve();
 | 
				
			||||||
 | 
					  const db = new Kysely<Database>({
 | 
				
			||||||
 | 
					    dialect: new SqliteDialect({
 | 
				
			||||||
 | 
					        database: new SQLite("order.sqlite3"),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { up } = await import('./migration/2024-04-19');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await up(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   const migrator = new Migrator({
 | 
				
			||||||
 | 
					//     db,
 | 
				
			||||||
 | 
					//     provider: new FileMigrationProvider({
 | 
				
			||||||
 | 
					//       fs,
 | 
				
			||||||
 | 
					//       path,
 | 
				
			||||||
 | 
					//       // This needs to be an absolute path.
 | 
				
			||||||
 | 
					//       migrationFolder: path.join(__dirname, './migration'),
 | 
				
			||||||
 | 
					//     }),
 | 
				
			||||||
 | 
					//   })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   const { error, results } = await migrator.migrateToLatest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   results?.forEach((it) => {
 | 
				
			||||||
 | 
					//     if (it.status === 'Success') {
 | 
				
			||||||
 | 
					//       console.log(`migration "${it.migrationName}" was executed successfully`)
 | 
				
			||||||
 | 
					//     } else if (it.status === 'Error') {
 | 
				
			||||||
 | 
					//       console.error(`failed to execute migration "${it.migrationName}"`)
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//   })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//   if (error) {
 | 
				
			||||||
 | 
					//     console.error('failed to migrate')
 | 
				
			||||||
 | 
					//     console.error(error)
 | 
				
			||||||
 | 
					//     process.exit(1)
 | 
				
			||||||
 | 
					//   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await db.destroy()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					migrateToLatest()
 | 
				
			||||||
							
								
								
									
										22
									
								
								migration/2024-04-19.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								migration/2024-04-19.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					import { Kysely } from 'kysely'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function up(db: Kysely<any>): Promise<void> {
 | 
				
			||||||
 | 
					  await db.schema
 | 
				
			||||||
 | 
					    .createTable('Order_State')
 | 
				
			||||||
 | 
					    .addColumn('id', 'varchar', col => col.primaryKey().notNull())
 | 
				
			||||||
 | 
					    .addColumn('orders', 'jsonb', col => col.notNull())
 | 
				
			||||||
 | 
					    .addColumn('completed', 'boolean', col => col.notNull())
 | 
				
			||||||
 | 
					    .addColumn('payment', 'varchar', col => col.notNull())
 | 
				
			||||||
 | 
					    .execute()
 | 
				
			||||||
 | 
					  await db.schema
 | 
				
			||||||
 | 
					    .createTable('Order_Number')
 | 
				
			||||||
 | 
					    // current date. (e.g. 2024-04-19)
 | 
				
			||||||
 | 
					    .addColumn('id', 'varchar', col => col.primaryKey().notNull())
 | 
				
			||||||
 | 
					    .addColumn('number', 'integer', col => col.notNull())
 | 
				
			||||||
 | 
					    .execute()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function down(db: Kysely<any>): Promise<void> {
 | 
				
			||||||
 | 
					    await db.schema.dropTable('Order_State').execute()
 | 
				
			||||||
 | 
					    await db.schema.dropTable('Order_Number').execute()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,8 @@
 | 
				
			||||||
/** @type {import('next').NextConfig} */
 | 
					/** @type {import('next').NextConfig} */
 | 
				
			||||||
const nextConfig = {};
 | 
					const nextConfig = {
 | 
				
			||||||
 | 
					    experimental: {
 | 
				
			||||||
 | 
					        serverComponentsExternalPackages: ["@deno/kv"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default nextConfig;
 | 
					export default nextConfig;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										30
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -6,21 +6,39 @@
 | 
				
			||||||
    "dev": "next dev",
 | 
					    "dev": "next dev",
 | 
				
			||||||
    "build": "next build",
 | 
					    "build": "next build",
 | 
				
			||||||
    "start": "next start",
 | 
					    "start": "next start",
 | 
				
			||||||
    "lint": "next lint"
 | 
					    "lint": "next lint",
 | 
				
			||||||
 | 
					    "shadcn": "shadcn-ui"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "type": "module",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@deno/kv": "^0.7.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-dialog": "^1.0.5",
 | 
				
			||||||
 | 
					    "@radix-ui/react-icons": "^1.3.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-navigation-menu": "^1.1.4",
 | 
				
			||||||
 | 
					    "@radix-ui/react-slot": "^1.0.2",
 | 
				
			||||||
 | 
					    "@radix-ui/react-tabs": "^1.0.4",
 | 
				
			||||||
 | 
					    "better-sqlite3": "^9.5.0",
 | 
				
			||||||
 | 
					    "class-variance-authority": "^0.7.0",
 | 
				
			||||||
 | 
					    "clsx": "^2.1.0",
 | 
				
			||||||
 | 
					    "kysely": "^0.27.3",
 | 
				
			||||||
 | 
					    "next": "14.2.1",
 | 
				
			||||||
    "react": "^18",
 | 
					    "react": "^18",
 | 
				
			||||||
    "react-dom": "^18",
 | 
					    "react-dom": "^18",
 | 
				
			||||||
    "next": "14.2.1"
 | 
					    "tailwind-merge": "^2.2.2",
 | 
				
			||||||
 | 
					    "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
 | 
					    "tsx": "^4.7.2",
 | 
				
			||||||
 | 
					    "vaul": "^0.9.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "typescript": "^5",
 | 
					    "@types/better-sqlite3": "^7.6.9",
 | 
				
			||||||
    "@types/node": "^20",
 | 
					    "@types/node": "^20",
 | 
				
			||||||
    "@types/react": "^18",
 | 
					    "@types/react": "^18",
 | 
				
			||||||
    "@types/react-dom": "^18",
 | 
					    "@types/react-dom": "^18",
 | 
				
			||||||
    "postcss": "^8",
 | 
					 | 
				
			||||||
    "tailwindcss": "^3.4.1",
 | 
					 | 
				
			||||||
    "eslint": "^8",
 | 
					    "eslint": "^8",
 | 
				
			||||||
    "eslint-config-next": "14.2.1"
 | 
					    "eslint-config-next": "14.2.1",
 | 
				
			||||||
 | 
					    "postcss": "^8",
 | 
				
			||||||
 | 
					    "shadcn-ui": "^0.8.0",
 | 
				
			||||||
 | 
					    "tailwindcss": "^3.4.1",
 | 
				
			||||||
 | 
					    "typescript": "^5"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2062
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2062
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										26
									
								
								public/menu.csv
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								public/menu.csv
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					name,HOT,COLD,category
 | 
				
			||||||
 | 
					아메리카노,1.5,2,커피
 | 
				
			||||||
 | 
					헤이즐넛 아메리카노,1.5,2,커피
 | 
				
			||||||
 | 
					바닐라 아메리카노,1.5,2,커피
 | 
				
			||||||
 | 
					꿀단지 커피,2.5,3,커피
 | 
				
			||||||
 | 
					곰다방커피,,2,커피
 | 
				
			||||||
 | 
					콜드브루,,2,커피
 | 
				
			||||||
 | 
					카페모카,2,2.5,커피
 | 
				
			||||||
 | 
					카페라떼,3,3.5,카페라떼
 | 
				
			||||||
 | 
					헤이즐넛 카페라떼,3,3.5,카페라떼
 | 
				
			||||||
 | 
					바닐라 카페라떼,3,3.5,카페라떼
 | 
				
			||||||
 | 
					초코라떼,3,3.5,카페라떼
 | 
				
			||||||
 | 
					딸기라떼,3,3.5,카페라떼
 | 
				
			||||||
 | 
					사과유자차,3,3.5,차
 | 
				
			||||||
 | 
					꿀유자차,2,2.5,차
 | 
				
			||||||
 | 
					폴라복숭아,,2.5,차
 | 
				
			||||||
 | 
					허니자몽 블랙티,2.5,3,차
 | 
				
			||||||
 | 
					오미자 차,2,2.5,차
 | 
				
			||||||
 | 
					달곰상곰 딸기레몬,3.5,3.5,탄산
 | 
				
			||||||
 | 
					청포도 에이드,3,3,탄산
 | 
				
			||||||
 | 
					자몽에이드,3,3,탄산
 | 
				
			||||||
 | 
					레몬에이드,3,3,탄산
 | 
				
			||||||
 | 
					오미자 에이드,3,3,탄산
 | 
				
			||||||
 | 
					허니브레드,,3,빵
 | 
				
			||||||
 | 
					소금빵,,3,빵
 | 
				
			||||||
 | 
					크로크무슈,,3.5,빵
 | 
				
			||||||
		
		
			
  | 
| 
						 | 
					@ -1,20 +1,84 @@
 | 
				
			||||||
import type { Config } from "tailwindcss";
 | 
					import type { Config } from "tailwindcss"
 | 
				
			||||||
 | 
					import defaultTheme from "tailwindcss/defaultTheme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const config: Config = {
 | 
					const config = {
 | 
				
			||||||
 | 
					  darkMode: ["class"],
 | 
				
			||||||
  content: [
 | 
					  content: [
 | 
				
			||||||
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
 | 
					    './pages/**/*.{ts,tsx}',
 | 
				
			||||||
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
 | 
					    './components/**/*.{ts,tsx}',
 | 
				
			||||||
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
 | 
					    './app/**/*.{ts,tsx}',
 | 
				
			||||||
  ],
 | 
					    './src/**/*.{ts,tsx}',
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					  prefix: "",
 | 
				
			||||||
  theme: {
 | 
					  theme: {
 | 
				
			||||||
 | 
					    container: {
 | 
				
			||||||
 | 
					      center: true,
 | 
				
			||||||
 | 
					      padding: "2rem",
 | 
				
			||||||
 | 
					      screens: {
 | 
				
			||||||
 | 
					        "2xl": "1400px",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    extend: {
 | 
					    extend: {
 | 
				
			||||||
      backgroundImage: {
 | 
					      fontFamily: {
 | 
				
			||||||
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
 | 
					        sans: ["var(--font-sans)", ...defaultTheme.fontFamily.sans],
 | 
				
			||||||
        "gradient-conic":
 | 
					      },
 | 
				
			||||||
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
 | 
					      colors: {
 | 
				
			||||||
 | 
					        border: "hsl(var(--border))",
 | 
				
			||||||
 | 
					        input: "hsl(var(--input))",
 | 
				
			||||||
 | 
					        ring: "hsl(var(--ring))",
 | 
				
			||||||
 | 
					        background: "hsl(var(--background))",
 | 
				
			||||||
 | 
					        foreground: "hsl(var(--foreground))",
 | 
				
			||||||
 | 
					        primary: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--primary))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--primary-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        secondary: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--secondary))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--secondary-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        destructive: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--destructive))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--destructive-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        muted: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--muted))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--muted-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        accent: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--accent))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--accent-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        popover: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--popover))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--popover-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        card: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--card))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--card-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      borderRadius: {
 | 
				
			||||||
 | 
					        lg: "var(--radius)",
 | 
				
			||||||
 | 
					        md: "calc(var(--radius) - 2px)",
 | 
				
			||||||
 | 
					        sm: "calc(var(--radius) - 4px)",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      keyframes: {
 | 
				
			||||||
 | 
					        "accordion-down": {
 | 
				
			||||||
 | 
					          from: { height: "0" },
 | 
				
			||||||
 | 
					          to: { height: "var(--radix-accordion-content-height)" },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "accordion-up": {
 | 
				
			||||||
 | 
					          from: { height: "var(--radix-accordion-content-height)" },
 | 
				
			||||||
 | 
					          to: { height: "0" },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      animation: {
 | 
				
			||||||
 | 
					        "accordion-down": "accordion-down 0.2s ease-out",
 | 
				
			||||||
 | 
					        "accordion-up": "accordion-up 0.2s ease-out",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [],
 | 
					  plugins: [require("tailwindcss-animate")],
 | 
				
			||||||
};
 | 
					} satisfies Config
 | 
				
			||||||
export default config;
 | 
					
 | 
				
			||||||
 | 
					export default config
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@
 | 
				
			||||||
    "moduleResolution": "bundler",
 | 
					    "moduleResolution": "bundler",
 | 
				
			||||||
    "resolveJsonModule": true,
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
    "isolatedModules": true,
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "target": "ESNext",
 | 
				
			||||||
    "jsx": "preserve",
 | 
					    "jsx": "preserve",
 | 
				
			||||||
    "incremental": true,
 | 
					    "incremental": true,
 | 
				
			||||||
    "plugins": [
 | 
					    "plugins": [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue