diff --git a/.pnp.cjs b/.pnp.cjs
index 3f32b5a..c036ff2 100755
--- a/.pnp.cjs
+++ b/.pnp.cjs
@@ -63,6 +63,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["react-router-dom", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:6.23.1"],\
["react-scripts", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.0.1"],\
["react-toastify", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:10.0.5"],\
+ ["react-tooltip", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.28.0"],\
["sass", "npm:1.77.4"],\
["stylelint", "npm:14.16.1"],\
["stylelint-config-standard", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:26.0.0"],\
@@ -3879,6 +3880,36 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
+ ["@floating-ui/core", [\
+ ["npm:1.6.8", {\
+ "packageLocation": "./.yarn/cache/@floating-ui-core-npm-1.6.8-496cdfbb6e-82faa6ea9d.zip/node_modules/@floating-ui/core/",\
+ "packageDependencies": [\
+ ["@floating-ui/core", "npm:1.6.8"],\
+ ["@floating-ui/utils", "npm:0.2.8"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
+ ["@floating-ui/dom", [\
+ ["npm:1.6.11", {\
+ "packageLocation": "./.yarn/cache/@floating-ui-dom-npm-1.6.11-b81155e63e-d6413759ab.zip/node_modules/@floating-ui/dom/",\
+ "packageDependencies": [\
+ ["@floating-ui/dom", "npm:1.6.11"],\
+ ["@floating-ui/core", "npm:1.6.8"],\
+ ["@floating-ui/utils", "npm:0.2.8"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
+ ["@floating-ui/utils", [\
+ ["npm:0.2.8", {\
+ "packageLocation": "./.yarn/cache/@floating-ui-utils-npm-0.2.8-01a00634a5-deb98bba01.zip/node_modules/@floating-ui/utils/",\
+ "packageDependencies": [\
+ ["@floating-ui/utils", "npm:0.2.8"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["@hookform/resolvers", [\
["npm:3.4.2", {\
"packageLocation": "./.yarn/cache/@hookform-resolvers-npm-3.4.2-4fe00fb172-5045c3b1bf.zip/node_modules/@hookform/resolvers/",\
@@ -7771,6 +7802,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
+ ["classnames", [\
+ ["npm:2.5.1", {\
+ "packageLocation": "./.yarn/cache/classnames-npm-2.5.1-c7273f3423-da424a8a6f.zip/node_modules/classnames/",\
+ "packageDependencies": [\
+ ["classnames", "npm:2.5.1"]\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["clean-css", [\
["npm:5.3.3", {\
"packageLocation": "./.yarn/cache/clean-css-npm-5.3.3-d2bb553a94-941987c148.zip/node_modules/clean-css/",\
@@ -13393,6 +13433,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["react-router-dom", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:6.23.1"],\
["react-scripts", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.0.1"],\
["react-toastify", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:10.0.5"],\
+ ["react-tooltip", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.28.0"],\
["sass", "npm:1.77.4"],\
["stylelint", "npm:14.16.1"],\
["stylelint-config-standard", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:26.0.0"],\
@@ -17250,6 +17291,34 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
+ ["react-tooltip", [\
+ ["npm:5.28.0", {\
+ "packageLocation": "./.yarn/cache/react-tooltip-npm-5.28.0-a698d2e4f8-4d1efae0fb.zip/node_modules/react-tooltip/",\
+ "packageDependencies": [\
+ ["react-tooltip", "npm:5.28.0"]\
+ ],\
+ "linkType": "SOFT"\
+ }],\
+ ["virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.28.0", {\
+ "packageLocation": "./.yarn/__virtual__/react-tooltip-virtual-80e0e5fce3/0/cache/react-tooltip-npm-5.28.0-a698d2e4f8-4d1efae0fb.zip/node_modules/react-tooltip/",\
+ "packageDependencies": [\
+ ["react-tooltip", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:5.28.0"],\
+ ["@floating-ui/dom", "npm:1.6.11"],\
+ ["@types/react", "npm:18.3.3"],\
+ ["@types/react-dom", "npm:18.3.0"],\
+ ["classnames", "npm:2.5.1"],\
+ ["react", "npm:18.3.1"],\
+ ["react-dom", "virtual:429706adb15e35099964c7cd76f08e01c8fe72ac836e96927082266bf5c07b84ac6c3bd82481948dbf5d4de4034e4e9eed908e986f2f622051577eb971cce2dc#npm:18.3.1"]\
+ ],\
+ "packagePeers": [\
+ "@types/react-dom",\
+ "@types/react",\
+ "react-dom",\
+ "react"\
+ ],\
+ "linkType": "HARD"\
+ }]\
+ ]],\
["read-cache", [\
["npm:1.0.0", {\
"packageLocation": "./.yarn/cache/read-cache-npm-1.0.0-00fa89ed05-cffc728b9e.zip/node_modules/read-cache/",\
diff --git a/.yarn/cache/@esbuild-darwin-arm64-npm-0.20.2-e287d70c91-8.zip b/.yarn/cache/@esbuild-darwin-arm64-npm-0.20.2-e287d70c91-8.zip
deleted file mode 100644
index 56e2864..0000000
Binary files a/.yarn/cache/@esbuild-darwin-arm64-npm-0.20.2-e287d70c91-8.zip and /dev/null differ
diff --git a/.yarn/cache/@rollup-rollup-darwin-arm64-npm-4.18.0-683829554f-8.zip b/.yarn/cache/@rollup-rollup-darwin-arm64-npm-4.18.0-683829554f-8.zip
deleted file mode 100644
index 4242cc7..0000000
Binary files a/.yarn/cache/@rollup-rollup-darwin-arm64-npm-4.18.0-683829554f-8.zip and /dev/null differ
diff --git a/.yarn/cache/fsevents-patch-21ad2b1333-8.zip b/.yarn/cache/fsevents-patch-21ad2b1333-8.zip
deleted file mode 100644
index c6a96df..0000000
Binary files a/.yarn/cache/fsevents-patch-21ad2b1333-8.zip and /dev/null differ
diff --git a/package.json b/package.json
index ae79d1d..1562d90 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"react-router-dom": "^6.9.0",
"react-scripts": "5.0.1",
"react-toastify": "^10.0.4",
+ "react-tooltip": "^5.28.0",
"sass": "^1.60.0",
"typescript": "^4.4.2",
"vite": "^5.2.12",
diff --git a/src/assets/svg/auth/menu.svg b/src/assets/svg/auth/menu.svg
new file mode 100644
index 0000000..35d3e15
--- /dev/null
+++ b/src/assets/svg/auth/menu.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svg/auth/search-icon.svg b/src/assets/svg/auth/search-icon.svg
new file mode 100644
index 0000000..3a27278
--- /dev/null
+++ b/src/assets/svg/auth/search-icon.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svg/common/arrow-left.svg b/src/assets/svg/common/arrow-left.svg
new file mode 100644
index 0000000..832d4a0
--- /dev/null
+++ b/src/assets/svg/common/arrow-left.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svg/common/arrow-right.svg b/src/assets/svg/common/arrow-right.svg
new file mode 100644
index 0000000..9357fe9
--- /dev/null
+++ b/src/assets/svg/common/arrow-right.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svg/common/info.svg b/src/assets/svg/common/info.svg
new file mode 100644
index 0000000..7096992
--- /dev/null
+++ b/src/assets/svg/common/info.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svg/common/loading.svg b/src/assets/svg/common/loading.svg
new file mode 100644
index 0000000..dd281bc
--- /dev/null
+++ b/src/assets/svg/common/loading.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/index.scss b/src/index.scss
index 5803e39..92b2ca6 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -36,3 +36,7 @@ button {
ul {
list-style: none;
}
+
+input::-webkit-inner-spin-button {
+ appearance: none;
+}
diff --git a/src/layout/Header/Dropdown.module.scss b/src/layout/Header/Dropdown.module.scss
new file mode 100644
index 0000000..d1f8dce
--- /dev/null
+++ b/src/layout/Header/Dropdown.module.scss
@@ -0,0 +1,50 @@
+.container {
+ position: absolute;
+ display: flex;
+ top: 80px;
+ right: 0;
+ width: 100%;
+ background-color: #f5f5f5;
+ box-shadow: 0 5px 7px rgb(0 0 0 / 10%);
+ z-index: 10;
+ flex-direction: column;
+}
+
+.setting-item__link {
+ text-decoration: none;
+ color: #333;
+ padding: 10px;
+ text-align: left;
+ width: calc(100% - 20px);
+
+ &:hover {
+ background-color: #f0f0f0;
+ }
+}
+
+.setting-item__button {
+ padding: 10px;
+ text-align: left;
+ width: 100%;
+ background: none;
+ border: none;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f0f0f0;
+ }
+}
+
+.setting-item__word {
+ font-size: 16px;
+ height: 30px;
+ color: #1f1f1f;
+ padding-left: 20px;
+}
+
+.setting-item__warning {
+ font-size: 16px;
+ height: 30px;
+ color: #ec2d30;
+ padding-left: 20px;
+}
diff --git a/src/layout/Header/Header.module.scss b/src/layout/Header/Header.module.scss
index 8be936a..4259fb8 100644
--- a/src/layout/Header/Header.module.scss
+++ b/src/layout/Header/Header.module.scss
@@ -19,8 +19,27 @@
}
}
+.logo__icon {
+ margin-left: 24px;
+}
+
.header__content {
display: flex;
gap: 8px;
align-items: center;
}
+
+.menu-container {
+ display: flex;
+ gap: 8px;
+ margin-right: 24px;
+}
+
+.menu-icon {
+ background: none;
+}
+
+.search,
+.menu {
+ cursor: pointer;
+}
diff --git a/src/pages/Coop/components/Setting/Setting.module.scss b/src/layout/Header/HeaderNavigation/HeaderNavigation.module.scss
similarity index 91%
rename from src/pages/Coop/components/Setting/Setting.module.scss
rename to src/layout/Header/HeaderNavigation/HeaderNavigation.module.scss
index 5ae1f76..f15308b 100644
--- a/src/pages/Coop/components/Setting/Setting.module.scss
+++ b/src/layout/Header/HeaderNavigation/HeaderNavigation.module.scss
@@ -2,7 +2,7 @@
display: flex;
align-items: center;
gap: 40px;
- padding-right: 24px;
+ margin-right: 24px;
}
.setting-item__link,
diff --git a/src/pages/Coop/components/Setting/index.tsx b/src/layout/Header/HeaderNavigation/index.tsx
similarity index 84%
rename from src/pages/Coop/components/Setting/index.tsx
rename to src/layout/Header/HeaderNavigation/index.tsx
index d2895e3..f643d7c 100644
--- a/src/pages/Coop/components/Setting/index.tsx
+++ b/src/layout/Header/HeaderNavigation/index.tsx
@@ -2,9 +2,9 @@ import { Link } from 'react-router-dom';
import { useLogout } from 'query/auth';
-import styles from './Setting.module.scss';
+import styles from './HeaderNavigation.module.scss';
-function Setting1() {
+function HeaderNavigation() {
const { logout } = useLogout();
const handleLogout = () => {
@@ -23,4 +23,4 @@ function Setting1() {
);
}
-export default Setting1;
+export default HeaderNavigation;
diff --git a/src/layout/Header/MobileDropdown.tsx b/src/layout/Header/MobileDropdown.tsx
new file mode 100644
index 0000000..cb5c899
--- /dev/null
+++ b/src/layout/Header/MobileDropdown.tsx
@@ -0,0 +1,34 @@
+import { Link } from 'react-router-dom';
+
+import { useLogout } from 'query/auth';
+
+import styles from './Dropdown.module.scss';
+
+interface MobileDropdownProps {
+ view: boolean;
+}
+
+function MobileDropdown({ view }: MobileDropdownProps) {
+ const { logout } = useLogout();
+
+ const handleLogout = () => {
+ logout();
+ };
+
+ return (
+
+ {view && (
+
+
+
BCSD Lab에 문의하기
+
+
+
+ )}
+
+ );
+}
+
+export default MobileDropdown;
diff --git a/src/layout/Header/index.tsx b/src/layout/Header/index.tsx
index c7fecad..70d6234 100644
--- a/src/layout/Header/index.tsx
+++ b/src/layout/Header/index.tsx
@@ -1,16 +1,45 @@
+import { Link } from 'react-router-dom';
+
+import Menu from 'assets/svg/auth/menu.svg?react';
import Logo from 'assets/svg/common/koin-logo.svg?react';
-import Setting1 from 'pages/Coop/components/Setting';
+import useBooleanState from 'hooks/useBooleanState';
+import useMediaQuery from 'hooks/useMediaQuery';
import styles from './Header.module.scss';
+import HeaderNavigation from './HeaderNavigation';
+import MobileDropdown from './MobileDropdown';
function Header() {
+ const { isMobile } = useMediaQuery();
+ const [isOpen,,,, toggleIsOpen] = useBooleanState(false);
+
return (
-
-
+
+
);
diff --git a/src/pages/Coop/Coop.module.scss b/src/pages/Coop/Coop.module.scss
index a94fe8a..c8e2625 100644
--- a/src/pages/Coop/Coop.module.scss
+++ b/src/pages/Coop/Coop.module.scss
@@ -2,13 +2,15 @@
.container {
display: flex;
+ justify-content: center;
max-width: 989px;
width: 100%;
margin: 0 auto;
+ gap: 10px;
@include media.media-breakpoint-down(mobile) {
flex-direction: column;
- max-width: 480px; // 모바일 뷰에서는 너비 제한
+ max-width: 100%;
width: 100%;
}
}
@@ -20,17 +22,35 @@
.content {
display: flex;
- flex-grow: 2; // 2배 공간
flex-direction: column;
padding: 16px 24px;
gap: 12px;
+ width: 100%;
+
+ @include media.media-breakpoint-down(mobile) {
+ box-sizing: border-box;
+ }
}
.calendarWrapper {
- flex-grow: 1; // 1:2 비율에서 1배의 공간
- max-width: 989px;
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+
+ @include media.media-breakpoint-down(mobile) {
+ flex-direction: column;
+ max-width: 480px;
+ width: 100%;
+ }
}
.diningTypeWrapper {
- flex-grow: 2; // 1:2 비율에서 2배 공간
+ @include media.media-breakpoint-down(mobile) {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ margin: 0 auto;
+ max-width: 100%;
+ width: 100%;
+ }
}
diff --git a/src/pages/Coop/components/Calendar/Calendar.module.scss b/src/pages/Coop/components/Calendar/Calendar.module.scss
index 94fe6e8..32ef345 100644
--- a/src/pages/Coop/components/Calendar/Calendar.module.scss
+++ b/src/pages/Coop/components/Calendar/Calendar.module.scss
@@ -1,3 +1,5 @@
+@use "src/assets/styles/mediaQuery" as media;
+
.container {
display: flex;
width: 100%;
@@ -15,7 +17,7 @@
font-size: 18px;
font-style: normal;
font-weight: 400;
- line-height: 160%; /* 22.4px */
+ line-height: 160%;
}
.title--sub {
@@ -24,10 +26,15 @@
font-size: 32px;
font-style: normal;
font-weight: 700;
- line-height: 160%; /* 32px */
+ line-height: 160%;
margin-bottom: 20px;
}
+.move-wrapper {
+ display: flex;
+ justify-content: space-between;
+}
+
.button-container {
display: flex;
align-items: center;
@@ -47,7 +54,7 @@
font-size: 14px;
font-style: normal;
font-weight: 400;
- line-height: 160%; /* 22.4px */
+ line-height: 160%;
&--selected {
color: #4b4b4b;
@@ -79,7 +86,7 @@
font-size: 13px;
font-style: normal;
font-weight: 400;
- line-height: 160%; /* 20.8px */
+ line-height: 160%;
}
.date-list {
@@ -101,7 +108,7 @@
font-size: 16px;
font-style: normal;
font-weight: 500;
- line-height: 160%; /* 25.6px */
+ line-height: 160%;
background: none;
border: none;
@@ -125,3 +132,62 @@
color: #fff;
}
}
+
+.title-wrapper-mobile {
+ width: 100%;
+ margin: 8px 24px;
+
+ @include media.media-breakpoint-down(mobile) {
+ margin: 0;
+ }
+}
+
+.move-wrapper-mobile {
+ display: flex;
+ justify-content: space-between;
+ width: calc(100% - 48px);
+}
+
+.button-container-mobile {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ font-size: 14px;
+}
+
+.title--main-mobile {
+ color: #727272;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 160%;
+}
+
+.title--sub-mobile {
+ color: #000;
+ width: 273px;
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 160%;
+ margin-bottom: 20px;
+}
+
+.form-toggle-button-mobile {
+ display: flex;
+ padding: 6px 14px;
+ gap: 10px;
+ border-radius: 12px;
+ background: none;
+ color: #727272;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 160%;
+ white-space: nowrap;
+
+ &--selected {
+ color: #4b4b4b;
+ background: #eee;
+ }
+}
diff --git a/src/pages/Coop/components/Calendar/hooks/useCalendar.ts b/src/pages/Coop/components/Calendar/hooks/useCalendar.ts
index d0ffa86..96032d1 100644
--- a/src/pages/Coop/components/Calendar/hooks/useCalendar.ts
+++ b/src/pages/Coop/components/Calendar/hooks/useCalendar.ts
@@ -59,7 +59,13 @@ function useCalendar() {
return setMonth((p) => p + 1);
};
+ const goToday = () => {
+ setYear(today.getFullYear());
+ setMonth(today.getMonth() + 1);
+ };
+
return {
+ goToday,
today,
year,
month,
diff --git a/src/pages/Coop/components/Calendar/index.tsx b/src/pages/Coop/components/Calendar/index.tsx
index b258970..249ec36 100644
--- a/src/pages/Coop/components/Calendar/index.tsx
+++ b/src/pages/Coop/components/Calendar/index.tsx
@@ -1,7 +1,10 @@
import { useState } from 'react';
+import useMediaQuery from 'hooks/useMediaQuery';
import cn from 'utils/className';
+import DateMover from '../DateMover';
+
import styles from './Calendar.module.scss';
import useCalendar from './hooks/useCalendar';
@@ -20,8 +23,15 @@ interface CalendarProps {
}
export default function Calendar({ selectedDate, setSelectedDate }: CalendarProps) {
- const { dateList, isToday } = useCalendar();
+ const {
+ dateList,
+ isToday,
+ prevMonth: prevCalendarMonth,
+ nextMonth: nextCalendarMonth,
+ goToday,
+ } = useCalendar();
const [dateListFormState, setdateListFormState] = useState<'week' | 'month'>('week');
+ const { isMobile } = useMediaQuery();
const getDateList = (form: 'week' | 'month') => {
if (form === 'week') {
@@ -38,61 +48,198 @@ export default function Calendar({ selectedDate, setSelectedDate }: CalendarProp
return [];
};
+ const prevMonth = () => {
+ const prevMonthDate = new Date(selectedDate);
+ prevMonthDate.setMonth(selectedDate.getMonth() - 1);
+ prevCalendarMonth();
+ setSelectedDate(prevMonthDate);
+ };
+
+ const nextMonth = () => {
+ const nextMonthDate = new Date(selectedDate);
+ nextMonthDate.setMonth(selectedDate.getMonth() + 1);
+ nextCalendarMonth();
+ setSelectedDate(nextMonthDate);
+ };
+
+ const prevWeek = () => {
+ const prevWeekDate = new Date(selectedDate);
+ prevWeekDate.setDate(selectedDate.getDate() - WEEK);
+
+ const todayDateIndex = dateList.findIndex((date) => isSameDate(selectedDate, date));
+ const rowIndex = Math.floor(todayDateIndex / WEEK);
+
+ if (rowIndex === 0) {
+ prevCalendarMonth();
+ }
+
+ setSelectedDate(prevWeekDate);
+ };
+
+ const nextWeek = () => {
+ const prevWeekDate = new Date(selectedDate);
+ prevWeekDate.setDate(selectedDate.getDate() + WEEK);
+
+ const todayDateIndex = dateList.findIndex((date) => isSameDate(selectedDate, date));
+ const rowIndex = Math.floor(todayDateIndex / WEEK);
+
+ if (rowIndex === 5) {
+ nextCalendarMonth();
+ }
+ setSelectedDate(prevWeekDate);
+ };
+
+ const handleTodayClick = () => {
+ const today = new Date();
+ goToday();
+ setSelectedDate(today);
+ };
+
+ const getSelectedDate = () => `${selectedDate.getMonth() + 1}월 ${selectedDate.getDate()}일 ${DAYS[selectedDate.getDay()]}요일`;
+
return (
-
-
-
월간 식단
-
- {`${selectedDate.getMonth() + 1}월 ${selectedDate.getDate()}일 ${DAYS[selectedDate.getDay()]}요일`}
-
-
-
-
-
-
-
-
- {DAYS.map((day) => (
-
{day}
- ))}
+
+ {isMobile ? (
+ // 모바일 뷰
+
+
+
+
+
+
+ {dateListFormState === 'month' ? '월간 식단' : '주간 식단'}
+
+
+ {getSelectedDate()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {DAYS.map((day) => (
+
{day}
+ ))}
+
+
+ {getDateList(dateListFormState).map((date) => (
+
+ ))}
+
+
-
- {getDateList(dateListFormState).map((date) => (
-
- ))}
+ ) : (
+ // 데스크탑 뷰
+
+
+
+ {dateListFormState === 'month' ? '월간 식단' : '주간 식단'}
+
+
+ {getSelectedDate()}
+
+
+
+
+
+
+
+
+
+
+
+
+ {DAYS.map((day) => (
+
{day}
+ ))}
+
+
+ {getDateList(dateListFormState).map((date) => (
+
+ ))}
+
+
-
+
+ )}
+
);
}
diff --git a/src/pages/Coop/components/DateMover/DateMover.module.scss b/src/pages/Coop/components/DateMover/DateMover.module.scss
new file mode 100644
index 0000000..311d017
--- /dev/null
+++ b/src/pages/Coop/components/DateMover/DateMover.module.scss
@@ -0,0 +1,35 @@
+.button-container {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+
+ & > button {
+ background: none;
+ }
+}
+
+.button-prev,
+.button-next {
+ &:hover {
+ background-color: #e1e1e1;
+ border-radius: 999px;
+ cursor: pointer;
+ }
+}
+
+.button-today {
+ min-width: 48px;
+ text-align: center;
+ cursor: default;
+ color: #000;
+ font-family: Pretendard, sans-serif;
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 160%; /* 28.8px */
+
+ &:hover {
+ background-color: #e1e1e1;
+ border-radius: 999px;
+ cursor: pointer;
+ }
+}
diff --git a/src/pages/Coop/components/DateMover/index.tsx b/src/pages/Coop/components/DateMover/index.tsx
new file mode 100644
index 0000000..4137f0b
--- /dev/null
+++ b/src/pages/Coop/components/DateMover/index.tsx
@@ -0,0 +1,35 @@
+import ArrowLeftIcon from 'assets/svg/common/arrow-left.svg?react';
+import ArrowRightIcon from 'assets/svg/common/arrow-right.svg?react';
+
+import styles from './DateMover.module.scss';
+
+interface DateMoverProps {
+ onPrevClick: () => void;
+ onNextClick: () => void;
+ onTodayClick: () => void;
+}
+
+export default function DateMover({ onPrevClick, onNextClick, onTodayClick }: DateMoverProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Coop/components/DiningBlocks/DiningBlocks.module.scss b/src/pages/Coop/components/DiningBlocks/DiningBlocks.module.scss
index ff7c419..56ebe55 100644
--- a/src/pages/Coop/components/DiningBlocks/DiningBlocks.module.scss
+++ b/src/pages/Coop/components/DiningBlocks/DiningBlocks.module.scss
@@ -176,3 +176,8 @@
transform: rotate(180deg);
}
}
+
+.diet-image {
+ width: 100%;
+ -webkit-user-drag: none;
+}
diff --git a/src/pages/Coop/components/DiningBlocks/index.tsx b/src/pages/Coop/components/DiningBlocks/index.tsx
index 46b3e3c..bd43376 100644
--- a/src/pages/Coop/components/DiningBlocks/index.tsx
+++ b/src/pages/Coop/components/DiningBlocks/index.tsx
@@ -107,7 +107,7 @@ export default function DiningBlocks({ diningType, date }: DiningBlocksProps) {
className={styles['image-trigger']}
onClick={() => fileInputRefs.current[dining.id]?.click()}
>
- {dining.image_url &&
}
+ {dining.image_url &&
}
{!dining.image_url && (
diff --git a/src/pages/Coop/components/DiningDownload/DiningDownload.module.scss b/src/pages/Coop/components/DiningDownload/DiningDownload.module.scss
index 9b7807a..053ae9d 100644
--- a/src/pages/Coop/components/DiningDownload/DiningDownload.module.scss
+++ b/src/pages/Coop/components/DiningDownload/DiningDownload.module.scss
@@ -1,25 +1,16 @@
.button-container {
display: flex;
background: #fff;
- max-width: 173px;
- width: 100%;
padding: 8px 12px;
gap: 8px;
border-radius: 8px;
- height: 26px;
-}
-
-.dining-download-button {
- display: flex;
- background: #fff;
- border: none;
- color: #171717;
font-size: 14px;
font-weight: 400;
- width: 119px;
- height: 26px;
- text-align: center;
- padding: 2px 0 0 4px;
+ align-items: center;
+
+ &:hover {
+ cursor: pointer;
+ }
}
.exel-icon {
diff --git a/src/pages/Coop/components/DiningDownload/index.tsx b/src/pages/Coop/components/DiningDownload/index.tsx
index 4b60f85..1e7ff7d 100644
--- a/src/pages/Coop/components/DiningDownload/index.tsx
+++ b/src/pages/Coop/components/DiningDownload/index.tsx
@@ -18,15 +18,17 @@ export default function DiningDownload() {
};
return (
-
-
+ <>
+
-
+
{isModalOpen &&
}
-
-
+ >
);
}
diff --git a/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/MobileDiningTypeSelect.module.scss b/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/MobileDiningTypeSelect.module.scss
new file mode 100644
index 0000000..ec80a79
--- /dev/null
+++ b/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/MobileDiningTypeSelect.module.scss
@@ -0,0 +1,73 @@
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-left: 16px;
+}
+
+.type-title {
+ color: #10477a;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 160%; /* 28.8px */
+}
+
+.dropdown {
+ position: relative;
+
+ &__trigger {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 16px;
+ border-radius: 8px;
+ background: none;
+ color: #000;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 160%; /* 25.6px */
+
+ &:active {
+ background: #eee;
+ }
+ }
+}
+
+.arrow-icon {
+ transition: all 0.4s ease;
+
+ &__transform {
+ transform: rotate(-180deg);
+ }
+}
+
+.dropdown-list {
+ z-index: 5;
+ position: absolute;
+ bottom: -122px;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ width: 84px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 20px 0 rgba(0 0 0 / 4%), 0 8px 32px 0 rgba(0 0 0 / 8%);
+}
+
+.dropdown-item {
+ display: flex;
+ align-items: center;
+ color: #000;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 160%; /* 25.6px */
+ padding: 6px 16px;
+ background: none;
+
+ &--selected {
+ background: #f5f5f5;
+ }
+}
diff --git a/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/index.tsx b/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/index.tsx
new file mode 100644
index 0000000..1c73249
--- /dev/null
+++ b/src/pages/Coop/components/DiningTypeSelect/MobileDiningTypeSelect/index.tsx
@@ -0,0 +1,59 @@
+import { useRef, useState } from 'react';
+
+import ArrowDown from 'assets/svg/common/arrow-down.svg?react';
+import useOnClickOutside from 'hooks/useOnclickOutside';
+import { DiningType, DINING_TYPES, DINING_TYPE_MAP } from 'models/dinings';
+import cn from 'utils/className';
+
+import styles from './MobileDiningTypeSelect.module.scss';
+
+interface MobileDiningTypeSelectProps {
+ selectedDiningType: DiningType;
+ setSelectedDiningType: (diningType: DiningType) => void;
+}
+
+export default function MobileDiningTypeSelect({
+ selectedDiningType, setSelectedDiningType,
+}: MobileDiningTypeSelectProps) {
+ const [typeOpen, setTypeOpen] = useState(false);
+ const ref = useRef
(null);
+
+ useOnClickOutside(ref, () => setTypeOpen(false));
+
+ return (
+
+
{`${DINING_TYPE_MAP[selectedDiningType]} 식단`}
+
+
setTypeOpen((v) => !v)}>
+ {DINING_TYPE_MAP[selectedDiningType]}
+
+
+ {typeOpen && (
+
+ {DINING_TYPES.map((type: DiningType) => (
+ {
+ setSelectedDiningType(type);
+ setTypeOpen(false);
+ }}
+ >
+ {DINING_TYPE_MAP[type]}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/Coop/components/DownloadModal/DownloadModal.module.scss b/src/pages/Coop/components/DownloadModal/DownloadModal.module.scss
index 4601580..eae2edd 100644
--- a/src/pages/Coop/components/DownloadModal/DownloadModal.module.scss
+++ b/src/pages/Coop/components/DownloadModal/DownloadModal.module.scss
@@ -8,6 +8,7 @@
width: 100vw;
height: 100vh;
background-color: #00000066;
+ z-index: 1000;
}
.date-container {
@@ -18,7 +19,6 @@
display: flex;
flex-direction: column;
width: 485px;
- height: 345px;
padding: 24px 32px;
box-sizing: border-box;
background-color: #fff;
@@ -81,27 +81,36 @@
.toggle-container {
display: flex;
gap: 24px;
- margin: 7px 0 30px;
+ justify-content: flex-end;
+ align-items: center;
position: relative;
- left: 162px;
+ margin: 8px 0;
+
+ & > label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
}
-.button-wrapper {
+.button-container {
display: flex;
- justify-content: center;
+ justify-content: flex-end;
align-items: center;
- margin: 0 auto;
- max-width: 160px;
- width: 100%;
- gap: 8px;
+ margin: 24px auto 0;
+ padding: 8px 36px;
+ color: #fff;
background-color: #175c8e;
border-radius: 4px;
+ gap: 8px;
}
-.buttons-confirm {
- height: 42px;
- border: none;
- background-color: #175c8e;
- color: #fff;
- margin-left: 10px;
+.button-title {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 24px;
+ font-size: 16px;
+ font-weight: 600;
}
diff --git a/src/pages/Coop/components/DownloadModal/index.tsx b/src/pages/Coop/components/DownloadModal/index.tsx
index b722139..df3f0d1 100644
--- a/src/pages/Coop/components/DownloadModal/index.tsx
+++ b/src/pages/Coop/components/DownloadModal/index.tsx
@@ -1,7 +1,9 @@
-import { useState, useEffect, useCallback } from 'react';
+import { useState, useCallback } from 'react';
import { getExcel } from 'api/dinings';
import DownloadIcon from 'assets/svg/common/download-white.svg?react';
+import LoadingSpinner from 'assets/svg/common/loading.svg?react';
+import useBooleanState from 'hooks/useBooleanState';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
@@ -15,15 +17,16 @@ interface DownloadModalProps {
}
interface DateInput {
- year: string;
- month: string;
- day: string;
+ year: number | '';
+ month: number | '';
+ day: number | '';
}
export default function DownloadModal({ closeModal }: DownloadModalProps) {
const [startDate, setStartDate] = useState({ year: '', month: '', day: '' });
const [endDate, setEndDate] = useState({ year: '', month: '', day: '' });
const [isStudentCafeteriaOnly, setIsStudentCafeteriaOnly] = useState(false);
+ const [isDownloading, setIsDownloading] = useBooleanState(false);
const handleDateChange = (
setDate: React.Dispatch>,
@@ -32,7 +35,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
) => {
setDate((prev) => ({
...prev,
- [field]: e.target.value,
+ [field]: e.target.valueAsNumber || '',
}));
};
@@ -42,25 +45,11 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
}
}, [closeModal]);
- const handleKeyDown = useCallback((e: KeyboardEvent) => {
- if (e.key === 'Escape') {
- closeModal();
- }
- }, [closeModal]);
-
- useEffect(() => {
- window.addEventListener('keydown', handleKeyDown);
-
- return () => {
- window.removeEventListener('keydown', handleKeyDown);
- };
- }, [handleKeyDown]);
-
const formatDate = (date: DateInput) => {
if (!date.year || !date.month || !date.day) {
return '';
}
- return `${date.year}-${date.month.padStart(2, '0')}-${date.day.padStart(2, '0')}`;
+ return `${date.year.toString()}-${date.month.toString().padStart(2, '0')}-${date.day.toString().padStart(2, '0')}`;
};
const showToast = (message: string) => {
@@ -79,11 +68,6 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
const formattedStartDate = formatDate(startDate);
const formattedEndDate = formatDate(endDate);
- if (!formattedStartDate || !formattedEndDate) {
- showToast('날짜를 모두 입력해 주세요.');
- return;
- }
-
const requestBody = {
startDate: formattedStartDate,
endDate: formattedEndDate,
@@ -91,12 +75,14 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
};
try {
+ setIsDownloading(true);
const response = await getExcel(requestBody);
let filename = `dining_${requestBody.startDate}~${requestBody.endDate}.xlsx`;
if (response && response.headers) {
const contentDisposition = response.headers['content-disposition'];
+
if (contentDisposition) {
const match = contentDisposition.match(/filename="?(.+)"?/);
if (match) {
@@ -107,6 +93,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
}
const downloadUrl = URL.createObjectURL(response.data);
+
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
@@ -114,8 +101,16 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
+ setIsDownloading(false);
} catch (error) {
- showToast('파일 다운로드에 실패했습니다. 다시 시도해 주세요.');
+ // backend 수정 시 반영
+ // if (error?.response?.data instanceof Blob) {
+ // const errorText = await error.response.data.text();
+ // const errorJson = JSON.parse(errorText);
+ // showToast(errorJson.message);
+ // }
+
+ showToast('다운로드에 실패했습니다.');
}
};
@@ -123,14 +118,13 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
식단 파일 다운로드
-
다운로드 할 식단의 기간을 설정해 주세요
-
+
식단은 2022/11/29 부터 다운받을 수 있어요.
시작일
handleDateChange(setStartDate, 'year', e)}
className={styles['date-input-box']}
@@ -138,7 +132,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
/>
/
handleDateChange(setStartDate, 'month', e)}
className={styles['date-input-box']}
@@ -146,7 +140,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
/>
/
handleDateChange(setStartDate, 'day', e)}
className={styles['date-input-box']}
@@ -159,7 +153,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
종료일
handleDateChange(setEndDate, 'year', e)}
className={styles['date-input-box']}
@@ -167,7 +161,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
/>
/
handleDateChange(setEndDate, 'month', e)}
className={styles['date-input-box']}
@@ -175,7 +169,7 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
/>
/
handleDateChange(setEndDate, 'day', e)}
className={styles['date-input-box']}
@@ -183,11 +177,9 @@ export default function DownloadModal({ closeModal }: DownloadModalProps) {
/>
-
+
+
-
-
-
-
-
- 다운로드
-
+
+ {isDownloading ? : '다운로드'}
-
+
diff --git a/src/pages/Coop/index.tsx b/src/pages/Coop/index.tsx
index bea53b0..db32bc4 100644
--- a/src/pages/Coop/index.tsx
+++ b/src/pages/Coop/index.tsx
@@ -1,5 +1,6 @@
import { Suspense, useState } from 'react';
+import useMediaQuery from 'hooks/useMediaQuery';
import ErrorBoundary from 'layout/ErrorBoundary';
import Calendar from 'pages/Coop/components/Calendar';
import DiningBlocks from 'pages/Coop/components/DiningBlocks';
@@ -7,6 +8,7 @@ import DiningDownload from 'pages/Coop/components/DiningDownload';
import DiningTypeSelect from 'pages/Coop/components/DiningTypeSelect';
import { getDiningTypeOnTime } from 'utils/operate';
+import MobileDiningTypeSelect from './components/DiningTypeSelect/MobileDiningTypeSelect';
import styles from './Coop.module.scss';
import type { DiningType } from 'models/dinings';
@@ -14,6 +16,7 @@ import type { DiningType } from 'models/dinings';
export default function Coop() {
const [date, setDate] = useState(new Date());
const [diningType, setDiningType] = useState
(getDiningTypeOnTime());
+ const { isMobile } = useMediaQuery();
return (
@@ -21,15 +24,24 @@ export default function Coop() {
-
-
-
-
-
+
+
+ )}
+ {isMobile ? (
+
-
+ ) : (
+
+
+
+ )}
}>
diff --git a/yarn.lock b/yarn.lock
index ecdec86..33fc0fe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2072,6 +2072,32 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/core@npm:^1.6.0":
+ version: 1.6.8
+ resolution: "@floating-ui/core@npm:1.6.8"
+ dependencies:
+ "@floating-ui/utils": ^0.2.8
+ checksum: 82faa6ea9d57e466779324e51308d6d49c098fb9d184a08d9bb7f4fad83f08cc070fc491f8d56f0cad44a16215fb43f9f829524288413e6c33afcb17303698de
+ languageName: node
+ linkType: hard
+
+"@floating-ui/dom@npm:^1.6.1":
+ version: 1.6.11
+ resolution: "@floating-ui/dom@npm:1.6.11"
+ dependencies:
+ "@floating-ui/core": ^1.6.0
+ "@floating-ui/utils": ^0.2.8
+ checksum: d6413759abd06a541edfad829c45313f930310fe76a3322e74a00eb655e283db33fe3e65b5265c4072eb54db7447e11225acd355a9a02cabd1d1b0d5fc8fc21d
+ languageName: node
+ linkType: hard
+
+"@floating-ui/utils@npm:^0.2.8":
+ version: 0.2.8
+ resolution: "@floating-ui/utils@npm:0.2.8"
+ checksum: deb98bba017c4e073c7ad5740d4dec33a4d3e0942d412e677ac0504f3dade15a68fc6fd164d43c93c0bb0bcc5dc5015c1f4080dfb1a6161140fe660624f7c875
+ languageName: node
+ linkType: hard
+
"@hookform/resolvers@npm:^3.1.0":
version: 3.4.2
resolution: "@hookform/resolvers@npm:3.4.2"
@@ -5089,6 +5115,13 @@ __metadata:
languageName: node
linkType: hard
+"classnames@npm:^2.3.0":
+ version: 2.5.1
+ resolution: "classnames@npm:2.5.1"
+ checksum: da424a8a6f3a96a2e87d01a432ba19315503294ac7e025f9fece656db6b6a0f7b5003bb1fbb51cbb0d9624d964f1b9bb35a51c73af9b2434c7b292c42231c1e5
+ languageName: node
+ linkType: hard
+
"clean-css@npm:^5.2.2":
version: 5.3.3
resolution: "clean-css@npm:5.3.3"
@@ -9801,6 +9834,7 @@ __metadata:
react-router-dom: ^6.9.0
react-scripts: 5.0.1
react-toastify: ^10.0.4
+ react-tooltip: ^5.28.0
sass: ^1.60.0
stylelint: ^14.9.1
stylelint-config-standard: ^26.0.0
@@ -12461,6 +12495,19 @@ __metadata:
languageName: node
linkType: hard
+"react-tooltip@npm:^5.28.0":
+ version: 5.28.0
+ resolution: "react-tooltip@npm:5.28.0"
+ dependencies:
+ "@floating-ui/dom": ^1.6.1
+ classnames: ^2.3.0
+ peerDependencies:
+ react: ">=16.14.0"
+ react-dom: ">=16.14.0"
+ checksum: 4d1efae0fbd39ec7f1414bb325582cca2f7541a8d464b61bf3a7c5e119821868aa2d47c28509e7ea3cca844a9655feb56fc466def0edaa8db07ce427146ef3b0
+ languageName: node
+ linkType: hard
+
"react@npm:^18.2.0":
version: 18.3.1
resolution: "react@npm:18.3.1"