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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -17,6 +18,7 @@ export default function PurchaseCompleteModal({
isOpen,
store,
menu,
pickupTimeText,
}: PurchaseCompleteModalProps) {
const router = useRouter();

Expand Down Expand Up @@ -74,10 +76,10 @@ export default function PurchaseCompleteModal({
</div>

<div className="ml-[0.6rem] flex min-w-0 flex-1 flex-col">
<p className="body1-m text-default">{menu.name}</p>
<p className="body1-m text-default">{menu.boxName}</p>

<p className="mt-[0.2rem] body2-r text-gray-600">
픽업시간: {menu.pickupStartTime} ~ {menu.pickupEndTime}
픽업시간: {pickupTimeText}
</p>

<p className="mt-[0.2rem] body2-m text-secondary">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<DayKey, string>>;

interface PurchaseContentProps {
store: StoreDetailItem;
menu: StoreMenuItem;
store: StoreRespDTO;
menu: StoreRandomBoxRespDTO;
}

const ACCOUNT_INFO = {
Expand All @@ -27,6 +30,56 @@ const NOTICE_LIST = [
"픽업 시간에 맞춰 상품을 수령해주세요.",
];

const DAY_KEYS: DayKey[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const DAY_LABELS: Record<DayKey, string> = {
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<string, unknown>;
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,
Expand All @@ -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 = () => {
Expand Down Expand Up @@ -78,6 +136,7 @@ export default function PurchaseContent({
<PurchaseOrderCard
store={store}
menu={menu}
pickupTimeText={pickupTimeText}
count={count}
totalPrice={totalPrice}
onDecrease={handleDecrease}
Expand Down Expand Up @@ -117,6 +176,7 @@ export default function PurchaseContent({
isOpen={isCompleteModalOpen}
store={store}
menu={menu}
pickupTimeText={pickupTimeText}
onClose={handleCloseCompleteModal}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,6 +18,7 @@ const formatPrice = (price: number) => `${price.toLocaleString()}원`;
export default function PurchaseOrderCard({
store,
menu,
pickupTimeText,
count,
totalPrice,
onDecrease,
Expand Down Expand Up @@ -46,11 +48,13 @@ export default function PurchaseOrderCard({
</div>

<div className="ml-[0.6rem] flex min-w-0 flex-1 flex-col">
<p className="body1-m text-default">{menu.name}</p>
<p className="body1-m text-default">{menu.boxName}</p>

<p className="mt-[0.2rem] body2-r text-gray-600">
픽업시간: {menu.pickupStartTime} ~ {menu.pickupEndTime}
</p>
{pickupTimeText ? (
<p className="mt-[0.2rem] body2-r text-gray-600">
픽업시간: {pickupTimeText}
</p>
) : null}

<div className="mt-auto flex items-end justify-between">
<div className="flex items-center">
Expand All @@ -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}
>
<Icon
name="Plus"
Expand Down
15 changes: 11 additions & 4 deletions apps/customer/src/app/(tabs)/main/store/[id]/purchase/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { notFound } from "next/navigation";
import PurchaseContent from "./_components/PurchaseContent";
import { MOCK_MAIN_STORE_DETAIL_MAP } from "../../_constants/mockStoreDetail";
import { storeModule } from "@/shared/api/api";

interface PurchasePageProps {
params: Promise<{
Expand All @@ -21,13 +21,20 @@ export default async function PurchasePage({
const storeId = Number(id);
const selectedMenuId = Number(menuId);

const store = MOCK_MAIN_STORE_DETAIL_MAP[storeId];
if (!Number.isFinite(storeId) || !Number.isFinite(selectedMenuId)) {
notFound();
}

const response = await storeModule.requests.getStoreDetail(storeId);
const store = response.data;

if (!store || !selectedMenuId) {
if (!store) {
notFound();
}

const menu = store.menus.find((item) => item.id === selectedMenuId);
const menu = store.randomBoxes.find(
(item) => item.boxId === selectedMenuId,
);

if (!menu) {
notFound();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { JsonValue } from "@compasser/api";
import type { DayKey } from "../../../_types/store-detail";

export type BusinessHoursValue = Partial<Record<DayKey, string>>;

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<string, unknown>;
const result: BusinessHoursValue = {};

for (const key of DAY_KEYS) {
const item = record[key];
if (typeof item === "string") {
result[key] = item;
}
}

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ export default function StoreDetailContent({
<StoreMenuCard
key={menu.boxId}
item={{
id: menu.boxId,
name: menu.boxName,
boxId: menu.boxId,
storeId: store.storeId,
boxName: menu.boxName,
stock: menu.stock,
price: menu.price,
buyLimit: menu.buyLimit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
"use client";

import { Card, Icon } from "@compasser/design-system";
import type { StoreMenuItem } from "../_types/store-detail";
import type { StoreRandomBoxRespDTO } from "@compasser/api";

interface StoreMenuCardProps {
item: StoreMenuItem;
item: StoreRandomBoxRespDTO;
pickupTimeText?: string;
onClick?: () => void;
}

const formatPrice = (price: number) => `${price.toLocaleString()}원`;

export default function StoreMenuCard({
item,
pickupTimeText,
onClick,
}: StoreMenuCardProps) {
return (
Expand All @@ -27,15 +29,17 @@ export default function StoreMenuCard({
</div>

<div className="ml-[0.6rem] flex min-w-0 flex-1 flex-col">
<p className="body1-m text-default">{item.name}</p>
<p className="body1-m text-default">{item.boxName}</p>

<p className="mt-[0.2rem] body2-r text-gray-600">
잔여개수 {item.remainingCount}개
잔여개수 {item.stock}개
</p>

<p className="mt-[0.2rem] body2-r text-gray-600">
픽업시간: {item.pickupStartTime} ~ {item.pickupEndTime}
</p>
{pickupTimeText ? (
<p className="mt-[0.2rem] body2-r text-gray-600">
픽업시간: {pickupTimeText}
</p>
) : null}

<div className="mt-auto flex justify-end">
<span className="body1-m text-secondary">
Expand Down
Loading
Loading