diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal.tsx b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal.tsx index 3e48053..21550eb 100644 --- a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal.tsx +++ b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal.tsx @@ -2,12 +2,13 @@ import { useRouter } from "next/navigation"; import { Button, Header, Icon } from "@compasser/design-system"; -import type { StoreDetailItem, StoreMenuItem } from "../../../_types/store-detail"; +import type { StoreRespDTO, StoreRandomBoxRespDTO } from "@compasser/api"; interface PurchaseCompleteModalProps { isOpen: boolean; - store: StoreDetailItem; - menu: StoreMenuItem; + store: StoreRespDTO; + menu: StoreRandomBoxRespDTO; + pickupTimeText: string; onClose?: () => void; } @@ -17,6 +18,7 @@ export default function PurchaseCompleteModal({ isOpen, store, menu, + pickupTimeText, }: PurchaseCompleteModalProps) { const router = useRouter(); @@ -74,10 +76,10 @@ export default function PurchaseCompleteModal({
-

{menu.name}

+

{menu.boxName}

- 픽업시간: {menu.pickupStartTime} ~ {menu.pickupEndTime} + 픽업시간: {pickupTimeText}

diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseContent.tsx b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseContent.tsx index 24b8b7f..28b56fa 100644 --- a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseContent.tsx +++ b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseContent.tsx @@ -3,16 +3,19 @@ import { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { Button, Header } from "@compasser/design-system"; -import type { StoreDetailItem, StoreMenuItem } from "../../../_types/store-detail"; +import type { JsonValue, StoreRespDTO, StoreRandomBoxRespDTO } from "@compasser/api"; import PurchaseGuideModal from "./PurchaseGuideModal"; import PurchaseInfoSection from "./PurchaseInfoSection"; import PurchaseNoticeCard from "./PurchaseNoticeCard"; import PurchaseOrderCard from "./PurchaseOrderCard"; import PurchaseCompleteModal from "./PurchaseCompleteModal"; +type DayKey = "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun"; +type BusinessHoursValue = Partial>; + interface PurchaseContentProps { - store: StoreDetailItem; - menu: StoreMenuItem; + store: StoreRespDTO; + menu: StoreRandomBoxRespDTO; } const ACCOUNT_INFO = { @@ -27,6 +30,56 @@ const NOTICE_LIST = [ "픽업 시간에 맞춰 상품을 수령해주세요.", ]; +const DAY_KEYS: DayKey[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; +const DAY_LABELS: Record = { + mon: "월", + tue: "화", + wed: "수", + thu: "목", + fri: "금", + sat: "토", + sun: "일", +}; + +function parseBusinessHours(value?: JsonValue): BusinessHoursValue | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return undefined; + } + + const record = value as Record; + const result: BusinessHoursValue = {}; + + const keys: DayKey[] = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]; + + for (const key of keys) { + const item = record[key]; + if (typeof item === "string") { + result[key] = item; + } + } + + return result; +} + +function getTodayPickupTimeText(businessHours?: BusinessHoursValue): string { + if (!businessHours) { + return "운영시간 정보 없음"; + } + + const today = DAY_KEYS[new Date().getDay()]; + const todayValue = businessHours[today]; + + if (!todayValue) { + return "운영시간 정보 없음"; + } + + if (todayValue.toLowerCase() === "closed") { + return `${DAY_LABELS[today]} 휴무`; + } + + return `${DAY_LABELS[today]} ${todayValue}`; +} + export default function PurchaseContent({ store, menu, @@ -38,12 +91,17 @@ export default function PurchaseContent({ const totalPrice = useMemo(() => menu.price * count, [menu.price, count]); + const pickupTimeText = useMemo(() => { + const businessHours = parseBusinessHours(store.businessHours); + return getTodayPickupTimeText(businessHours); + }, [store.businessHours]); + const handleDecrease = () => { setCount((prev) => Math.max(prev - 1, 0)); }; const handleIncrease = () => { - setCount((prev) => Math.min(prev + 1, menu.remainingCount)); + setCount((prev) => Math.min(prev + 1, menu.stock)); }; const handleOpenGuideModal = () => { @@ -78,6 +136,7 @@ export default function PurchaseContent({ diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseItemCard.tsx b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseItemCard.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseOrderCard.tsx b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseOrderCard.tsx index 51e8126..b27f032 100644 --- a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseOrderCard.tsx +++ b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseOrderCard.tsx @@ -1,11 +1,12 @@ "use client"; import { Card, Icon } from "@compasser/design-system"; -import type { StoreDetailItem, StoreMenuItem } from "../../../_types/store-detail"; +import type { StoreRespDTO, StoreRandomBoxRespDTO } from "@compasser/api"; interface PurchaseOrderCardProps { - store: StoreDetailItem; - menu: StoreMenuItem; + store: StoreRespDTO; + menu: StoreRandomBoxRespDTO; + pickupTimeText?: string; count: number; totalPrice: number; onDecrease: () => void; @@ -17,6 +18,7 @@ const formatPrice = (price: number) => `${price.toLocaleString()}원`; export default function PurchaseOrderCard({ store, menu, + pickupTimeText, count, totalPrice, onDecrease, @@ -46,11 +48,13 @@ export default function PurchaseOrderCard({

-

{menu.name}

+

{menu.boxName}

-

- 픽업시간: {menu.pickupStartTime} ~ {menu.pickupEndTime} -

+ {pickupTimeText ? ( +

+ 픽업시간: {pickupTimeText} +

+ ) : null}
@@ -75,7 +79,7 @@ export default function PurchaseOrderCard({ onClick={onIncrease} className="ml-[0.6rem] flex h-[2.8rem] w-[2.8rem] items-center justify-center" aria-label="수량 증가" - disabled={count >= menu.remainingCount} + disabled={count >= menu.stock} > item.id === selectedMenuId); + const menu = store.randomBoxes.find( + (item) => item.boxId === selectedMenuId, + ); if (!menu) { notFound(); diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/purchase/utils/business-hours.ts b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/utils/business-hours.ts new file mode 100644 index 0000000..04b835e --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/[id]/purchase/utils/business-hours.ts @@ -0,0 +1,26 @@ +import type { JsonValue } from "@compasser/api"; +import type { DayKey } from "../../../_types/store-detail"; + +export type BusinessHoursValue = Partial>; + +const DAY_KEYS: DayKey[] = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]; + +export function parseBusinessHours( + value?: JsonValue, +): BusinessHoursValue | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return undefined; + } + + const record = value as Record; + const result: BusinessHoursValue = {}; + + for (const key of DAY_KEYS) { + const item = record[key]; + if (typeof item === "string") { + result[key] = item; + } + } + + return result; +} \ No newline at end of file diff --git a/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx b/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx index 2712c95..08a440f 100644 --- a/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx +++ b/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx @@ -225,8 +225,9 @@ export default function StoreDetailContent({ void; } @@ -12,6 +13,7 @@ const formatPrice = (price: number) => `${price.toLocaleString()}원`; export default function StoreMenuCard({ item, + pickupTimeText, onClick, }: StoreMenuCardProps) { return ( @@ -27,15 +29,17 @@ export default function StoreMenuCard({
-

{item.name}

+

{item.boxName}

- 잔여개수 {item.remainingCount}개 + 잔여개수 {item.stock}개

-

- 픽업시간: {item.pickupStartTime} ~ {item.pickupEndTime} -

+ {pickupTimeText ? ( +

+ 픽업시간: {pickupTimeText} +

+ ) : null}
diff --git a/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts b/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts deleted file mode 100644 index 45bd561..0000000 --- a/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts +++ /dev/null @@ -1,360 +0,0 @@ -import type { DayKey, StoreBusinessHour, StoreDetailItem } from "../_types/store-detail"; - -const createWeeklyHours = ( - defaultOpen: string, - defaultClose: string, - overrides?: Partial>, -): Record => ({ - mon: { open: defaultOpen, close: defaultClose }, - tue: { open: defaultOpen, close: defaultClose }, - wed: { open: defaultOpen, close: defaultClose }, - thu: { open: defaultOpen, close: defaultClose }, - fri: { open: defaultOpen, close: defaultClose }, - sat: { open: defaultOpen, close: defaultClose }, - sun: { open: defaultOpen, close: defaultClose }, - ...overrides, -}); - -export const MOCK_MAIN_STORE_DETAIL_LIST: StoreDetailItem[] = [ - { - id: 1, - storeName: "가톨릭대 정문 브레드하우스", - roadAddress: "경기 부천시 지봉로 43", - lotAddress: "경기 부천시 역곡동 543-1", - email: "breadhouse@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("09:00", "18:00", { - sat: { open: "09:00", close: "17:00" }, - sun: { open: "10:00", close: "17:00" }, - }), - menus: [ - { - id: 101, - name: "딸기 생크림 박스", - remainingCount: 3, - pickupStartTime: "19:00", - pickupEndTime: "21:00", - originalPrice: 12000, - price: 5500, - imageUrl: "/images/mock/menu/menu-1-1.jpg", - }, - { - id: 102, - name: "소금빵 랜덤박스", - remainingCount: 2, - pickupStartTime: "19:30", - pickupEndTime: "21:00", - originalPrice: 11000, - price: 5000, - imageUrl: "/images/mock/menu/menu-1-2.jpg", - }, - { - id: 103, - name: "크루아상 4종 세트", - remainingCount: 1, - pickupStartTime: "18:30", - pickupEndTime: "20:00", - originalPrice: 15000, - price: 7000, - imageUrl: "/images/mock/menu/menu-1-3.jpg", - }, - ], - }, - { - id: 2, - storeName: "부천역 카페 오븐데이", - roadAddress: "경기 부천시 부일로 460", - lotAddress: "경기 부천시 심곡동 221-9", - email: "ovenday@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("10:00", "20:00", { - sun: { open: "11:00", close: "19:00" }, - }), - menus: [ - { - id: 201, - name: "수제 쿠키 랜덤박스", - remainingCount: 3, - pickupStartTime: "18:30", - pickupEndTime: "20:30", - originalPrice: 9000, - price: 4000, - imageUrl: "/images/mock/menu/menu-2-1.jpg", - }, - { - id: 202, - name: "스콘 디저트팩", - remainingCount: 2, - pickupStartTime: "19:00", - pickupEndTime: "20:30", - originalPrice: 10000, - price: 4500, - imageUrl: "/images/mock/menu/menu-2-2.jpg", - }, - ], - }, - { - id: 3, - storeName: "역곡 브런치살롱", - roadAddress: "경기 부천시 역곡로 8", - lotAddress: "경기 부천시 역곡동 112-4", - email: "brunchsalon@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("09:30", "19:30"), - menus: [ - { - id: 301, - name: "샌드위치 브런치팩", - remainingCount: 4, - pickupStartTime: "10:00", - pickupEndTime: "13:00", - originalPrice: 13500, - price: 6900, - imageUrl: "/images/mock/menu/menu-3-1.jpg", - }, - { - id: 302, - name: "샐러드 브런치 세트", - remainingCount: 2, - pickupStartTime: "11:00", - pickupEndTime: "13:30", - originalPrice: 14000, - price: 7500, - imageUrl: "/images/mock/menu/menu-3-2.jpg", - }, - ], - }, - { - id: 4, - storeName: "역곡역 앞 솔트베이크", - roadAddress: "경기 부천시 역곡로 3", - lotAddress: "경기 부천시 역곡동 98-7", - email: "saltbake@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("08:30", "19:30"), - menus: [ - { - id: 401, - name: "소금빵 4종 박스", - remainingCount: 3, - pickupStartTime: "17:00", - pickupEndTime: "20:00", - originalPrice: 13000, - price: 6000, - imageUrl: "/images/mock/menu/menu-4-1.jpg", - }, - { - id: 402, - name: "베이커리 랜덤팩", - remainingCount: 1, - pickupStartTime: "18:00", - pickupEndTime: "19:30", - originalPrice: 14000, - price: 6500, - imageUrl: "/images/mock/menu/menu-4-2.jpg", - }, - ], - }, - { - id: 5, - storeName: "가톨릭대 후문 모어커피", - roadAddress: "경기 부천시 지봉로 50", - lotAddress: "경기 부천시 역곡동 601-12", - email: "morecoffee@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("09:00", "22:00"), - menus: [ - { - id: 501, - name: "오늘의 디저트팩", - remainingCount: 5, - pickupStartTime: "20:00", - pickupEndTime: "22:00", - originalPrice: 9500, - price: 4500, - imageUrl: "/images/mock/menu/menu-5-1.jpg", - }, - { - id: 502, - name: "쿠키+음료 세트", - remainingCount: 2, - pickupStartTime: "19:30", - pickupEndTime: "21:30", - originalPrice: 11000, - price: 5200, - imageUrl: "/images/mock/menu/menu-5-2.jpg", - }, - ], - }, - { - id: 6, - storeName: "부천 디저트랩", - roadAddress: "경기 부천시 부일로 448", - lotAddress: "경기 부천시 심곡동 203-15", - email: "dessertlab@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("12:00", "21:00"), - menus: [ - { - id: 601, - name: "마카롱 혼합 박스", - remainingCount: 2, - pickupStartTime: "18:00", - pickupEndTime: "21:00", - originalPrice: 15000, - price: 7000, - imageUrl: "/images/mock/menu/menu-6-1.jpg", - }, - { - id: 602, - name: "케이크 조각 세트", - remainingCount: 1, - pickupStartTime: "19:00", - pickupEndTime: "20:30", - originalPrice: 16000, - price: 7500, - imageUrl: "/images/mock/menu/menu-6-2.jpg", - }, - ], - }, - { - id: 7, - storeName: "역곡 한끼식당", - roadAddress: "경기 부천시 역곡로 22", - lotAddress: "경기 부천시 역곡동 231-6", - email: "hankki@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("10:30", "20:30"), - menus: [ - { - id: 701, - name: "한식 도시락 세트", - remainingCount: 4, - pickupStartTime: "13:00", - pickupEndTime: "15:00", - originalPrice: 12000, - price: 6500, - imageUrl: "/images/mock/menu/menu-7-1.jpg", - }, - { - id: 702, - name: "반찬 랜덤팩", - remainingCount: 2, - pickupStartTime: "18:30", - pickupEndTime: "20:00", - originalPrice: 10000, - price: 5000, - imageUrl: "/images/mock/menu/menu-7-2.jpg", - }, - ], - }, - { - id: 8, - storeName: "가톨릭대 베이글스팟", - roadAddress: "경기 부천시 지봉로 59", - lotAddress: "경기 부천시 역곡동 614-8", - email: "bagelspot@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("08:00", "17:00", { - sun: { open: "09:00", close: "16:00" }, - }), - menus: [ - { - id: 801, - name: "베이글 랜덤팩", - remainingCount: 3, - pickupStartTime: "16:00", - pickupEndTime: "19:00", - originalPrice: 11000, - price: 5200, - imageUrl: "/images/mock/menu/menu-8-1.jpg", - }, - { - id: 802, - name: "크림치즈 베이글 세트", - remainingCount: 2, - pickupStartTime: "15:30", - pickupEndTime: "17:30", - originalPrice: 12500, - price: 5800, - imageUrl: "/images/mock/menu/menu-8-2.jpg", - }, - ], - }, - { - id: 9, - storeName: "부천 로컬키친", - roadAddress: "경기 부천시 부일로 472", - lotAddress: "경기 부천시 심곡동 227-2", - email: "localkitchen@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("11:00", "21:00"), - menus: [ - { - id: 901, - name: "파스타 투고 박스", - remainingCount: 2, - pickupStartTime: "11:30", - pickupEndTime: "14:00", - originalPrice: 15000, - price: 7900, - imageUrl: "/images/mock/menu/menu-9-1.jpg", - }, - { - id: 902, - name: "리조또 데일리팩", - remainingCount: 3, - pickupStartTime: "12:00", - pickupEndTime: "14:30", - originalPrice: 14500, - price: 7600, - imageUrl: "/images/mock/menu/menu-9-2.jpg", - }, - ], - }, - { - id: 10, - storeName: "역곡 스윗테이블", - roadAddress: "경기 부천시 역곡로 15", - lotAddress: "경기 부천시 역곡동 142-10", - email: "sweettable@gmail.com", - thumbnailImageUrl: "/images/mock/store/store-1.jpg", - businessHours: createWeeklyHours("10:00", "22:00"), - menus: [ - { - id: 1001, - name: "조각케이크 랜덤팩", - remainingCount: 3, - pickupStartTime: "19:30", - pickupEndTime: "21:30", - originalPrice: 13000, - price: 6000, - imageUrl: "/images/mock/menu/menu-10-1.jpg", - }, - { - id: 1002, - name: "디저트 박스 Level.2", - remainingCount: 1, - pickupStartTime: "20:00", - pickupEndTime: "21:30", - originalPrice: 16000, - price: 8000, - imageUrl: "/images/mock/menu/menu-10-2.jpg", - }, - { - id: 1003, - name: "쿠키&케이크 세트", - remainingCount: 2, - pickupStartTime: "19:00", - pickupEndTime: "22:00", - originalPrice: 15000, - price: 7200, - imageUrl: "/images/mock/menu/menu-10-3.jpg", - }, - ], - }, -]; - -export const MOCK_MAIN_STORE_DETAIL_MAP = Object.fromEntries( - MOCK_MAIN_STORE_DETAIL_LIST.map((store) => [store.id, store]), -) as Record; \ No newline at end of file diff --git a/apps/customer/tsconfig.json b/apps/customer/tsconfig.json index 0f798ce..0d92a6f 100644 --- a/apps/customer/tsconfig.json +++ b/apps/customer/tsconfig.json @@ -1,9 +1,8 @@ { "extends": "@compasser/typescript-config/next.json", "compilerOptions": { - "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] } }, "include": [ diff --git a/apps/owner/tsconfig.json b/apps/owner/tsconfig.json index 0f798ce..0d92a6f 100644 --- a/apps/owner/tsconfig.json +++ b/apps/owner/tsconfig.json @@ -1,9 +1,8 @@ { "extends": "@compasser/typescript-config/next.json", "compilerOptions": { - "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] } }, "include": [ diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 6293d6f..e8a474c 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "@compasser/typescript-config/base.json", - "compilerOptions": { - "baseUrl": "." - }, + "compilerOptions": {}, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] -} +} \ No newline at end of file