@@ -61,4 +65,4 @@ const halfStarEmptyStyle = (size: number) => css`
height: ${size}px;
position: absolute;
clip-path: inset(0 0 0 50%); // 오른쪽 절반만 표시
-`;
\ No newline at end of file
+`;
diff --git a/src/pages/Signin/index.tsx b/src/pages/Signin/index.tsx
index 4bb78d0..28fe4cb 100644
--- a/src/pages/Signin/index.tsx
+++ b/src/pages/Signin/index.tsx
@@ -1,15 +1,15 @@
/** @jsxImportSource @emotion/react */
-import { css } from "@emotion/react";
-import colors from "styles/color";
+import { css } from '@emotion/react';
+import colors from 'styles/color';
-import { useForm } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
-import { type SubmitHandler } from "react-hook-form";
+import { type SubmitHandler } from 'react-hook-form';
-import * as z from "zod";
-import { loginSchema } from "schema";
-import { Input } from "common/components";
+import * as z from 'zod';
+import { loginSchema } from 'schema';
+import { Input } from 'common/components';
type SignInFormType = z.infer
;
@@ -23,8 +23,8 @@ const SignIn = () => {
resolver: zodResolver(loginSchema),
});
- const onSubmit: SubmitHandler = (data) => {
- console.log(data);
+ const onSubmit: SubmitHandler = () => {
+ // console.log(data);
reset();
};
@@ -36,7 +36,7 @@ const SignIn = () => {
css={_input}
type="text"
placeholder="Email"
- {...register("accountId")}
+ {...register('accountId')}
/>
{errors.accountId && {errors.accountId.message}
}
@@ -44,7 +44,7 @@ const SignIn = () => {
css={_input}
type="password"
placeholder="Password"
- {...register("password")}
+ {...register('password')}
/>
{errors.password && {errors.password.message}
}
@@ -63,7 +63,7 @@ const _container = css`
justify-content: center;
align-items: center;
height: 100vh;
- background: url("/src/assets/login.jpg") no-repeat center center;
+ background: url('/src/assets/login.jpg') no-repeat center center;
background-size: cover;
@media (max-width: 768px) {
diff --git a/src/pages/dashboard/AdminHome.tsx b/src/pages/dashboard/AdminHome.tsx
new file mode 100644
index 0000000..4d0f47d
--- /dev/null
+++ b/src/pages/dashboard/AdminHome.tsx
@@ -0,0 +1,9 @@
+const AdminHome = () => {
+ return (
+
+ );
+};
+
+export default AdminHome;
diff --git a/src/pages/dashboard/OwnerHome.tsx b/src/pages/dashboard/OwnerHome.tsx
new file mode 100644
index 0000000..ead639e
--- /dev/null
+++ b/src/pages/dashboard/OwnerHome.tsx
@@ -0,0 +1,39 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+
+import CardList from './components/CardList';
+import StoreInfo from './components/StoreInfo';
+import { useGetStoresQuery } from 'queries/modules/stores/useStoresQuery';
+import { CARD_LIST_ARR } from './constants';
+import OrderTable from './components/OrderTable';
+
+const OwnerHome = () => {
+ const {
+ data: { response },
+ } = useGetStoresQuery(1);
+
+ const storeData = response;
+
+ const { name, contactNumber, openTime, closeTime } = storeData;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default OwnerHome;
+
+const _ownerContainer = css`
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+`;
diff --git a/src/pages/dashboard/components/Card.tsx b/src/pages/dashboard/components/Card.tsx
new file mode 100644
index 0000000..e5fb845
--- /dev/null
+++ b/src/pages/dashboard/components/Card.tsx
@@ -0,0 +1,77 @@
+/** @jsxImportSource @emotion/react */
+
+import { css } from '@emotion/react';
+import colors from 'styles/color';
+import fonts from 'styles/font';
+
+type CardProps = {
+ icon?: React.FunctionComponent>;
+ title: string;
+ total?: number | string; // 총 산출 - 추후 백엔드 요청
+ due?: number; // 산출 기간 - 추후 백엔드 요청
+ width?: number;
+ height?: number;
+ titleText?: boolean;
+ border?: boolean;
+};
+
+const Card = ({
+ icon: Icon,
+ total,
+ title,
+ due,
+ width = 339,
+ height = 168,
+ titleText = false,
+ border = false,
+}: CardProps) => {
+ return (
+
+ {Icon &&
}
+
+
+
{total ?? total}
+
{title ?? title}
+
{due ? `(${due} days)` : null}
+
+
+ );
+};
+
+export default Card;
+
+const _container = (width: number, height: number, border: boolean) => css`
+ display: flex;
+ align-items: center;
+ width: ${width}px;
+ height: ${height}px;
+ background-color: ${colors.white};
+ border-radius: 12px;
+ border: ${border ? `1px solid ${colors.lightGray}` : null};
+ padding: 32px;
+ gap: 28px;
+`;
+
+const _icon = css`
+ width: 84px;
+ height: 84px;
+`;
+
+const _totalTxt = css`
+ font-size: 32px;
+ font-weight: 800;
+`;
+
+const _titleTxt = (titleText: boolean) => [
+ css`
+ color: ${titleText ? colors.textThird : null};
+ `,
+ fonts['14_500'],
+];
+
+const _dueTxt = [
+ css`
+ color: ${colors.textThird};
+ `,
+ fonts['12_400'],
+];
diff --git a/src/pages/dashboard/components/CardList.tsx b/src/pages/dashboard/components/CardList.tsx
new file mode 100644
index 0000000..5f133d1
--- /dev/null
+++ b/src/pages/dashboard/components/CardList.tsx
@@ -0,0 +1,20 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+
+const CardList = ({ cardListArr }: { cardListArr: React.ReactNode[] }) => {
+ return (
+
+ {cardListArr.map((card, idx) => (
+
{card}
+ ))}
+
+ );
+};
+
+export default CardList;
+
+const _container = css`
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px;
+`;
diff --git a/src/pages/dashboard/components/OptionMenu.tsx b/src/pages/dashboard/components/OptionMenu.tsx
new file mode 100644
index 0000000..e68ab57
--- /dev/null
+++ b/src/pages/dashboard/components/OptionMenu.tsx
@@ -0,0 +1,81 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+
+import { CheckCircle, XCircle } from 'common/components/icons';
+import colors from 'styles/color';
+
+import type { OptionMenuProps } from '../types';
+
+const OptionMenu = ({
+ successTxt = 'open',
+ cancelTxt = 'close',
+ open,
+ setOpen,
+ setIsSelected,
+}: OptionMenuProps) => {
+ const handleOpen = () => {
+ setOpen(true);
+ setIsSelected(true);
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ setIsSelected(true);
+ };
+
+ return (
+
+
+
+ {successTxt}
+
+
+
+
+ {cancelTxt}
+
+
+ );
+};
+
+export default OptionMenu;
+
+const _container = css`
+ display: flex;
+ flex-direction: column;
+ width: 212px;
+ height: 120px;
+ background-color: ${colors.white};
+ border-radius: 8px;
+ padding: 24px;
+ gap: 16px;
+
+ position: absolute; /* position을 absolute로 설정 */
+ left: 100%; /* 버튼의 오른쪽 외부에 배치 */
+ bottom: 50%;
+ z-index: 100; /* 다른 요소보다 위에 오도록 설정 */
+ margin-inline-start: 16px; /* 왼쪽 여백 추가 */
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); /* 그림자 추가 */
+`;
+
+const _icoTxt = css`
+ display: flex;
+ align-items: center;
+ gap: 12px;
+`;
+
+const _txt = (open: boolean) => css`
+ color: ${open ? colors.primary : null};
+
+ &:hover {
+ cursor: pointer;
+ }
+`;
+
+const _closeTxt = (open: boolean) => css`
+ color: ${open ? null : colors.primary};
+
+ &:hover {
+ cursor: pointer;
+ }
+`;
diff --git a/src/pages/dashboard/components/OrderTable.tsx b/src/pages/dashboard/components/OrderTable.tsx
new file mode 100644
index 0000000..58adb4d
--- /dev/null
+++ b/src/pages/dashboard/components/OrderTable.tsx
@@ -0,0 +1,242 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+import styled from '@emotion/styled';
+
+import fonts from 'styles/font';
+
+import {
+ ColumnDef,
+ getCoreRowModel,
+ useReactTable,
+ flexRender,
+ getSortedRowModel, // 정렬된 행 모델을 가져오는 함수
+ SortingState, // 정렬 상태 타입
+} from '@tanstack/react-table';
+import { useGetStoresOrders } from 'queries/modules/stores/useStoresQuery';
+import { Order } from 'api/modules/stores/types';
+
+import get from 'lodash/get';
+import colors from 'styles/color';
+import { useMemo, useState } from 'react';
+import { formatToKoreanCurrency } from 'utils/formatTokoCurrency';
+import { UpdownArrow } from 'common/components/icons';
+import { PAGE_SIZE } from '../constants';
+import { cloneDeep } from 'lodash';
+
+/**
+ * TODO:
+ * 시간관계상 Table을 공통 컴포넌트로 분리하지 않고, OwnerHome 컴포넌트에 직접 구현
+ * 추후 Table 컴포넌트로 분리하여 재사용성을 높일 예정
+ */
+
+const OrderTable = () => {
+ const { data } = useGetStoresOrders(1);
+ const orderData: Order[] = get(data, 'response.orders', []);
+
+ // 정렬 상태 관리
+ const [sorting, setSorting] = useState([]);
+
+ // 페이지 네이션
+ const [pageIndex, setPageIndex] = useState(0); // 현재 페이지
+
+ const columns: ColumnDef[] = useMemo(
+ () => [
+ {
+ header: 'OrderID',
+ accessorKey: 'orderId',
+ enableSorting: true,
+ },
+ {
+ header: 'storeName',
+ accessorKey: 'storeName',
+ enableSorting: true,
+ },
+ {
+ header: 'ordererName',
+ accessorKey: 'ordererName',
+ enableSorting: true,
+ },
+ {
+ header: 'menutotalPrice',
+ accessorKey: 'menutotalPrice',
+ accessorFn: (data) => formatToKoreanCurrency(data.menutotalPrice),
+ enableSorting: true,
+ },
+ {
+ header: 'status',
+ accessorKey: 'status',
+ enableSorting: true,
+ },
+ ],
+ [],
+ );
+
+ const currentPageData = useMemo(() => {
+ const start = pageIndex * PAGE_SIZE;
+ const end = start + PAGE_SIZE;
+ const orderTableData = cloneDeep(orderData);
+
+ return orderTableData.slice(start, end);
+ }, [orderData, pageIndex]);
+
+ // table hooks 사용
+ const table = useReactTable({
+ data: currentPageData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting,
+ },
+ onSortingChange: setSorting, // 정렬 상태 변경 핸들러
+ });
+
+ return (
+ <>
+ New Order
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ {
+ header.column.getIsSorted() ? (
+ header.column.getIsSorted() === 'asc' ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ ) // 정렬되지 않은 상태의 기본 아이콘
+ }
+
+
+ |
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ |
+ ))}
+
+ ))}
+
+
+ {/* 페이지네이션 컨트롤 */}
+
+
+
+ {`${pageIndex + 1} / ${Math.ceil(orderData.length / PAGE_SIZE)} 페이지`}
+
+
+
+ >
+ );
+};
+
+const StyledArrow = styled(UpdownArrow)<{ state: 'none' | 'up' | 'down' }>`
+ width: 18px;
+ height: 18px;
+
+ /* 위쪽 화살표 색상 변경 */
+ path:first-of-type {
+ fill: ${({ state }) => (state === 'up' ? colors.textMuted : '#d3d3e0')};
+ }
+
+ /* 아래쪽 화살표 색상 변경 */
+ path:last-of-type {
+ fill: ${({ state }) => (state === 'down' ? colors.textMuted : '#d3d3e0')};
+ }
+`;
+
+const Arrow = ({ clicked }: { clicked: 'none' | 'up' | 'down' }) => {
+ return ;
+};
+
+export default OrderTable;
+
+const _mainTxt = css`
+ ${fonts['24_800']};
+ margin-bottom: 20px;
+`;
+
+const _table = css`
+ width: 100%;
+ border-collapse: collapse;
+ background-color: ${colors.white};
+ border-radius: 12px;
+
+ th,
+ td {
+ padding: 2% 4%;
+ border-bottom: 1px solid #e9ecff;
+ }
+
+ td {
+ color: ${colors.textMuted};
+ }
+`;
+
+const _header = [css``, fonts['18_500']];
+
+const _tbody = [css``, fonts['16_400']];
+
+const _icoWrapper = css`
+ display: flex;
+ gap: 4px;
+`;
+
+const _arrowWrapper = css`
+ cursor: pointer;
+`;
+
+const _pagination = [
+ css`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+ gap: 10px;
+
+ button {
+ padding: 8px 12px;
+ background-color: ${colors.primary};
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ &:disabled {
+ background-color: ${colors.lightGray};
+ cursor: not-allowed;
+ }
+ }
+ `,
+ fonts['16_500'],
+];
diff --git a/src/pages/dashboard/components/StoreInfo.tsx b/src/pages/dashboard/components/StoreInfo.tsx
new file mode 100644
index 0000000..3f8e8d7
--- /dev/null
+++ b/src/pages/dashboard/components/StoreInfo.tsx
@@ -0,0 +1,165 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+import { Button } from 'common/components';
+
+import { useNavigate } from 'react-router-dom';
+
+import colors from 'styles/color';
+import fonts from 'styles/font';
+import CardList from './CardList';
+
+import { StoreInfoProps } from '../types';
+import { STORE_CARD_LIST_ARR } from '../constants';
+
+import { isCurrentTimeWithinRange } from 'utils/isCurrentTimeWithinRange';
+import OptionMenu from './OptionMenu';
+
+import { useEffect, useRef, useState } from 'react'; // useState 추가
+
+// StoreInfo 컴포넌트 내부 수정된 부분
+const StoreInfo = ({
+ openTime,
+ closeTime,
+ name,
+ contactNumber,
+}: StoreInfoProps) => {
+ const [open, setOpen] = useState(
+ isCurrentTimeWithinRange(openTime, closeTime),
+ ); // 현재 시간이 영업 시간인지 상태 관리
+ const [isOptionMenuVisible, setOptionMenuVisible] = useState(false); // 모달 상태 관리
+ const [isSelected, setIsSelected] = useState(false); // 옵션이 선택되었는지 상태 관리
+
+ const navigate = useNavigate();
+
+ const handleNavigate = () => {
+ navigate('/store'); // 수정 페이지로 이동
+ };
+
+ const buttonRef = useRef(null); // 버튼을 참조하기 위한 useRef
+
+ useEffect(() => {
+ if (isSelected) {
+ setOptionMenuVisible(false); // 옵션이 선택되면 모달 닫기
+ setIsSelected(false);
+ }
+ }, [isSelected]);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ buttonRef.current &&
+ !buttonRef.current.contains(event.target as Node) // 버튼이나 모달 바깥을 클릭한 경우
+ ) {
+ setOptionMenuVisible(false); // 모달 닫기
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [buttonRef]);
+
+ const handleMouseEnter = () => setOptionMenuVisible(true);
+
+ return (
+
+
+
+
가게정보
+
{name}
+
연락처 : {contactNumber}
+
+
+
+
+ {/* OptionMenu 모달 표시 제어 */}
+ {isOptionMenuVisible && (
+
+ )}
+
+
+
+
+
+ {`${openTime} ~ ${closeTime}`}
+
+ {`Manage Store Info >`}
+
+
+
+
+
+
+ );
+};
+
+export default StoreInfo;
+
+const _container = css`
+ display: flex;
+ flex-direction: column;
+ width: 717px;
+ height: 384px;
+ background-color: ${colors.white};
+ padding: 2%;
+ gap: 20px;
+`;
+
+const _mainTxt = [css``, fonts['16_600']];
+
+const btn = (open: boolean) => css`
+ background-color: ${open ? colors.green : colors.brightOrange};
+`;
+
+const storeInfo = css`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const _saleTime = css`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: ${colors.brightGreen};
+ height: 18%;
+ padding: 20px;
+ border-radius: 12px;
+`;
+
+const _saleTxt = [
+ css`
+ display: flex;
+ gap: 12px;
+ `,
+ fonts['14_600'],
+];
+
+const _manageTxt = [
+ css`
+ color: ${colors.primary};
+
+ &:hover {
+ cursor: pointer;
+ }
+ `,
+ fonts['14_500'],
+];
+
+const _contactNumber = css`
+ color: ${colors.textThird};
+`;
+
+const _buttonContainer = css`
+ position: relative; /* 자식 요소가 절대 위치를 기준으로 배치될 수 있도록 함 */
+ display: inline - block; /* 필요한 경우 inline-block으로 조정 */
+`;
diff --git a/src/pages/dashboard/constants/index.tsx b/src/pages/dashboard/constants/index.tsx
new file mode 100644
index 0000000..53b2c51
--- /dev/null
+++ b/src/pages/dashboard/constants/index.tsx
@@ -0,0 +1,43 @@
+import { Coffee, Dallars, Orders } from 'common/components/icons';
+import Card from '../components/Card';
+
+/**
+ * 백엔드 데이터가 없기 때문에 임시 데이터로 대체
+ */
+
+const CARD_LIST_ARR = [
+ ,
+ ,
+ ,
+];
+
+const STORE_CARD_LIST_ARR = [
+ ,
+ ,
+ ,
+];
+
+const PAGE_SIZE = 10;
+
+export { CARD_LIST_ARR, STORE_CARD_LIST_ARR, PAGE_SIZE };
diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx
index 6ebb6c4..27e3fb6 100644
--- a/src/pages/dashboard/index.tsx
+++ b/src/pages/dashboard/index.tsx
@@ -1,18 +1,27 @@
-interface Props {
- role: "admin" | "owner"; // 역할 타입 정의, enum으로 빼고 API에 맞출 예정
-}
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
-const AdminHome = () => {
- return Welcome, Admin! This is your dashboard.
;
-};
-
-const OwnerHome = () => {
- return Welcome, Owner! This is your home screen.
;
-};
+import OwnerHome from './OwnerHome';
+import AdminHome from './AdminHome';
+import { DashboardProps } from './types';
+import fonts from 'styles/font';
-const Home = ({ role }: Props) => {
- if (role === "admin") return ;
- else return ;
+const Home = ({ role }: DashboardProps) => {
+ return (
+
+ );
};
export default Home;
+
+const _container = css`
+ display: flex;
+ flex-direction: column;
+ padding: 32px;
+ gap: 16px;
+`;
+
+const _mainTxt = [css``, fonts['24_800']];
diff --git a/src/pages/dashboard/types.ts b/src/pages/dashboard/types.ts
new file mode 100644
index 0000000..fb7d172
--- /dev/null
+++ b/src/pages/dashboard/types.ts
@@ -0,0 +1,18 @@
+export type DashboardProps = {
+ role: 'admin' | 'owner'; // 역할 타입 정의, enum으로 빼고 API에 맞출 예정
+};
+
+export type StoreInfoProps = {
+ name: string;
+ openTime: string;
+ closeTime: string;
+ contactNumber: string;
+};
+
+export type OptionMenuProps = {
+ successTxt?: string;
+ cancelTxt?: string;
+ open: boolean;
+ setOpen: React.Dispatch>;
+ setIsSelected: React.Dispatch>;
+};
diff --git a/src/pages/store/components/Category.tsx b/src/pages/store/components/Category.tsx
new file mode 100644
index 0000000..eaf6096
--- /dev/null
+++ b/src/pages/store/components/Category.tsx
@@ -0,0 +1,65 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+
+import { Textarea, Input } from 'common/components';
+import type { CategoryProps } from '../types';
+
+import colors from 'styles/color';
+
+const Category = ({
+ contactNumber,
+ zipCode,
+ addressDetail,
+ address,
+ // categories,
+}: CategoryProps) => {
+ const defaultAddress = `${zipCode} ${address} ${addressDetail}`;
+
+ return (
+
+
+ 카테고리
+
+
+
+
+ 연락처
+
+
+
+
+ 주소
+
+
+
+ );
+};
+
+export default Category;
+
+/**
+ * 주소
+ * 우편번호
+ * 주소
+ * 상세주소
+ */
+
+/**
+ * 연락처
+ * contactNumber
+ */
+
+const _container = css`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`;
+
+const _addressContainer = css`
+ display: flex;
+ flex-direction: column;
+`;
+
+const _labelText = css`
+ color: ${colors.textThird};
+`;
diff --git a/src/pages/store/components/EditableItem.tsx b/src/pages/store/components/EditableItem.tsx
new file mode 100644
index 0000000..fffe570
--- /dev/null
+++ b/src/pages/store/components/EditableItem.tsx
@@ -0,0 +1,127 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+import { useState } from 'react';
+
+import type { EditableItemProps, OpeningHoursAndFeesProps } from '../types';
+import colors from 'styles/color';
+import fonts from 'styles/font';
+
+const EditableItem = ({
+ label,
+ value,
+ type,
+ onChange,
+ width = '84px',
+}: EditableItemProps) => {
+ const handleChange = (e: React.ChangeEvent) => {
+ onChange(e.target.value);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+const OpeningHoursAndFees = ({
+ open,
+ close,
+ minCost,
+ delivery,
+}: OpeningHoursAndFeesProps) => {
+ const [openTime, setOpenTime] = useState(open);
+ const [closeTime, setCloseTime] = useState(close);
+ const [minOrder, setMinOrder] = useState(minCost);
+ const [deliveryFee, setDeliveryFee] = useState(delivery);
+
+ return (
+
+
+
영업 시간
+
+
+
+
+
+
최소 주문 금액 및 배달료
+
+ setMinOrder(Number(value))}
+ />
+ setDeliveryFee(value)}
+ />
+
+
+
+ );
+};
+
+export default OpeningHoursAndFees;
+
+// Emotion 스타일 정의
+const _itemContainerStyle = css`
+ display: flex;
+ width: 320px;
+ height: 70px;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background-color: ${colors.white};
+`;
+
+const _labelStyle = [
+ css`
+ color: ${colors.textPrimary};
+ `,
+ fonts['14_500'],
+];
+
+const _inputStyle = (width: string) => css`
+ width: ${width};
+ padding: 4px;
+ text-align: right;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ outline: none;
+
+ &:focus {
+ border-color: ${colors.primary};
+ }
+`;
+
+const _sectionTitleStyle = [
+ css`
+ margin-top: 20px;
+ margin-bottom: 10px;
+ color: ${colors.textThird};
+ `,
+ fonts['16_500'],
+];
diff --git a/src/pages/store/components/ImageUpload.tsx b/src/pages/store/components/ImageUpload.tsx
new file mode 100644
index 0000000..5b13153
--- /dev/null
+++ b/src/pages/store/components/ImageUpload.tsx
@@ -0,0 +1,69 @@
+/** @jsxImportSource @emotion/react */
+import { css } from '@emotion/react';
+
+import { ImageLoader } from '@pic-pik/react';
+import { useState } from 'react';
+
+import colors from 'styles/color';
+import fonts from 'styles/font';
+
+const ImageUpload = () => {
+ return ;
+};
+
+const PicPickComponent = () => {
+ const [src, setSrc] = useState(null);
+
+ return (
+ {
+ setSrc(data.src);
+ }}
+ limit={{
+ width: {
+ max: 3000,
+ onError: (error) => {
+ console.log(error);
+ },
+ },
+ height: 3000,
+ }}
+ >
+
+ {src ? (
+
![이미지]({src})
+ ) : (
+
Please upload a picture
+ )}
+
+
+ );
+};
+
+export default ImageUpload;
+
+const _container = css`
+ width: 352px;
+ height: 352px;
+ background-color: #abaaaa;
+ border-radius: 8px;
+`;
+
+const _text = [
+ css`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ color: ${colors.white};
+ `,
+ fonts['16_500'],
+];
+
+const _img = css`
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 8px;
+`;
diff --git a/src/pages/store/index.tsx b/src/pages/store/index.tsx
index b0f0e29..ebb6b34 100644
--- a/src/pages/store/index.tsx
+++ b/src/pages/store/index.tsx
@@ -1,10 +1,100 @@
/** @jsxImportSource @emotion/react */
-import { css } from "@emotion/react";
+import { css } from '@emotion/react';
+
+import { Button } from 'common/components';
+
+import { useGetStoresQuery } from 'queries/modules/stores/useStoresQuery';
+import get from 'lodash/get';
+
+import ImageUpload from './components/ImageUpload';
+import Category from './components/Category';
+import OpeningHoursAndFees from './components/EditableItem';
+import fonts from 'styles/font';
+import colors from 'styles/color';
const Store = ({ ...rest }) => {
- return ;
+ const { data } = useGetStoresQuery(1);
+
+ const storeData = get(data, 'response');
+
+ const {
+ name,
+ contactNumber,
+ zipCode,
+ addressDetail,
+ address,
+ categories,
+ openTime,
+ closeTime,
+ minimumOrderAmount,
+ } = storeData;
+
+ return (
+
+
{name}
+
+
+
+
+
+
+
+ );
};
export default Store;
-const _container = css``;
+const _container = css`
+ display: flex;
+ flex-direction: column;
+ margin: 20px;
+`;
+
+const _titleText = [
+ css`
+ color: ${colors.primary};
+ `,
+ fonts['24_800'],
+];
+
+const _bcontainer = css`
+ display: flex;
+ width: 100%;
+ gap: 10%;
+`;
+
+const _btnStyleContainer = css`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+const _contentsContainer = css`
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+`;
diff --git a/src/pages/store/types.ts b/src/pages/store/types.ts
new file mode 100644
index 0000000..895eab6
--- /dev/null
+++ b/src/pages/store/types.ts
@@ -0,0 +1,23 @@
+export type CategoryProps = {
+ contactNumber: string;
+ zipCode: string;
+ addressDetail: string;
+ address: string;
+ categories: string[];
+};
+
+export type OpeningHoursAndFeesProps = {
+ open: string;
+ close: string;
+ minCost: number;
+ delivery: string;
+};
+
+// EditableItem Props 타입 정의
+export type EditableItemProps = {
+ width?: string;
+ label: string;
+ value: string | number;
+ type: 'text' | 'number' | 'time';
+ onChange: (value: string) => void;
+};
diff --git a/src/queries/core/index.ts b/src/queries/core/index.ts
index e985404..60f9c43 100644
--- a/src/queries/core/index.ts
+++ b/src/queries/core/index.ts
@@ -14,6 +14,10 @@ export const queryClient = new QueryClient({
retry: 1, // 실패 시 한 번만 재시도
gcTime: 0, //
refetchOnWindowFocus: false,
+ throwOnError: true, // ErrorBoundary 처리를 위해 에러를 throw
+ },
+ mutations: {
+ throwOnError: true, // ErrorBoundary 처리를 위해 에러를 throw
},
},
queryCache: new QueryCache({
@@ -24,8 +28,6 @@ export const queryClient = new QueryClient({
*/
console.error(error);
},
- onSuccess: (data) => {
- console.log(data);
- }
+ onSuccess: () => {},
}),
});
diff --git a/src/queries/keys/index.ts b/src/queries/keys/index.ts
index 51207d7..f310329 100644
--- a/src/queries/keys/index.ts
+++ b/src/queries/keys/index.ts
@@ -1,38 +1,49 @@
-
/**
* query KEY 관리
* 쿼리가 많아질 시 폴더 별로 관리
*/
-import { type GetStoresMenuParams, StoresParams } from 'api/modules/stores/types';
+import { type StoresParams } from 'api/modules/stores/types';
const queryKeys = {
account: {
all: ['@/account'],
- profile: (id: number, queryParams?: Record) => [{ key: '@/account/profile', id, queryParams }],
+ profile: (id: number, queryParams?: Record) => [
+ { key: '@/account/profile', id, queryParams },
+ ],
updateProfile: (id: number) => [{ key: '@/account/profile/update', id }],
},
stores: {
all: ['@/stores'],
store: {
all: ['@/stores/store'],
- list: (storeId: number, queryParams?: GetStoresMenuParams) => [{ key: '@/stores/store/list', storeId, queryParams }],
+ list: (storeId: number, queryParams?: StoresParams) => [
+ { key: '@/stores/store/list', storeId, queryParams },
+ ],
},
menus: {
all: ['@/stores/menus'],
- list: (storeId: number, queryParams?: StoresParams) => [{ key: '@/stores/menus/list', storeId, queryParams }],
- detail: (storeId: number, menuId: number) => [{ key: '@/stores/menus/detail', storeId, menuId }],
+ list: (storeId: number, queryParams?: StoresParams) => [
+ { key: '@/stores/menus/list', storeId, queryParams },
+ ],
+ detail: (storeId: number, menuId: number) => [
+ { key: '@/stores/menus/detail', storeId, menuId },
+ ],
},
orders: {
all: ['@/stores/orders'],
list: (storeId: number) => [{ key: '@/stores/orders/list', storeId }],
- detail: (storeId: number, orderId: number) => [{ key: '@/stores/orders/detail', storeId, orderId }],
+ detail: (storeId: number, orderId: number) => [
+ { key: '@/stores/orders/detail', storeId, orderId },
+ ],
},
reviews: {
all: ['@/stores/reviews'],
- list: (storeId: number, queryParams?: StoresParams) => [{ key: '@/stores/reviews/list', storeId, queryParams }],
+ list: (storeId: number, queryParams?: StoresParams) => [
+ { key: '@/stores/reviews/list', storeId, queryParams },
+ ],
},
- }
-}
+ },
+};
export default queryKeys;
diff --git a/src/queries/modules/stores/useStoresQuery.ts b/src/queries/modules/stores/useStoresQuery.ts
index b089b22..6eeef58 100644
--- a/src/queries/modules/stores/useStoresQuery.ts
+++ b/src/queries/modules/stores/useStoresQuery.ts
@@ -1,44 +1,75 @@
-import { QueryKey, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import {
+ QueryKey,
+ useMutation,
+ useQuery,
+ useQueryClient,
+ useSuspenseQuery,
+} from '@tanstack/react-query';
import queryKeys from 'queries/keys';
import { storeAPI } from 'api/modules/stores/index';
-import { type UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
-import { type CreateMenuRequest, CreateMenuResponse, CreateStoreRequest, CreateStoreResponse, GetMenuDetailResponse, GetOrderDetailResponse, GetOrderResponse, GetReviewsParams, GetReviewsResponse, GetStoreResponse, GetStoresMenuParams, GetStoresMenuResponse, StoresParams, UpdateOrderRequest } from 'api/modules/stores/types';
+import {
+ type UseMutationOptions,
+ UseQueryOptions,
+} from '@tanstack/react-query';
+import {
+ type CreateMenuRequest,
+ CreateMenuResponse,
+ CreateStoreRequest,
+ CreateStoreResponse,
+ GetMenuDetailResponse,
+ GetOrderDetailResponse,
+ GetOrderResponse,
+ GetReviewsParams,
+ GetReviewsResponse,
+ GetStoreResponse,
+ GetStoresMenuResponse,
+ StoresParams,
+ UpdateOrderRequest,
+} from 'api/modules/stores/types';
import { type CommonResponseReturnType } from 'api/modules/commonType';
// 음식점 정보 등록 hooks
export const useCreateStoresQuery = (
- options?: UseMutationOptions, Error, CreateStoreRequest>
+ options?: UseMutationOptions<
+ CommonResponseReturnType,
+ Error,
+ CreateStoreRequest
+ >,
) => {
return useMutation({
- mutationFn: (payload: CreateStoreRequest) => storeAPI.store.createStore(payload),
- ...options
+ mutationFn: (payload: CreateStoreRequest) =>
+ storeAPI.store.createStore(payload),
+ ...options,
});
-}
+};
// 음식점 정보 조회 hooks
-export const useGetStoresQuery = (storeId: number, queryParams?: GetStoresMenuParams, options?:
- Omit, // 데이터 타입
+ QueryKey // queryKey 타입
+ >,
+ 'queryKey' | 'queryFn' // 제거할 프로퍼티
>,
- 'queryKey' | 'queryFn' // 제거할 프로퍼티
- >) => {
- return useQuery({
+) => {
+ return useSuspenseQuery({
queryKey: queryKeys.stores.store.list(storeId, queryParams),
queryFn: () => storeAPI.store.getStore(storeId, queryParams),
- enabled: !!storeId,
...options,
});
-}
+};
// 음식점 정보 삭제
export const useDeleteStoresQuery = (
- options?: UseMutationOptions
+ options?: UseMutationOptions,
) => {
const queryClient = useQueryClient();
@@ -49,66 +80,91 @@ export const useDeleteStoresQuery = (
options.onSuccess(_, storeId, context);
}
// 쿼리 무효화
- queryClient.invalidateQueries({queryKey: queryKeys.stores.store.list(storeId)});
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.stores.store.list(storeId),
+ });
},
onError: (error) => {
console.error(error);
},
...options,
});
-}
+};
// 메뉴 정보 조회 hooks
-export const useGetStoresMenuQuery = (storeId: number, queryParams?: StoresParams, options?: Omit,
+ 'queryKey' | 'queryFn' // 제거할 프로퍼티
>,
- 'queryKey' | 'queryFn' // 제거할 프로퍼티
- >
) => {
return useQuery({
queryKey: queryKeys.stores.menus.list(storeId, queryParams),
queryFn: () => storeAPI.menu.getStoresMenu(storeId, queryParams),
enabled: !!storeId,
- ...options
+ ...options,
});
-}
+};
// 메뉴 정보 상세 조회 hooks
-export const useGetStoresMenuDetailQuery = (storeId: number, menuId: number, options?: Omit,
+ 'queryKey' | 'queryFn' // 제거할 프로퍼티
>,
- 'queryKey' | 'queryFn' // 제거할 프로퍼티
- >
) => {
return useQuery({
queryKey: queryKeys.stores.menus.detail(storeId, menuId),
queryFn: () => storeAPI.menu.getStoresMenuDetail(storeId, menuId),
enabled: !!storeId,
- ...options
+ ...options,
});
-}
+};
// 메뉴 정보 추가 hooks
export const useCreateStoresMenuQuery = (
- options?: UseMutationOptions
+ options?: UseMutationOptions<
+ CreateMenuResponse,
+ Error,
+ { storeId: number; payload: CreateMenuRequest }
+ >,
) => {
const queryClient = useQueryClient();
- return useMutation({
- mutationFn: ({ storeId, payload }) => storeAPI.menu.createStoresMenu(storeId, payload),
+ return useMutation<
+ CreateMenuResponse,
+ Error,
+ { storeId: number; payload: CreateMenuRequest }
+ >({
+ mutationFn: ({ storeId, payload }) =>
+ storeAPI.menu.createStoresMenu(storeId, payload),
// 성공 시 실행할 작업
onSuccess: (data, { storeId, payload }, context) => {
if (options?.onSuccess) {
- options.onSuccess(data, { storeId: storeId, payload: payload }, context);
+ options.onSuccess(
+ data,
+ { storeId: storeId, payload: payload },
+ context,
+ );
}
- queryClient.invalidateQueries({queryKey: queryKeys.stores.menus.list(storeId)});
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.stores.menus.list(storeId),
+ });
},
// 에러 처리
@@ -120,19 +176,27 @@ export const useCreateStoresMenuQuery = (
// 메뉴 정보 삭제 hooks
export const useDeleteStoresMenuQuery = (
- options?: UseMutationOptions
+ options?: UseMutationOptions<
+ void,
+ Error,
+ { storeId: number; menuId: number }
+ >,
) => {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ storeId, menuId }: { storeId: number; menuId: number }) => storeAPI.menu.deleteStoresMenu(storeId, menuId),
+ mutationFn: ({ storeId, menuId }: { storeId: number; menuId: number }) =>
+ storeAPI.menu.deleteStoresMenu(storeId, menuId),
// 성공 시 실행할 작업
onSuccess: (data, variables, context) => {
if (options?.onSuccess) {
options.onSuccess(data, variables, context);
}
- queryClient.invalidateQueries({ queryKey: queryKeys.stores.menus.all, exact: false });
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.stores.menus.all,
+ exact: false,
+ });
},
// 에러 처리
@@ -149,21 +213,20 @@ export const useDeleteStoresMenuQuery = (
// 주문 정보 조회 hooks
export const useGetStoresOrders = (
storeId: number,
- options?:
- Omit,
+ QueryKey
+ >,
+ 'queryKey' | 'queryFn'
>,
- 'queryKey' | 'queryFn'
- >
) => {
- return useQuery({
+ return useSuspenseQuery({
queryKey: queryKeys.stores.orders.list(storeId),
queryFn: () => storeAPI.order.getStoresOrders(storeId),
- enabled: !!storeId,
- ...options
+ ...options,
});
};
@@ -171,39 +234,50 @@ export const useGetStoresOrders = (
export const useGetStoresOrderDetail = (
storeId: number,
orderId: number,
- options?:
- Omit,
- 'queryKey' | 'queryFn'
- >
+ 'queryKey' | 'queryFn'
+ >,
) => {
return useQuery({
queryKey: queryKeys.stores.orders.detail(storeId, orderId),
queryFn: () => storeAPI.order.getStoresOrderDetail(storeId, orderId),
enabled: !!storeId,
- ...options
+ ...options,
});
};
// 주문 정보 수정(취소) hooks
export const useUpdateStoresOrder = (
- options?: UseMutationOptions
+ options?: UseMutationOptions<
+ void,
+ Error,
+ { storeId: number; orderId: number; payload: UpdateOrderRequest }
+ >,
) => {
const queryClient = useQueryClient();
- return useMutation({
- mutationFn: ({ storeId, orderId, payload }) => storeAPI.order.updateStoresOrder(storeId, orderId, payload),
+ return useMutation<
+ void,
+ Error,
+ { storeId: number; orderId: number; payload: UpdateOrderRequest }
+ >({
+ mutationFn: ({ storeId, orderId, payload }) =>
+ storeAPI.order.updateStoresOrder(storeId, orderId, payload),
// 성공 시 실행할 작업
onSuccess: (_, { storeId, orderId, payload }, context) => {
if (options?.onSuccess) {
options.onSuccess(_, { storeId, orderId, payload }, context);
}
- queryClient.invalidateQueries({queryKey: queryKeys.stores.orders.list(storeId)});
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.stores.orders.list(storeId),
+ });
},
// 에러 처리
@@ -221,40 +295,45 @@ export const useUpdateStoresOrder = (
export const useGetStoresReview = (
storeId: number,
queryParams?: GetReviewsParams,
- options?:
- Omit,
- 'queryKey' | 'queryFn'
- >
+ options?: Omit<
+ UseQueryOptions,
+ 'queryKey' | 'queryFn'
+ >,
) => {
return useQuery({
queryKey: queryKeys.stores.reviews.list(storeId, queryParams),
queryFn: () => storeAPI.review.getStoresReviews(storeId, queryParams),
enabled: !!storeId,
- ...options
+ ...options,
});
-}
+};
// 리뷰 정보 삭제 hooks
export const useDeleteStoresReview = (
- options?: UseMutationOptions
+ options?: UseMutationOptions<
+ void,
+ Error,
+ { storeId: number; reviewId: number }
+ >,
) => {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ storeId, reviewId }: { storeId: number; reviewId: number }) => storeAPI.review.deleteStoresReview(storeId, reviewId),
+ mutationFn: ({
+ storeId,
+ reviewId,
+ }: {
+ storeId: number;
+ reviewId: number;
+ }) => storeAPI.review.deleteStoresReview(storeId, reviewId),
// 성공 시 실행할 작업
onSuccess: (data, variables, context) => {
- // 로그인 성공 메세지 출력
+ // 로그인 성공 메세지 출력
if (options?.onSuccess) {
options.onSuccess(data, variables, context);
}
- queryClient.invalidateQueries({queryKey: queryKeys.stores.reviews.all});
+ queryClient.invalidateQueries({ queryKey: queryKeys.stores.reviews.all });
},
// 에러 처리
@@ -264,6 +343,6 @@ export const useDeleteStoresReview = (
}
console.error(error);
},
- ...options
+ ...options,
});
};
diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx
index 927de23..bdaa85b 100644
--- a/src/routes/AppRoutes.tsx
+++ b/src/routes/AppRoutes.tsx
@@ -7,7 +7,7 @@ import MenuDetail from "pages/menuDetail";
import Profile from "pages/profile";
import Order from "pages/order";
import OrderDetail from "pages/orderDetail";
-import SignIn from "pages/signIn";
+import SignIn from "pages/signin";
import Reviews from "pages/reviews";
import Store from "pages/store";
import StoreDetail from "pages/storeDetail";
diff --git a/src/styles/color.ts b/src/styles/color.ts
index 040512c..45008ef 100644
--- a/src/styles/color.ts
+++ b/src/styles/color.ts
@@ -1,25 +1,29 @@
const colors = {
- primary: "#2F4CDD", // Primary
- secondary: "#DEE4FF", // Secondary
- accent: "#B519EC", // Accent
- textPrimary: "#000000", // Text Primary
- textSecondary: "#0F0F0F", // Text Secondary
- textMuted: "#3E4954", // Text Muted,
- textThird: "#767373",
- backgroundLight: "#F5F6FA", // Background Light
- success: "#28C76F", // Success
- danger: "#EA5455", // Danger
- warning: "#FF9F43", // Warning
- darkBackground: "#2C2E33", // Dark Background
- brightOrange: "#FF6F61", // Bright Orange
- brightRed: "#FF6D4D",
- white: "#fff", // White
- lightWhite: "#fdfdfd",
- mildWhite: "#f9f9f9",
- lightGray: "#ebebeb",
- gray: "#969BA0",
- placeholder: "#555", // Placeholder,
- icy: "#fafbff",
+ primary: '#2F4CDD', // Primary
+ secondary: '#DEE4FF', // Secondary
+ accent: '#B519EC', // Accent
+ textPrimary: '#000000', // Text Primary
+ textSecondary: '#0F0F0F', // Text Secondary
+ textMuted: '#3E4954', // Text Muted,
+ textThird: '#767373',
+ backgroundLight: '#F5F6FA', // Background Light
+ success: '#28C76F', // Success
+ danger: '#EA5455', // Danger
+ warning: '#FF9F43', // Warning
+ darkBackground: '#2C2E33', // Dark Background
+ brightOrange: '#FF6F61', // Bright Orange
+ brightRed: '#FF6D4D',
+ white: '#fff', // White
+ lightWhite: '#fdfdfd',
+ mildWhite: '#f9f9f9',
+ lightGray: '#ebebeb',
+ gray: '#969BA0',
+ placeholder: '#555', // Placeholder,
+ icy: '#fafbff',
+ brightGreen: '#E9FFEF',
+ green: '#2BC155',
+ lightYellow: '#fff1b8',
+ yellow: '#FFD700',
};
export default colors;
diff --git a/src/styles/font.ts b/src/styles/font.ts
index 49c9177..57c99bc 100644
--- a/src/styles/font.ts
+++ b/src/styles/font.ts
@@ -1,64 +1,84 @@
const fonts = {
- "40_600": {
- fontSize: "40px",
+ '40_600': {
+ fontSize: '40px',
fontWeight: 600,
},
- "36_800": {
- fontSize: "36px",
+ '36_800': {
+ fontSize: '36px',
fontWeight: 800,
},
- "32_600": {
- fontSize: "32px",
+ '32_600': {
+ fontSize: '32px',
fontWeight: 600,
},
- "28_600": {
- fontSize: "28px",
+ '28_600': {
+ fontSize: '28px',
fontWeight: 600,
},
- "24_600": {
- fontSize: "20px",
+ '24_800': {
+ fontSize: '20px',
+ fontWeight: 800,
+ },
+ '24_600': {
+ fontSize: '20px',
fontWeight: 600,
},
- "20_500": {
- fontSize: "20px",
+ '20_500': {
+ fontSize: '20px',
fontWeight: 500,
},
- "18_600": {
- fontSize: "18px",
+ '18_600': {
+ fontSize: '18px',
fontWeight: 600,
},
- "18_500": {
- fontSize: "18px",
+ '18_500': {
+ fontSize: '18px',
fontWeight: 500,
},
- "18_400": {
- fontSize: "18px",
+ '18_400': {
+ fontSize: '18px',
fontWeight: 400,
},
- "16_800": {
- fontSize: "16px",
+ '16_800': {
+ fontSize: '16px',
fontWeight: 800,
},
- "16_600": {
- fontSize: "16px",
+ '16_600': {
+ fontSize: '16px',
fontWeight: 600,
},
- "16_500": {
- fontSize: "16px",
+ '16_500': {
+ fontSize: '16px',
fontWeight: 500,
},
- "16_400": {
- fontSize: "16px",
+ '16_400': {
+ fontSize: '16px',
fontWeight: 400,
},
- "14_500": {
- fontSize: "14px",
+ '16_300': {
+ fontSize: '16px',
+ fontWeight: 300,
+ },
+ '14_500': {
+ fontSize: '14px',
+ fontWeight: 500,
+ },
+ '14_600': {
+ fontSize: '14px',
fontWeight: 500,
},
- "12_400": {
- fontSize: "12px",
+ '12_400': {
+ fontSize: '12px',
fontWeight: 400,
},
+ '12_500': {
+ fontSize: '12px',
+ fontWeight: 500,
+ },
+ '12_600': {
+ fontSize: '12px',
+ fontWeight: 600,
+ },
};
export default fonts;
diff --git a/src/utils/formatTokoCurrency.ts b/src/utils/formatTokoCurrency.ts
new file mode 100644
index 0000000..e919486
--- /dev/null
+++ b/src/utils/formatTokoCurrency.ts
@@ -0,0 +1,18 @@
+export const formatToKoreanCurrency = (
+ amount: number | string | undefined,
+): string => {
+ // 기본값 처리: amount가 undefined인 경우 "₩0" 반환
+ if (amount === undefined) return '₩0';
+
+ // 문자열을 숫자로 변환하는 로직
+ const parsedAmount =
+ typeof amount === 'string' ? parseFloat(amount.replace(/,/g, '')) : amount;
+
+ // 유효하지 않은 숫자(NaN)인 경우 "₩0" 반환
+ if (isNaN(parsedAmount)) return '₩0';
+
+ return new Intl.NumberFormat('ko-KR', {
+ style: 'currency',
+ currency: 'KRW',
+ }).format(parsedAmount);
+};
diff --git a/src/utils/getProfileImg.tsx b/src/utils/getProfileImg.tsx
index b92daa3..f68a9a5 100644
--- a/src/utils/getProfileImg.tsx
+++ b/src/utils/getProfileImg.tsx
@@ -1,4 +1,4 @@
-import { P1, P2, P3, P4 } from 'components/icons';
+import { P1, P2, P3, P4 } from 'common/components/icons';
export const getProfileImg = (profileSrc: string): JSX.Element | null => {
const profileImage: Record = {
diff --git a/src/utils/isCurrentTimeWithinRange.ts b/src/utils/isCurrentTimeWithinRange.ts
new file mode 100644
index 0000000..69365c6
--- /dev/null
+++ b/src/utils/isCurrentTimeWithinRange.ts
@@ -0,0 +1,33 @@
+import { parse, isBefore, isAfter } from 'date-fns';
+
+/**
+ * 현재 시간이 openTime과 closeTime 사이에 있는지 확인하는 함수
+ * @param {string} openTime - 오픈 시간 (예: '09:00')
+ * @param {string} closeTime - 닫는 시간 (예: '18:00')
+ * @returns {boolean} - 현재 시간이 openTime과 closeTime 사이에 있으면 true, 그렇지 않으면 false
+ *
+ * 예시 사용
+ * const result = isCurrentTimeWithinRange('09:00', '18:00');
+ * console.log(result); // 현재 시간이 09:00과 18:00 사이에 있으면 true, 그렇지 않으면 false
+ */
+export const isCurrentTimeWithinRange = (
+ openTime: string,
+ closeTime: string,
+) => {
+ // 현재 시간을 Date 객체로 구함
+ const currentTime = new Date();
+
+ // openTime과 closeTime 문자열을 'HH:mm' 형식의 Date 객체로 변환
+ const openTimeDate = parse(openTime, 'HH:mm', new Date());
+ const closeTimeDate = parse(closeTime, 'HH:mm', new Date());
+
+ // 현재 시간이 openTime보다 이전이거나 closeTime보다 이후이면 false 반환
+ if (
+ isBefore(currentTime, openTimeDate) ||
+ isAfter(currentTime, closeTimeDate)
+ ) {
+ return false;
+ }
+
+ return true;
+};