diff --git a/.eslintrc.json b/.eslintrc.json
index 10f001f8..2180e0d1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -28,7 +28,8 @@
"no-duplicate-imports": "error", //중복 Import 안돼
"no-console": ["warn", { "allow": ["warn", "error", "info"] }], //콘솔은 확인 뒤 지우기
- "no-unused-vars": "error", //사용하지 않은 변수면 없애기
+ "no-unused-vars": "off", //사용하지 않은 변수면 없애기
+ "@typescript-eslint/no-unused-vars": ["error"], //사용하지 않은 변수면 없애기
"no-multiple-empty-lines": "error", //공백 금지
"no-undef": "error", //정의 안 한 변수 사용 x
"indent": "off", // 프리티어 충돌로 인한 OFF
diff --git a/package.json b/package.json
index 1a45c503..5826cc24 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,8 @@
"lint:css": "stylelint './src/**/*.{ts,tsx}'"
},
"dependencies": {
+ "@tanstack/react-query": "^5.45.1",
+ "@tanstack/react-query-devtools": "^5.45.1",
"@types/axios": "^0.14.0",
"@types/react-copy-to-clipboard": "^5.0.4",
"axios": "^1.4.0",
diff --git a/src/App.css b/src/App.css
index a19e07a5..a37449c4 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,58 +1,57 @@
#app {
- height: 100%;
+ height: 100%;
}
html,
body {
- position: relative;
- height: 100%;
+ position: relative;
+ height: 100%;
}
body {
- margin: 0;
- background: #eee;
- padding: 0;
- color: #000;
- font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
- font-size: 14px;
+ margin: 0;
+ padding: 0;
+ color: #000;
+ font-size: 14px;
+ font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
+ background: #eee;
}
.swiper {
- z-index: 0;
- width: 100%;
- height: 100%;
+ z-index: 0;
+ width: 100%;
+ height: 100%;
}
.swiper-slide {
-
- /* Center slide text vertically */
- display: flex;
- align-items: center;
- justify-content: center;
- background: #141414;
- text-align: center;
- font-size: 18px;
+ /* Center slide text vertically */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ text-align: center;
+ background: #141414;
}
.swiper-slide img {
- display: block;
- width: 100%;
- height: 100%;
- object-fit: cover;
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
}
.swiper-button-prev {
- display: none;
+ display: none;
}
.swiper-button-next {
- display: none;
+ display: none;
}
.swiper-pagination-bullet {
- background-color: #A4A4A4;
+ background-color: #a4a4a4 !important;
}
.swiper-pagination-bullet-active {
- background-color: #3253FF;
-}
\ No newline at end of file
+ background-color: #3253ff !important;
+}
diff --git a/src/App.tsx b/src/App.tsx
index a5a64135..75914087 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,18 @@
-import { useEffect } from 'react';
+/**카카오톡 인앱브라우저 종료후 크롬 및 사파리로 오픈하는 utils file */
+import './utils/changeBrowser';
+import 'react-toastify/dist/ReactToastify.css';
+import './App.css';
-import { ThemeProvider } from 'styled-components';
-import styled from 'styled-components/macro';
-import ToastContainerBox from 'utils/toast/ToastContainer';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import Router from './Router';
import GlobalStyle from './styles/globalStyles';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import Router from './Router';
+import { ThemeProvider } from 'styled-components';
+import ToastContainerBox from 'utils/toast/ToastContainer';
+import styled from 'styled-components/macro';
import { theme } from './styles/theme';
-
-import './App.css';
-/**카카오톡 인앱브라우저 종료후 크롬 및 사파리로 오픈하는 utils file */
-import './utils/changeBrowser';
-import 'react-toastify/dist/ReactToastify.css';
+import { useEffect } from 'react';
const MobileWrapper = styled.div`
display: flex;
@@ -47,14 +48,19 @@ function App() {
window.removeEventListener('resize', setScreenSize);
};
}, []);
+
+ const queryClient = new QueryClient();
return (
<>
-
-
-
-
-
+
+
+
+
+
+
+
+
>
);
diff --git a/src/Router.tsx b/src/Router.tsx
index 8536d068..93b95642 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -1,15 +1,16 @@
+import { BrowserRouter, Route, Routes } from 'react-router-dom';
+
import ChooseBestTime from 'pages/bestMeetTime/ChooseBestTime';
import CreateMeeting from 'pages/createMeeting/CreateMeeting';
import CueCard from 'pages/cueCard/CueCard';
import ErrorPage404 from 'pages/errorLoading/ErrorPage404';
import LoadingPage from 'pages/errorLoading/LoadingPage';
-import SelectSchedulePriority from 'pages/legacy/selectSchedule/SelectPriorityPage';
-import SelectPage from 'pages/legacy/selectSchedule/SelectSchedulePage';
import LoginEntrance from 'pages/loginEntrance/LoginEntrance';
import OnBoarding from 'pages/onBoarding/OnBoarding';
+import SelectPage from 'pages/legacy/selectSchedule/SelectSchedulePage';
+import SelectSchedule from 'pages/selectSchedule/SelectSchedule';
+import SelectSchedulePriority from 'pages/legacy/selectSchedule/SelectPriorityPage';
import SteppingLayout from 'pages/steppingStone/SteppingLayout';
-import Test from 'pages/Test';
-import { BrowserRouter, Route, Routes } from 'react-router-dom';
const Router = () => {
return (
@@ -18,8 +19,7 @@ const Router = () => {
} />
} />
} />
- } />
- } />
+ } />
}
@@ -36,8 +36,6 @@ const Router = () => {
} />
} />
} />
- } />
- } />
);
diff --git a/src/components/atomComponents/Button.tsx b/src/components/atomComponents/Button.tsx
index 8428104a..34f99cd2 100644
--- a/src/components/atomComponents/Button.tsx
+++ b/src/components/atomComponents/Button.tsx
@@ -1,7 +1,7 @@
-import React from 'react';
-
import { css, styled } from 'styled-components';
+import React from 'react';
+
interface ButtonProps {
children: React.ReactNode;
typeState: string;
@@ -73,6 +73,7 @@ const buttonCSS = {
${buttonDefaultCSS.basicCss};
background: ${({ theme }) => theme.colors.grey7};
color: ${({ theme }) => theme.colors.grey5};
+ pointer-events: none;
`,
halfsecondaryDisabled: css`
${buttonDefaultCSS.basicCss};
diff --git a/src/components/moleculesComponents/Header.tsx b/src/components/moleculesComponents/Header.tsx
index 10528811..0ac32bf3 100644
--- a/src/components/moleculesComponents/Header.tsx
+++ b/src/components/moleculesComponents/Header.tsx
@@ -1,30 +1,28 @@
-import { BackIc, ExitIc, HambergerIc, LinkIc, MainLogoIc } from 'components/Icon/icon';
import { Dispatch, SetStateAction, useState } from 'react';
-import CopyToClipboard from 'react-copy-to-clipboard';
-import Navigation from './Navigation';
import Text from 'components/atomComponents/Text';
-import { notify } from 'utils/toast/copyLink';
+import { BackIc, ExitIc, HambergerIc, LinkIc, MainLogoIc } from 'components/Icon/icon';
+import { useScheduleStepContext } from 'pages/selectSchedule/contexts/useScheduleStepContext';
+import { ScheduleStepType } from 'pages/selectSchedule/types';
+import CopyToClipboard from 'react-copy-to-clipboard';
+import { useParams } from 'react-router';
+import { useNavigate } from 'react-router-dom';
import styled from 'styled-components/macro';
import { theme } from 'styles/theme';
-import { useNavigate } from 'react-router-dom';
-import { useParams } from 'react-router';
+import { notify } from 'utils/toast/copyLink';
+
+import Navigation from './Navigation';
+
interface HeaderProps {
position: string;
- setStep?: Dispatch>;
+ setFunnelStep?: Dispatch>;
+ setSelectScheduleStep?: Dispatch>;
}
-function Header({ position, setStep }: HeaderProps) {
+function Header({ position, setFunnelStep }: HeaderProps) {
+ const { scheduleStep, setScheduleStep } = useScheduleStepContext();
const navigationOptions = [
- // {
- // title: '공지사항',
- // url: '',
- // },
- // {
- // title: 'ASAP family',
- // url: '',
- // },
{
title: '약속 생성하기',
url: '/meet/create',
@@ -38,8 +36,8 @@ function Header({ position, setStep }: HeaderProps) {
const navigate = useNavigate();
const [isNaviOpen, setIsNaviOpen] = useState(false);
const backToFunnel = () => {
- if (setStep !== undefined) {
- setStep((prev) => {
+ if (setFunnelStep !== undefined) {
+ setFunnelStep((prev) => {
if (prev === 0) {
navigate('/');
return prev;
@@ -48,6 +46,16 @@ function Header({ position, setStep }: HeaderProps) {
});
}
};
+ const backToSelectSchedule = () => {
+ if (setScheduleStep !== undefined) {
+ if (scheduleStep === 'selectTimeSlot') {
+ window.history.back();
+ return;
+ } else if (scheduleStep === 'selectPriority') {
+ setScheduleStep('selectTimeSlot');
+ }
+ }
+ };
const { meetingId } = useParams();
return (
@@ -78,7 +86,7 @@ function Header({ position, setStep }: HeaderProps) {
) : position === 'schedule' ? (
- window.history.back()}>
+
diff --git a/src/components/moleculesComponents/TitleComponents.tsx b/src/components/moleculesComponents/TitleComponents.tsx
index 4a532931..66ff3363 100644
--- a/src/components/moleculesComponents/TitleComponents.tsx
+++ b/src/components/moleculesComponents/TitleComponents.tsx
@@ -4,30 +4,35 @@ import { theme } from 'styles/theme';
interface TextProps {
main: string;
- sub: string;
+ sub?: string;
+ padding?: string;
}
-function TitleComponents({ main, sub }: TextProps) {
+const defaultPadding = `4.4rem 0 4.2rem 0`;
+function TitleComponents({ main, sub, padding = defaultPadding }: TextProps) {
return (
-
+
{main}
-
- {sub}
-
+ {sub && (
+
+ {sub}
+
+ )}
);
}
export default TitleComponents;
-const TitleWrapper = styled.div`
+const TitleWrapper = styled.div<{ $padding: string }>`
display: flex;
position: relative;
flex-direction: column;
gap: 1.2rem;
- padding: 4.4rem 0 4.2rem 0;
+ padding: ${({ $padding }) => $padding};
+
width: 100%;
`;
diff --git a/src/components/timetableComponents/Timetable.tsx b/src/components/timetableComponents/Timetable.tsx
index bde346a3..aa2b904e 100644
--- a/src/components/timetableComponents/Timetable.tsx
+++ b/src/components/timetableComponents/Timetable.tsx
@@ -1,40 +1,42 @@
import { ReactNode } from 'react';
-import { DateType } from 'pages/selectSchedule/SelectSchedule';
import styled from 'styled-components';
-import Column from './parts/Column';
import DateTitle from './parts/ColumnTitle';
import SlotTitle from './parts/SlotTitle';
-
-interface RenderProps {
- date: string;
- timeSlots: string[];
-}
+import { ColumnStructure, DateType } from './types';
interface TimetableProps {
timeSlots: string[];
availableDates: DateType[];
- children: (props: RenderProps) => ReactNode;
+ children: (props: ColumnStructure) => ReactNode;
+ bottomItem?: ReactNode;
}
-function Timetable({ timeSlots, availableDates, children }: TimetableProps) {
+function Timetable({ timeSlots, availableDates, children, bottomItem }: TimetableProps) {
const emptyDates = Array.from({ length: 7 - availableDates.length }, (_, i) => `empty${i + 1}`);
return (
-
-
-
-
-
- {availableDates.map((date) => {
- const dateKey = Object.values(date).join('/');
- return {children({ date: dateKey, timeSlots })};
- })}
- {emptyDates && emptyDates.map((value) => )}
-
-
-
+ <>
+
+
+
+
+
+ {availableDates.map((date) => {
+ const dateKey = Object.values(date).join('/');
+ return (
+
+ {children({ date: dateKey, timeSlots })}
+
+ );
+ })}
+ {emptyDates && emptyDates.map((value) => )}
+
+
+
+ {bottomItem}
+ >
);
}
@@ -45,19 +47,26 @@ const TimetableWrapper = styled.div`
gap: 0.75rem;
`;
-const TableWrapper = styled.div`
+const TableWithDateWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 0.8rem;
`;
-const Table = styled.div`
+const TableWrapper = styled.div`
display: flex;
border-bottom: 1px solid ${({ theme }) => theme.colors.grey7};
border-left: 1px solid ${({ theme }) => theme.colors.grey7};
`;
-const EmptyColumn = styled.div`
+const ColumnWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+
+ border-right: 1px solid ${({ theme }) => theme.colors.grey7};
+`;
+
+const EmptyColumnWrapper = styled.div`
display: flex;
flex-direction: column;
border-top: 1px solid ${({ theme }) => theme.colors.grey7};
diff --git a/src/components/timetableComponents/context.ts b/src/components/timetableComponents/context.ts
deleted file mode 100644
index 94d14cf6..00000000
--- a/src/components/timetableComponents/context.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createContext, useContext } from 'react';
-
-export type SelectedSlotType = {
- [key: number]: {
- date: string;
- startSlot: string;
- endSlot: string;
- };
-};
-
-type TimetableContextType = {
- startSlot?: string;
- setStartSlot: (startSlot?: string) => void;
- selectedSlots: SelectedSlotType;
- setSelectedSlots: (selectedSlots: SelectedSlotsType) => void;
-};
-
-export const TimetableContext = createContext({
- startSlot: undefined,
- setStartSlot: () => undefined,
- selectedSlots: {},
- setSelectedSlots: () => undefined,
-});
-
-export function useTimetableContext() {
- const context = useContext(TimetableContext);
- if (context == null) {
- throw new Error('TimetableContext Error');
- }
- return context;
-}
diff --git a/src/components/timetableComponents/parts/Column.tsx b/src/components/timetableComponents/parts/Column.tsx
deleted file mode 100644
index 894d8da6..00000000
--- a/src/components/timetableComponents/parts/Column.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { ReactNode } from 'react';
-
-import styled from 'styled-components';
-
-interface ColumnProps {
- children: ReactNode;
-}
-
-function Column({ children }: ColumnProps) {
- return {children};
-}
-
-export default Column;
-
-const ColumnWrapper = styled.div`
- display: flex;
- flex-direction: column;
-
- border-right: 1px solid ${({ theme }) => theme.colors.grey7};
-`;
diff --git a/src/components/timetableComponents/parts/ColumnTitle.tsx b/src/components/timetableComponents/parts/ColumnTitle.tsx
index c6bf61dd..74dd699a 100644
--- a/src/components/timetableComponents/parts/ColumnTitle.tsx
+++ b/src/components/timetableComponents/parts/ColumnTitle.tsx
@@ -1,8 +1,9 @@
-import { DateType } from '../Timetable';
import Text from 'components/atomComponents/Text';
import styled from 'styled-components';
import { theme } from 'styles/theme';
+import { DateType } from '../types';
+
interface ColumnTitleProps {
availableDates: DateType[];
}
diff --git a/src/components/timetableComponents/parts/Slot.tsx b/src/components/timetableComponents/parts/Slot.tsx
index fd2dea06..355fd68a 100644
--- a/src/components/timetableComponents/parts/Slot.tsx
+++ b/src/components/timetableComponents/parts/Slot.tsx
@@ -1,45 +1,36 @@
+import { ReactNode } from 'react';
import styled from 'styled-components';
-import useSlotSeletion from '../hooks/useSlotSelection';
-
interface SlotProps {
- slot: string;
- selectedEntryId?: number;
+ slotId: string;
+ slotStyle?: string;
+ onClick?: () => void;
+ children?: ReactNode;
}
-function Slot({ slot, selectedEntryId }: SlotProps) {
- const { startSlot, onClickSlot } = useSlotSeletion();
-
- const borderStyle = slot.endsWith(':30') ? 'dashed' : 'solid';
- const styledSlotProps = {
- $borderStyle: borderStyle,
- $isSelected: selectedEntryId !== undefined,
- onClick: () => onClickSlot(slot, selectedEntryId),
- };
+function Slot({ slotId, slotStyle, onClick, children }: SlotProps) {
+ const borderStyle = slotId.endsWith(':30') ? 'dashed' : 'solid';
- if (slot === startSlot) {
- return ;
- } else {
- return ;
- }
+ return (
+
+ {children}
+
+ );
}
export default Slot;
-const DefaultSlot = styled.div<{ $borderStyle: string; $isSelected: boolean }>`
- border-top: 1px ${({ $borderStyle }) => $borderStyle} ${({ theme }) => theme.colors.grey7};
- background-color: ${({ $isSelected, theme }) =>
- $isSelected ? theme.colors.main1 : 'transparent'};
- cursor: pointer;
- width: 4.4rem;
- height: 2.2rem;
-`;
+const DefaultSlot = styled.div<{
+ $borderStyle: string;
+ $slotStyle?: string;
+}>`
+ border-top: 1px solid ${({ theme }) => theme.colors.grey7};
+ border-top-style: ${({ $borderStyle }) => $borderStyle};
+ ${({ $slotStyle }) => $slotStyle};
-const SelectingSlot = styled.div<{ $borderStyle: string; $isSelected: boolean }>`
- border: 1px dashed ${({ theme }) => theme.colors.main5};
- background-color: ${({ $isSelected, theme }) =>
- $isSelected ? theme.colors.main1 : 'transparent'};
- cursor: pointer;
width: 4.4rem;
height: 2.2rem;
+
+ display: flex;
+ justify-content: center;
`;
diff --git a/src/components/timetableComponents/types.ts b/src/components/timetableComponents/types.ts
new file mode 100644
index 00000000..bf73e819
--- /dev/null
+++ b/src/components/timetableComponents/types.ts
@@ -0,0 +1,22 @@
+interface BaseStructure {
+ timeSlots: string[];
+}
+
+export interface TimetableStructure extends BaseStructure {
+ availableDates: DateType[];
+}
+
+export interface ColumnStructure extends BaseStructure {
+ date: string;
+}
+
+export interface DateType {
+ month: string | undefined;
+ day: string | undefined;
+ dayOfWeek: string | undefined;
+}
+
+export interface SlotType {
+ startTime: string;
+ endTime: string;
+}
diff --git a/src/components/timetableComponents/utils.ts b/src/components/timetableComponents/utils.ts
index 76cab6a5..d3f6d7bb 100644
--- a/src/components/timetableComponents/utils.ts
+++ b/src/components/timetableComponents/utils.ts
@@ -1,27 +1,27 @@
-import { SlotType } from './Timetable';
-
+import { SlotType } from './types';
/**
*
- * @description 시작 시간(startTime)과 종료 시간(endTime) 사이에서 30분 간격으로 시간 슬롯을 생성하여 반환하는 함수입니다.
+ * @desc 문자열로 된 time('HH:MM')에 minutes을 더하는 함수
*/
+export const addMinutes = (time: string, minutes: number) => {
+ const [hour, minute] = time.split(':').map(Number);
+ const totalMinutes = hour * 60 + minute + minutes;
+ const newHour = String(Math.floor(totalMinutes / 60)).padStart(2, '0');
+ const newMinute = String(totalMinutes % 60).padStart(2, '0');
+ return `${newHour}:${newMinute}`;
+};
-export function getAvailableTimes(times: SlotType) {
- function addMinutes(time: string, minutes: number) {
- const [hour, minute] = time.split(':').map(Number);
- const totalMinutes = hour * 60 + minute + minutes;
- const newHour = String(Math.floor(totalMinutes / 60)).padStart(2, '0');
- const newMinute = String(totalMinutes % 60).padStart(2, '0');
- return `${newHour}:${newMinute}`;
- }
+/**
+ *
+ * @desc 시작 시간(startTime)과 종료 시간(endTime) 사이에서 30분 간격으로 시간 슬롯을 생성하여 반환하는 함수
+ */
- function getTimeSlots(startTime: string, endTime: string): string[] {
- const slots = [];
- let curTime = startTime;
- while (curTime < endTime) {
- slots.push(curTime);
- curTime = addMinutes(curTime, 30);
- }
- return slots;
+export const getAvailableTimes = ({ startTime, endTime }: SlotType) => {
+ const slots = [];
+ let curTime = startTime;
+ while (curTime < endTime) {
+ slots.push(curTime);
+ curTime = addMinutes(curTime, 30);
}
- return getTimeSlots(times.startTime, times.endTime);
-}
+ return slots;
+};
diff --git a/src/pages/LoginEntrance/components/MemberComponent.tsx b/src/pages/LoginEntrance/components/MemberComponent.tsx
index b1a30430..a87ea7ac 100644
--- a/src/pages/LoginEntrance/components/MemberComponent.tsx
+++ b/src/pages/LoginEntrance/components/MemberComponent.tsx
@@ -1,16 +1,16 @@
import React, { Dispatch, SetStateAction } from 'react';
-import { userNameAtom } from 'atoms/atom';
import Button from 'components/atomComponents/Button';
+import Header from 'components/moleculesComponents/Header';
import Text from 'components/atomComponents/Text';
import TextInput from 'components/atomComponents/TextInput';
-import Header from 'components/moleculesComponents/Header';
import TitleComponent from 'components/moleculesComponents/TitleComponents';
-import { useParams } from 'react-router';
-import { useNavigate } from 'react-router-dom';
-import { useRecoilState } from 'recoil';
import styled from 'styled-components/macro';
import { theme } from 'styles/theme';
+import { useNavigate } from 'react-router-dom';
+import { useParams } from 'react-router';
+import { useRecoilState } from 'recoil';
+import { userNameAtom } from 'atoms/atom';
interface HostInfoProps {
name: string;
@@ -38,7 +38,7 @@ function MemberComponent({ hostInfo, setHostInfo }: HostProps) {
const loginMember = () => {
setUserName(hostInfo.name);
- navigate(`/member/schedule/${meetingId}`);
+ navigate(`/member/select/${meetingId}`);
};
return (
diff --git a/src/pages/LoginEntrance/components/NoAvailableTimeModal.tsx b/src/pages/LoginEntrance/components/NoAvailableTimeModal.tsx
index 3b56bf2d..0b82b270 100644
--- a/src/pages/LoginEntrance/components/NoAvailableTimeModal.tsx
+++ b/src/pages/LoginEntrance/components/NoAvailableTimeModal.tsx
@@ -10,7 +10,7 @@ interface ModalProps {
setIsModalOpen: Dispatch>;
}
-function NoAvailableTimeModal ({ setIsModalOpen }: ModalProps) {
+function NoAvailableTimeModal({ setIsModalOpen }: ModalProps) {
const { meetingId } = useParams();
return (
@@ -29,7 +29,7 @@ function NoAvailableTimeModal ({ setIsModalOpen }: ModalProps) {
방장 페이지에 접속할 수 있어요!
-
+
가능 시간 입력하러 가기
diff --git a/src/pages/OverallSchedule/OverallSchedule.tsx b/src/pages/OverallSchedule/OverallSchedule.tsx
index f29f495f..84a2d4ef 100644
--- a/src/pages/OverallSchedule/OverallSchedule.tsx
+++ b/src/pages/OverallSchedule/OverallSchedule.tsx
@@ -1,167 +1,47 @@
-import React, { useEffect, useState } from 'react';
-
-import { availableDatesAtom, preferTimesAtom, timeSlotUserNameAtom } from 'atoms/atom';
-import Text from 'components/atomComponents/Text';
-import LoadingPage from 'pages/errorLoading/LoadingPage';
+import { getAvailableTimes } from 'components/timetableComponents/utils';
import { useParams } from 'react-router-dom';
-import { useRecoilState, useRecoilValue } from 'recoil';
-import { OverallScheduleData } from 'src/types/overallScheduleType';
-import { styled } from 'styled-components';
-import { theme } from 'styles/theme';
-import { availableScheduleOptionApi } from 'utils/apis/availbleScheduleOptionApi';
-import { overallScheduleApi } from 'utils/apis/overallScheduleApi';
-
-import TimeTable from './components/TimeTable';
-import { getFormattedAvailableDateTimes } from './utils/getFormattedAvailableDateTimes';
+import styled from 'styled-components';
+import { useGetOverallSchedule } from 'utils/apis/useGetOverallSchedule';
+import { useGetTimetable } from 'utils/apis/useGetTimetable';
+import OverallScheduleTable from './components/OverallScheduleTable';
+import Title from './components/Title';
-const OverallSchedule = () => {
+function OverallSchedule() {
const { meetingId } = useParams();
- const [overallScheduleData, setOverallScheduleData] = useState();
-
- const [availableDates, setAvailableDates] = useRecoilState(availableDatesAtom);
-
- const [preferTimes, setPreferTimes] = useRecoilState(preferTimesAtom);
-
- const timeSlotUserNames = useRecoilValue(timeSlotUserNameAtom);
-
- const [memberCount, setMemberCount] = useState(0);
- const [totalUserNames, setTotalUserNames] = useState();
-
- const getAvailableScheduleOption = async () => {
- try {
- const { data } = await availableScheduleOptionApi(meetingId);
- setAvailableDates(data.data.availableDates);
- setPreferTimes(data.data.preferTimes);
- } catch (err) {
- console.log(err);
- }
- };
-
-
- const getOverallSchedule = async () => {
- try {
- const result = await overallScheduleApi(meetingId);
- const { data } = result.data;
- const uniqueData = [...new Set(data.totalUserNames)];
- setOverallScheduleData(data);
- setMemberCount(data.memberCount);
- setTotalUserNames(uniqueData);
- } catch (err) {
- console.log(err);
- }
- };
-
- useEffect(() => {
- getAvailableScheduleOption();
- getOverallSchedule();
- }, []);
+ const { data: dataTimetable, isLoading: isLoadingTimetable } = useGetTimetable(meetingId);
+ const { data: dataOverallSchedule, isLoading: isLoadingOverallSchedule } = useGetOverallSchedule(
+ meetingId,
+ );
- const formattedAvailableDateTimes =
- overallScheduleData && getFormattedAvailableDateTimes(overallScheduleData);
+ // 시간대 선택 단계가 없어질 것을 고려하여 상수값을 설정해놓음
+ const PREFER_TIMES = { startTime: '06:00', endTime: '24:00' };
return (
- {overallScheduleData ? (
- <>
-
-
- 현재까지
-
-
- {memberCount.toString()}명
-
-
- 이 입력했어요
-
-
-
- {totalUserNames &&
- totalUserNames.map((name, idx) => (
-
- {name}
- {idx !== totalUserNames.length - 1 ? ',' : ''}
-
- ))}
-
-
+ {!isLoadingTimetable &&
+ !isLoadingOverallSchedule &&
+ dataTimetable &&
+ dataOverallSchedule && (
+
-
- {!timeSlotUserNames ? (
-
-
- 블럭을 선택하면 해당 시간대에 참여가능한
-
-
- 인원을 확인할 수 있어요
-
-
- ) : (
- timeSlotUserNames.map((name, idx) => (
-
- {name}
- {idx !== timeSlotUserNames.length - 1 ? ',' : ''}
-
- ))
- )}
-
- >
- ) : (
-
-
-
- )}
+ )}
);
-};
+}
export default OverallSchedule;
-const UserNameWrapper = styled.aside`
+const OverallScheduleWrapper = styled.div`
display: flex;
- position: fixed;
- bottom: 4.4rem;
- flex-wrap: wrap;
+ flex-direction: column;
+ align-items: center;
justify-content: center;
- border: 1px solid ${({ theme }) => theme.colors.grey5};
- border-radius: 0.8rem;
- background: ${({ theme }) => theme.colors.grey9};
- width: 33.5rem;
- min-height: 8.3rem;
- text-align: center;
- color: ${({ theme }) => theme.colors.white};
-`;
-
-const OverallScheduleWrapper = styled.main`
- margin-bottom: 16.1rem;
+ margin-bottom: 16.4rem;
`;
-const TextOneLine = styled.div`
- display: flex;
- flex-wrap: wrap;
- margin-top: 3.7rem;
- width: 100%;
-`;
-const TotalUserNames = styled.div`
- display: flex;
- margin-top: 1.2rem;
- margin-bottom: 2.4rem;
-`;
-
-const LoadingWrapper = styled.div`
- position: relative;
- top: 25rem;
- width: 100%;
-`;
-
-const TextTwoLine = styled.div`
- display:flex;
- flex-direction:column;
- align-items: center;
- justify-content:center;
-`
\ No newline at end of file
diff --git a/src/pages/OverallSchedule/components/OverallScheduleColumn.tsx b/src/pages/OverallSchedule/components/OverallScheduleColumn.tsx
new file mode 100644
index 00000000..96853a6d
--- /dev/null
+++ b/src/pages/OverallSchedule/components/OverallScheduleColumn.tsx
@@ -0,0 +1,45 @@
+import Slot from 'components/timetableComponents/parts/Slot';
+import { ColumnStructure } from 'components/timetableComponents/types';
+import { theme } from 'styles/theme';
+import { TimeSlot } from 'utils/apis/useGetOverallSchedule';
+
+import { useSlotClick } from '../hooks/useSlotClick';
+
+interface OverallScheduleColumnProps extends ColumnStructure {
+ availableSlotInfo: TimeSlot[];
+}
+
+function OverallScheduleColumn({ date, timeSlots, availableSlotInfo }: OverallScheduleColumnProps) {
+
+ const { clickedSlot, onClickSlot } = useSlotClick();
+
+ const getTimeSlotStyle = (colorLevel: number, slotId:string) => {
+ const COLOR :{ [key : number]: string } = {
+ 0: 'transparent',
+ 1: theme.colors.level1,
+ 2: theme.colors.level2,
+ 3: theme.colors.level3,
+ 4: theme.colors.level4,
+ 5: theme.colors.level5,
+ };
+
+ const isClickedSlot = clickedSlot === slotId;
+ return `
+ background-color: ${isClickedSlot && colorLevel!==0 ? theme.colors.sub1 : COLOR[colorLevel]};
+ cursor: ${colorLevel !== 0 ? 'pointer' : 'default'};
+ `
+ }
+
+ return (
+ <>
+ {timeSlots.map((timeSlot) => {
+ const { colorLevel = 0, userNames = [] } = availableSlotInfo.find((info) => info.time === timeSlot) ?? {};
+ const slotId = `${date}/${timeSlot}`;
+
+ return onClickSlot(slotId, userNames)}/>;
+ })}
+ >
+ );
+}
+
+export default OverallScheduleColumn;
diff --git a/src/pages/OverallSchedule/components/OverallScheduleTable.tsx b/src/pages/OverallSchedule/components/OverallScheduleTable.tsx
new file mode 100644
index 00000000..1553de8d
--- /dev/null
+++ b/src/pages/OverallSchedule/components/OverallScheduleTable.tsx
@@ -0,0 +1,65 @@
+import { useState } from 'react';
+
+import Timetable from 'components/timetableComponents/Timetable';
+import { ColumnStructure, TimetableStructure } from 'components/timetableComponents/types';
+import {
+ AvailableDateTime,
+ TimeSlot,
+ getOverallScheduleResponse,
+} from 'utils/apis/useGetOverallSchedule';
+
+import OverallScheduleColumn from './OverallScheduleColumn';
+import UserNames from './UserNames';
+import { ClickContext } from '../contexts/useClickContext';
+
+interface OverallScheduleTableProps extends TimetableStructure {
+ dataOverallSchedule: getOverallScheduleResponse['data'];
+}
+
+function OverallScheduleTable({
+ timeSlots,
+ availableDates,
+ dataOverallSchedule,
+}: OverallScheduleTableProps) {
+ const [clickedSlot, setClickedSlot] = useState(undefined);
+ const [clickedUserNames, setClickedUserNames] = useState([]);
+
+ const getAvailableTimesPerDate = (
+ availableDates: AvailableDateTime[],
+ date: string,
+ ): TimeSlot[] => {
+ const [month, day, dayOfWeek] = date.split('/');
+
+ const matchedDate = availableDates.find(
+ (date) => date.month === month && date.day === day && date.dayOfWeek === dayOfWeek,
+ );
+
+ return matchedDate ? matchedDate.timeSlots : [];
+ };
+
+ return (
+
+ }>
+ {({ date, timeSlots }: ColumnStructure) => (
+
+ )}
+
+
+ );
+}
+
+export default OverallScheduleTable;
diff --git a/src/pages/OverallSchedule/components/Title.tsx b/src/pages/OverallSchedule/components/Title.tsx
new file mode 100644
index 00000000..5fea4c8a
--- /dev/null
+++ b/src/pages/OverallSchedule/components/Title.tsx
@@ -0,0 +1,49 @@
+import Text from 'components/atomComponents/Text';
+import styled from 'styled-components';
+import { theme } from 'styles/theme';
+
+interface TitleProps {
+ memberCount?: number;
+ totalUserNames?: string[];
+}
+
+function Title({ memberCount, totalUserNames }: TitleProps) {
+ return (
+ <>
+
+
+ 현재까지
+
+
+ {memberCount !== undefined ? memberCount.toString() : ''}명
+
+
+ 이 입력했어요
+
+
+
+ {totalUserNames && (
+
+ {totalUserNames.join(',')}
+
+ )}
+
+ >
+ );
+}
+
+export default Title;
+
+const TextOneLine = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 3.7rem;
+ width: 100%;
+`;
+
+const TotalUserNames = styled.div`
+ display: flex;
+ margin-top: 1.2rem;
+ margin-bottom: 2.4rem;
+ width: 100%;
+`;
diff --git a/src/pages/OverallSchedule/components/UserNames.tsx b/src/pages/OverallSchedule/components/UserNames.tsx
new file mode 100644
index 00000000..48c04a8c
--- /dev/null
+++ b/src/pages/OverallSchedule/components/UserNames.tsx
@@ -0,0 +1,52 @@
+import Text from 'components/atomComponents/Text';
+import styled from 'styled-components';
+import { theme } from 'styles/theme';
+
+import { useClickContext } from '../contexts/useClickContext';
+
+function UserNames() {
+ const { clickedUserNames } = useClickContext();
+
+ return (
+
+ {clickedUserNames.length === 0 ? (
+
+
+ 블럭을 선택하면 해당 시간대에 참여가능한
+
+
+ 인원을 확인할 수 있어요
+
+
+ ) : (
+
+ {clickedUserNames.join(', ')}
+
+ )}
+
+ );
+}
+
+export default UserNames;
+
+const UserNamesWrapper = styled.aside`
+ display: flex;
+ position: fixed;
+ bottom: 4.4rem;
+ flex-wrap: wrap;
+ justify-content: center;
+ border: 1px solid ${({ theme }) => theme.colors.grey5};
+ border-radius: 0.8rem;
+ background: ${({ theme }) => theme.colors.grey9};
+ width: 33.5rem;
+ min-height: 8.3rem;
+ text-align: center;
+ color: ${({ theme }) => theme.colors.white};
+`;
+
+const Texts = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`;
diff --git a/src/pages/OverallSchedule/contexts/useClickContext.ts b/src/pages/OverallSchedule/contexts/useClickContext.ts
new file mode 100644
index 00000000..4723cb90
--- /dev/null
+++ b/src/pages/OverallSchedule/contexts/useClickContext.ts
@@ -0,0 +1,23 @@
+import { Dispatch, SetStateAction, createContext, useContext } from 'react';
+
+interface ClickContextType {
+ clickedSlot: string | undefined;
+ setClickedSlot: Dispatch>;
+ clickedUserNames: string[];
+ setClickedUserNames: Dispatch>;
+}
+
+export const ClickContext = createContext({
+ clickedSlot: undefined,
+ setClickedSlot: () => undefined,
+ clickedUserNames: [],
+ setClickedUserNames: () => [],
+});
+
+export function useClickContext() {
+ const context = useContext(ClickContext);
+ if (context == null) {
+ throw new Error('ClickContext Error');
+ }
+ return context;
+}
diff --git a/src/pages/OverallSchedule/hooks/useSlotClick.ts b/src/pages/OverallSchedule/hooks/useSlotClick.ts
new file mode 100644
index 00000000..f49c5cd7
--- /dev/null
+++ b/src/pages/OverallSchedule/hooks/useSlotClick.ts
@@ -0,0 +1,12 @@
+import { useClickContext } from '../contexts/useClickContext';
+
+export const useSlotClick = () => {
+ const { clickedSlot, setClickedSlot, setClickedUserNames } = useClickContext();
+
+ const onClickSlot = (targetSlot: string, targetUserNames: string[]) => {
+ setClickedSlot(targetSlot);
+ setClickedUserNames(targetUserNames);
+ };
+
+ return { clickedSlot, onClickSlot };
+};
diff --git a/src/pages/SteppingStone/components/SteppingBtnSection.tsx b/src/pages/SteppingStone/components/SteppingBtnSection.tsx
index b44ede21..2107e84e 100644
--- a/src/pages/SteppingStone/components/SteppingBtnSection.tsx
+++ b/src/pages/SteppingStone/components/SteppingBtnSection.tsx
@@ -46,7 +46,7 @@ function SteppingBtnSection({ steppingType }: SteppingProps) {
-
+
diff --git a/src/pages/Test.tsx b/src/pages/Test.tsx
deleted file mode 100644
index 828db086..00000000
--- a/src/pages/Test.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import styled from 'styled-components';
-
-import SelectSchedule from './selectSchedule/SelectSchedule';
-
-function Test() {
- return (
-
-
-
- );
-}
-
-export default Test;
-
-const StyledTest = styled.div`
- padding: 5rem;
-`;
diff --git a/src/pages/createMeeting/CreateMeeting.tsx b/src/pages/createMeeting/CreateMeeting.tsx
index 5759f390..b99eeccd 100644
--- a/src/pages/createMeeting/CreateMeeting.tsx
+++ b/src/pages/createMeeting/CreateMeeting.tsx
@@ -1,12 +1,12 @@
import { useEffect, useState } from 'react';
import Header from 'components/moleculesComponents/Header';
+import { MeetingInfo } from './types/useFunnelInterface';
import ReturnBodyComponent from 'pages/createMeeting/components/ReturnBodyComponent';
import ReturnTitleComponent from 'pages/createMeeting/components/ReturnTitleComponent';
-import styled from 'styled-components/macro';
-
import { funnelStep } from './data/meetingInfoData';
-import { MeetingInfo } from './types/useFunnelInterface';
+import styled from 'styled-components/macro';
+import { useGetTimetable } from 'utils/apis/useGetTimetable';
const initialMeetingInfo: MeetingInfo = {
title: '',
@@ -24,10 +24,11 @@ function CreateMeeting() {
const [step, setStep] = useState(0);
const [meetingInfo, setMeetingInfo] = useState(initialMeetingInfo);
const currentStep = funnelStep[step];
+
return (
<>
-
+
{
+ const { meetingId } = useParams();
+ const [overallScheduleData, setOverallScheduleData] = useState();
+
+ const [availableDates, setAvailableDates] = useRecoilState(availableDatesAtom);
+
+ const [preferTimes, setPreferTimes] = useRecoilState(preferTimesAtom);
+
+ const timeSlotUserNames = useRecoilValue(timeSlotUserNameAtom);
+
+ const [memberCount, setMemberCount] = useState(0);
+ const [totalUserNames, setTotalUserNames] = useState();
+
+ const getAvailableScheduleOption = async () => {
+ try {
+ const { data } = await availableScheduleOptionApi(meetingId);
+ setAvailableDates(data.data.availableDates);
+ setPreferTimes(data.data.preferTimes);
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+
+ const getOverallSchedule = async () => {
+ try {
+ const result = await overallScheduleApi(meetingId);
+ const { data } = result.data;
+ const uniqueData = [...new Set(data.totalUserNames)];
+ setOverallScheduleData(data);
+ setMemberCount(data.memberCount);
+ setTotalUserNames(uniqueData);
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ useEffect(() => {
+ getAvailableScheduleOption();
+ getOverallSchedule();
+ }, []);
+
+ const formattedAvailableDateTimes =
+ overallScheduleData && getFormattedAvailableDateTimes(overallScheduleData);
+
+ return (
+
+ {overallScheduleData ? (
+ <>
+
+
+ 현재까지
+
+
+ {memberCount.toString()}명
+
+
+ 이 입력했어요
+
+
+
+ {totalUserNames &&
+ totalUserNames.map((name, idx) => (
+
+ {name}
+ {idx !== totalUserNames.length - 1 ? ',' : ''}
+
+ ))}
+
+
+
+ {!timeSlotUserNames ? (
+
+
+ 블럭을 선택하면 해당 시간대에 참여가능한
+
+
+ 인원을 확인할 수 있어요
+
+
+ ) : (
+ timeSlotUserNames.map((name, idx) => (
+
+ {name}
+ {idx !== timeSlotUserNames.length - 1 ? ',' : ''}
+
+ ))
+ )}
+
+ >
+ ) : (
+
+
+
+ )}
+
+ );
+};
+
+export default OverallSchedule;
+
+const UserNameWrapper = styled.aside`
+ display: flex;
+ position: fixed;
+ bottom: 4.4rem;
+ flex-wrap: wrap;
+ justify-content: center;
+ border: 1px solid ${({ theme }) => theme.colors.grey5};
+ border-radius: 0.8rem;
+ background: ${({ theme }) => theme.colors.grey9};
+ width: 33.5rem;
+ min-height: 8.3rem;
+ text-align: center;
+ color: ${({ theme }) => theme.colors.white};
+`;
+
+const OverallScheduleWrapper = styled.main`
+ margin-bottom: 16.1rem;
+`;
+
+const TextOneLine = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 3.7rem;
+ width: 100%;
+`;
+
+const TotalUserNames = styled.div`
+ display: flex;
+ margin-top: 1.2rem;
+ margin-bottom: 2.4rem;
+`;
+
+const LoadingWrapper = styled.div`
+ position: relative;
+ top: 25rem;
+ width: 100%;
+`;
+
+const TextTwoLine = styled.div`
+ display:flex;
+ flex-direction:column;
+ align-items: center;
+ justify-content:center;
+`
\ No newline at end of file
diff --git a/src/pages/OverallSchedule/components/Column.tsx b/src/pages/legacy/overallSchedule/components/Column.tsx
similarity index 100%
rename from src/pages/OverallSchedule/components/Column.tsx
rename to src/pages/legacy/overallSchedule/components/Column.tsx
diff --git a/src/pages/OverallSchedule/components/Row.tsx b/src/pages/legacy/overallSchedule/components/Row.tsx
similarity index 100%
rename from src/pages/OverallSchedule/components/Row.tsx
rename to src/pages/legacy/overallSchedule/components/Row.tsx
diff --git a/src/pages/OverallSchedule/components/TimeTable.tsx b/src/pages/legacy/overallSchedule/components/TimeTable.tsx
similarity index 100%
rename from src/pages/OverallSchedule/components/TimeTable.tsx
rename to src/pages/legacy/overallSchedule/components/TimeTable.tsx
diff --git a/src/pages/OverallSchedule/utils/getFormattedAvailableDateTimes.ts b/src/pages/legacy/overallSchedule/utils/getFormattedAvailableDateTimes.ts
similarity index 100%
rename from src/pages/OverallSchedule/utils/getFormattedAvailableDateTimes.ts
rename to src/pages/legacy/overallSchedule/utils/getFormattedAvailableDateTimes.ts
diff --git a/src/pages/OverallSchedule/utils/setUserNames.ts b/src/pages/legacy/overallSchedule/utils/setUserNames.ts
similarity index 100%
rename from src/pages/OverallSchedule/utils/setUserNames.ts
rename to src/pages/legacy/overallSchedule/utils/setUserNames.ts
diff --git a/src/pages/legacy/selectSchedule/SelectModal.tsx b/src/pages/legacy/selectSchedule/SelectModal.tsx
index b8320c87..484f8e8e 100644
--- a/src/pages/legacy/selectSchedule/SelectModal.tsx
+++ b/src/pages/legacy/selectSchedule/SelectModal.tsx
@@ -1,30 +1,27 @@
-import React, { Dispatch, SetStateAction, useEffect } from 'react';
-
-import { scheduleAtom, userNameAtom } from 'atoms/atom';
+import { userNameAtom } from 'atoms/atom';
import { isAxiosError } from 'axios';
import Text from 'components/atomComponents/Text';
import { ExitIc } from 'components/Icon/icon';
+import { useSelectContext } from 'pages/selectSchedule/contexts/useSelectContext';
+import { formatHostScheduleScheme, formatMemberScheduleScheme } from 'pages/selectSchedule/utils';
import { useNavigate, useParams } from 'react-router';
-import { useRecoilState, useRecoilValue } from 'recoil';
+import { useRecoilValue } from 'recoil';
import styled from 'styled-components/macro';
import { theme } from 'styles/theme';
-import { hostAvailableApi, userAvailableApi } from 'utils/apis/createHostAvailableSchedule';
-
-import { ScheduleStates } from './types/Schedule';
-import { transformHostScheduleType, transformUserScheduleType } from './utils/changeApiReq';
+import { hostAvailableApi, userAvailableApi } from 'utils/apis/legacy/createHostAvailableSchedule';
interface ModalProps {
- setShowModal: Dispatch>;
+ setShowModal: (isModalOpen: boolean) => void;
}
function SelectModal({ setShowModal }: ModalProps) {
- const [scheduleList, setScheduleList] = useRecoilState(scheduleAtom);
+ const { selectedSlots } = useSelectContext();
const userName = useRecoilValue(userNameAtom);
const navigate = useNavigate();
const { auth, meetingId } = useParams();
- const updateScheduleType = transformHostScheduleType(scheduleList);
- const updateMemberScheduleType = transformUserScheduleType(scheduleList, userName);
+ const updateScheduleType = formatHostScheduleScheme(selectedSlots);
+ const updateMemberScheduleType = formatMemberScheduleScheme(selectedSlots, userName);
const postHostAvailableApi = async () => {
try {
@@ -117,7 +114,7 @@ export default SelectModal;
const ReturnModalWrpper = styled.div`
display: flex;
- position: absolute;
+ position: fixed;
top: 0;
left: 0;
flex-direction: column;
@@ -164,12 +161,15 @@ const MentContainer = styled.div`
`;
const ModalMent = styled.span`
- color: ${({ theme }) => theme.colors.white};
- ${({ theme }) => theme.fonts.body2};
+ margin-top: 2.4rem;
+ margin-bottom: 0.8rem;
+
width: 14.4rem;
+
text-align: center;
- margin-bottom: 0.8rem;
- margin-top: 2.4rem;
+
+ color: ${({ theme }) => theme.colors.white};
+ ${({ theme }) => theme.fonts.body2};
`;
const ModalHighlight = styled.span`
diff --git a/src/pages/legacy/selectSchedule/SelectPriorityPage.tsx b/src/pages/legacy/selectSchedule/SelectPriorityPage.tsx
index 8a4f0358..fbf5e6ed 100644
--- a/src/pages/legacy/selectSchedule/SelectPriorityPage.tsx
+++ b/src/pages/legacy/selectSchedule/SelectPriorityPage.tsx
@@ -1,19 +1,18 @@
import React, { useEffect, useState } from 'react';
-
import { availableDatesAtom, preferTimesAtom, scheduleAtom } from 'atoms/atom';
-import axios from 'axios';
+import { useNavigate, useParams } from 'react-router-dom';
+
import Button from 'components/atomComponents/Button';
-import Text from 'components/atomComponents/Text';
+import Header from 'components/moleculesComponents/Header';
import PriorityDropdown from 'components/legacy/scheduleComponents/components/PriorityDropdown';
+import SelectModal from './SelectModal';
+import Text from 'components/atomComponents/Text';
import TimeTable from 'components/legacy/scheduleComponents/components/TimeTable';
-import Header from 'components/moleculesComponents/Header';
-import { useNavigate, useParams } from 'react-router-dom';
-import { useRecoilState } from 'recoil';
+import { availableScheduleOptionApi } from 'utils/apis/legacy/availbleScheduleOptionApi';
+import axios from 'axios';
import styled from 'styled-components';
import { theme } from 'styles/theme';
-import { availableScheduleOptionApi } from 'utils/apis/availbleScheduleOptionApi';
-
-import SelectModal from './SelectModal';
+import { useRecoilState } from 'recoil';
const SelectSchedulePriority = () => {
const [availableDates, setAvailableDates] = useRecoilState(availableDatesAtom);
diff --git a/src/pages/legacy/selectSchedule/SelectSchedulePage.tsx b/src/pages/legacy/selectSchedule/SelectSchedulePage.tsx
index 5084ac55..93648246 100644
--- a/src/pages/legacy/selectSchedule/SelectSchedulePage.tsx
+++ b/src/pages/legacy/selectSchedule/SelectSchedulePage.tsx
@@ -1,21 +1,20 @@
import React, { useEffect, useRef, useState } from 'react';
-
import { availableDatesAtom, preferTimesAtom, scheduleAtom } from 'atoms/atom';
-import axios from 'axios';
+import { useNavigate, useParams } from 'react-router-dom';
+
import Button from 'components/atomComponents/Button';
-import Text from 'components/atomComponents/Text';
-import { PlusIc } from 'components/Icon/icon';
-import TimeTable from 'components/legacy/scheduleComponents/components/TimeTable';
import Header from 'components/moleculesComponents/Header';
-import { useNavigate, useParams } from 'react-router-dom';
-import { useRecoilState } from 'recoil';
import { MeetingDetail } from 'src/types/availbleScheduleType';
+import { PlusIc } from 'components/Icon/icon';
+import { ScheduleStates } from './types/Schedule';
+import SelectSchedule from './components/SelectSchedule';
+import Text from 'components/atomComponents/Text';
+import TimeTable from 'components/legacy/scheduleComponents/components/TimeTable';
+import { availableScheduleOptionApi } from 'utils/apis/legacy/availbleScheduleOptionApi';
+import axios from 'axios';
import styled from 'styled-components/macro';
import { theme } from 'styles/theme';
-import { availableScheduleOptionApi } from 'utils/apis/availbleScheduleOptionApi';
-
-import SelectSchedule from './components/SelectSchedule';
-import { ScheduleStates } from './types/Schedule';
+import { useRecoilState } from 'recoil';
function SelectSchedulePage() {
// 가능시간 선택지 - 날짜
diff --git a/src/pages/legacy/selectSchedule/utils/changeApiReq.ts b/src/pages/legacy/selectSchedule/utils/changeApiReq.ts
deleted file mode 100644
index 2aac57f0..00000000
--- a/src/pages/legacy/selectSchedule/utils/changeApiReq.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import {
- HostAvailableSchduleRequestType,
- UserAvailableScheduleRequestType,
-} from 'src/types/createAvailableSchduleType';
-
-import { ScheduleStates } from '../types/Schedule';
-
-export const transformHostScheduleType = (
- scheduleList: ScheduleStates[],
-): (HostAvailableSchduleRequestType | null)[] => {
- return scheduleList.map((item) => {
- const matchedResult = item.date.match(/(\d+)월 (\d+)일 \((\S+)\)/);
- if (!matchedResult) {
- return null;
- }
- const [, month, day, dateOfWeek] = matchedResult;
-
- return {
- id: item.id.toString(),
- month: month.padStart(2, '0'),
- day: day.padStart(2, '0'),
- dayOfWeek: dateOfWeek,
- startTime: item.startTime,
- endTime: item.endTime,
- priority: item.priority,
- };
- });
-};
-
-export const transformUserScheduleType = (
- scheduleList: ScheduleStates[],
- meetInfo: string,
-): UserAvailableScheduleRequestType => {
- const availableTimes = scheduleList.map((item) => {
- const matchedResult = item.date.match(/(\d+)월 (\d+)일 \((\S+)\)/);
- if (!matchedResult) {
- return {
- id: '',
- month: '',
- day: '',
- dayOfWeek: '',
- startTime: '',
- endTime: '',
- priority: 0,
- };
- }
- // const [, month, day, dateOfWeek]: string[] | null = item.date.match(
- // /(\d+)월 (\d+)일 \((\S+)\)/,
- // );
- const [, month, day, dateOfWeek] = matchedResult;
- return {
- id: item.id.toString(),
- month: month.padStart(2, '0'),
- day: day.padStart(2, '0'),
- dayOfWeek: dateOfWeek,
- startTime: item.startTime,
- endTime: item.endTime,
- priority: item.priority,
- };
- });
-
- const final: UserAvailableScheduleRequestType = {
- name: meetInfo,
- availableTimes,
- };
- return final;
-};
diff --git a/src/pages/onBoarding/OnBoarding.tsx b/src/pages/onBoarding/OnBoarding.tsx
index aafc912a..3457f444 100644
--- a/src/pages/onBoarding/OnBoarding.tsx
+++ b/src/pages/onBoarding/OnBoarding.tsx
@@ -2,19 +2,19 @@ import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
+import { Autoplay, Navigation, Pagination } from 'swiper/modules';
+import { Swiper, SwiperSlide } from 'swiper/react';
+
+import Button from 'components/atomComponents/Button';
import CardPng from 'assets/images/card.png';
+import Explain from './components/Explain';
+import Header from 'components/moleculesComponents/Header';
import InsertPng from 'assets/images/insert.png';
+import { Link } from 'react-router-dom';
import MakePng from 'assets/images/make.png';
import PointPng from 'assets/images/point.png';
-import Button from 'components/atomComponents/Button';
import Text from 'components/atomComponents/Text';
-import Header from 'components/moleculesComponents/Header';
-import { Link } from 'react-router-dom';
import styled from 'styled-components/macro';
-import { Autoplay, Navigation, Pagination } from 'swiper/modules';
-import { Swiper, SwiperSlide } from 'swiper/react';
-
-import Explain from './components/Explain';
const slides = [
{
@@ -60,7 +60,7 @@ function OnBoarding() {
pagination={{
clickable: true,
}}
- navigation={true}
+ navigation={false}
modules={[Autoplay, Pagination, Navigation]}
className="mySwiper"
>
diff --git a/src/pages/selectSchedule/SelectSchedule.tsx b/src/pages/selectSchedule/SelectSchedule.tsx
index 4712222f..6e8dadc1 100644
--- a/src/pages/selectSchedule/SelectSchedule.tsx
+++ b/src/pages/selectSchedule/SelectSchedule.tsx
@@ -1,70 +1,62 @@
import { useState } from 'react';
-import { SelectedSlotType, TimetableContext } from 'components/timetableComponents/context';
+import Header from 'components/moleculesComponents/Header';
+import TitleComponents from 'components/moleculesComponents/TitleComponents';
import { getAvailableTimes } from 'components/timetableComponents/utils';
+import { useParams } from 'react-router-dom';
+import styled from 'styled-components';
+import { useGetTimetable } from 'utils/apis/useGetTimetable';
-import SelectionSlots from './components/SelectionSlots';
-import Timetable from '../../components/timetableComponents/Timetable';
+import Description from './components/Description';
+import SelectScheduleTable from './components/SelectScheduleTable';
+import { ScheduleStepContext } from './contexts/useScheduleStepContext';
+import { ScheduleStepType } from './types';
+import { TITLES } from './utils';
-// api 연결 후 지울 것
-export type DateType = {
- month: string | undefined;
- day: string | undefined;
- dayOfWeek: string | undefined;
-};
-
-const availableDates: DateType[] = [
- {
- month: '6',
- day: '20',
- dayOfWeek: '목',
- },
- {
- month: '6',
- day: '21',
- dayOfWeek: '금',
- },
- {
- month: '6',
- day: '22',
- dayOfWeek: '토',
- },
- {
- month: '6',
- day: '23',
- dayOfWeek: '일',
- },
-];
-
-export type SlotType = {
- startTime: string;
- endTime: string;
-};
-
-const preferTimes: SlotType = {
- startTime: '06:00',
- endTime: '24:00',
-};
+function SelectSchedule() {
+ const [scheduleStep, setScheduleStep] = useState('selectTimeSlot');
+ const { meetingId } = useParams();
+ const { data, isLoading } = useGetTimetable(meetingId);
-const timeSlots = getAvailableTimes(preferTimes);
+ // 시간대 선택 단계가 없어질 것을 고려하여 상수값을 설정해놓음
+ const PREFER_TIMES = { startTime: '06:00', endTime: '24:00' };
-function SelectSchedule() {
- const [startSlot, setStartSlot] = useState(undefined);
- const [selectedSlots, setSelectedSlots] = useState({});
return (
-
-
- {({ date, timeSlots }) => }
-
-
+
+
+
+ {!isLoading &&
+ data && (
+ <>
+ {scheduleStep === 'selectTimeSlot' && (
+
+ )}
+
+
+ >
+ )}
+
+
);
}
export default SelectSchedule;
+
+const SelectScheduleWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 16.4rem;
+`;
diff --git a/src/pages/selectSchedule/components/Description.tsx b/src/pages/selectSchedule/components/Description.tsx
new file mode 100644
index 00000000..34e88e25
--- /dev/null
+++ b/src/pages/selectSchedule/components/Description.tsx
@@ -0,0 +1,84 @@
+import { DURATION, PLACE } from '../utils';
+
+import Text from 'components/atomComponents/Text';
+import styled from 'styled-components';
+import { theme } from 'styles/theme';
+
+interface DescriptionProps {
+ duration: keyof typeof DURATION;
+ place: keyof typeof PLACE;
+ placeDetail?: string;
+}
+
+function Description({ duration: durationOg, place: placeOg, placeDetail }: DescriptionProps) {
+ const duration = DURATION[durationOg];
+ const place = PLACE[placeOg];
+ return (
+
+
+ {place ? (
+ <>
+
+
+ 회의는
+
+
+ {duration}
+
+
+ {'동안'}
+
+
+
+
+ {place}
+
+ {placeDetail && (
+
+ {`(${placeDetail})`}
+
+ )}
+
+ {'으로 진행될 예정이에요!'}
+
+
+ >
+ ) : (
+
+
+ 회의는
+
+
+ {duration}
+
+
+ {'동안 진행될 예정이에요!'}
+
+
+ )}
+
+
+ );
+}
+
+export default Description;
+
+const DescriptionWrapper = styled.div`
+ display: flex;
+ position: relative;
+ margin: 2rem 0;
+`;
+
+const Texts = styled.div`
+ display: flex;
+ flex-direction: column;
+ border-radius: 0.8rem;
+ background-color: ${theme.colors.grey9};
+ padding: 1.5rem 2.4rem;
+ width: 33.5rem;
+`;
+const OneLine = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+`;
diff --git a/src/pages/selectSchedule/components/SelectScheduleTable.tsx b/src/pages/selectSchedule/components/SelectScheduleTable.tsx
new file mode 100644
index 00000000..4134e8bc
--- /dev/null
+++ b/src/pages/selectSchedule/components/SelectScheduleTable.tsx
@@ -0,0 +1,58 @@
+import { useState } from 'react';
+
+import Timetable from 'components/timetableComponents/Timetable';
+import { ColumnStructure, TimetableStructure } from 'components/timetableComponents/types';
+
+import PriorityColumn from './selectPriority/PriorityColumn';
+import PriorityCta from './selectPriority/PriorityCta';
+import PriorityDropdown from './selectPriority/PriorityDropdown';
+import SelectionColumn from './selectTimeSlot/SelectionColumn';
+import TimeSlotCta from './selectTimeSlot/TimeSlotCta';
+import { useScheduleStepContext } from '../contexts/useScheduleStepContext';
+import { SelectContext, SelectedSlotType } from '../contexts/useSelectContext';
+import { StepSlotsType, StepbottomItemsType } from '../types';
+
+function SelectScheduleTable({ timeSlots, availableDates }: TimetableStructure) {
+ const [startSlot, setStartSlot] = useState(undefined);
+ const [selectedSlots, setSelectedSlots] = useState({});
+
+ const { scheduleStep } = useScheduleStepContext();
+
+ const stepColumns: StepSlotsType = {
+ selectTimeSlot: ({ date, timeSlots }: ColumnStructure) => (
+
+ ),
+ selectPriority: ({ date, timeSlots }: ColumnStructure) => (
+
+ ),
+ };
+ const stepColumn = stepColumns[scheduleStep];
+
+ const bottomItems: StepbottomItemsType = {
+ selectTimeSlot: ,
+ selectPriority: (
+ <>
+
+
+ >
+ ),
+ };
+ const bottomItem = bottomItems[scheduleStep];
+
+ return (
+
+
+ {stepColumn}
+
+
+ );
+}
+
+export default SelectScheduleTable;
diff --git a/src/pages/selectSchedule/components/SelectionSlots.tsx b/src/pages/selectSchedule/components/SelectionSlots.tsx
deleted file mode 100644
index 3e23acaa..00000000
--- a/src/pages/selectSchedule/components/SelectionSlots.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useTimetableContext } from '../../../components/timetableComponents/context';
-import Slot from '../../../components/timetableComponents/parts/Slot';
-
-interface SelectionSlotsProps {
- date: string;
- timeSlots: string[];
-}
-
-function SelectionSlots({ date, timeSlots }: SelectionSlotsProps) {
- const { selectedSlots } = useTimetableContext();
- const selectedSlotsPerDate = Object.entries(selectedSlots).filter(
- ([, slot]) => slot.date === date,
- );
-
- return (
- <>
- {timeSlots.map((timeSlot) => {
- const belongingEntry = selectedSlotsPerDate.find(
- ([, { startSlot, endSlot }]) => timeSlot >= startSlot && timeSlot <= endSlot,
- );
-
- const selectedEntryId = belongingEntry ? parseInt(belongingEntry[0]) : undefined;
-
- return (
-
- );
- })}
- >
- );
-}
-
-export default SelectionSlots;
diff --git a/src/pages/selectSchedule/components/selectPriority/PriorityColumn.tsx b/src/pages/selectSchedule/components/selectPriority/PriorityColumn.tsx
new file mode 100644
index 00000000..7c30d2b4
--- /dev/null
+++ b/src/pages/selectSchedule/components/selectPriority/PriorityColumn.tsx
@@ -0,0 +1,65 @@
+import Text from 'components/atomComponents/Text';
+import { ColumnStructure } from 'components/timetableComponents/types';
+import { useSelectContext } from 'pages/selectSchedule/contexts/useSelectContext';
+import { theme } from 'styles/theme';
+
+import Slot from '../../../../components/timetableComponents/parts/Slot';
+
+function PriorityColumn({ date, timeSlots }: ColumnStructure) {
+ const { selectedSlots } = useSelectContext();
+ const selectedSlotsPerDate = Object.entries(selectedSlots).filter(
+ ([, slot]) => slot.date === date,
+ );
+
+ const getPriorityColumntyle = (selectedEntryId?: number, priority?: number) => {
+ const isSelectedSlot = selectedEntryId !== undefined;
+ const slotColor =
+ priority === 1
+ ? theme.colors.main1
+ : priority === 2
+ ? theme.colors.main2
+ : priority === 3
+ ? theme.colors.main3
+ : theme.colors.grey6;
+
+ return `
+ ${isSelectedSlot ? `background-color:${slotColor}` : `background-color: transparent`}
+ `;
+ };
+
+ return (
+ <>
+ {timeSlots.map((timeSlot) => {
+ const belongingEntry = selectedSlotsPerDate.find(
+ ([, { startSlot, endSlot }]) => timeSlot >= startSlot && timeSlot <= endSlot,
+ );
+
+ let isFirstSlot = false;
+ if (belongingEntry !== undefined) {
+ if (selectedSlots[parseInt(belongingEntry[0])].startSlot === timeSlot) {
+ isFirstSlot = true;
+ }
+ }
+
+ const selectedEntryId = belongingEntry ? parseInt(belongingEntry[0]) : undefined;
+ const slotId = `${date}/${timeSlot}`;
+ const priority =
+ selectedEntryId !== undefined ? selectedSlots[selectedEntryId].priority : 0;
+
+ return (
+
+
+ {isFirstSlot && priority !== 0 ? priority : ''}
+
+
+ );
+ })}
+ >
+ );
+}
+
+export default PriorityColumn;
diff --git a/src/pages/selectSchedule/components/selectPriority/PriorityCta.tsx b/src/pages/selectSchedule/components/selectPriority/PriorityCta.tsx
new file mode 100644
index 00000000..61709cdd
--- /dev/null
+++ b/src/pages/selectSchedule/components/selectPriority/PriorityCta.tsx
@@ -0,0 +1,54 @@
+import { useState } from 'react';
+
+import Button from 'components/atomComponents/Button';
+import Text from 'components/atomComponents/Text';
+import SelectModal from 'pages/legacy/selectSchedule/SelectModal';
+import styled from 'styled-components';
+
+function PriorityCta() {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ return (
+ <>
+
+
+
+
+ {isModalOpen && }
+ >
+ );
+}
+
+export default PriorityCta;
+
+const BtnDim = styled.div`
+ display: flex;
+ position: fixed;
+ bottom: 0;
+ gap: 1rem;
+ align-items: end;
+ justify-content: center;
+ z-index: 2;
+
+ margin-top: 3rem;
+ background: ${({ theme }) => theme.colors.dim_gradient};
+ padding-bottom: 2.9rem;
+
+ width: 100%;
+ height: 16.4rem;
+
+ pointer-events: none;
+`;
diff --git a/src/pages/selectSchedule/components/selectPriority/PriorityDropdown.tsx b/src/pages/selectSchedule/components/selectPriority/PriorityDropdown.tsx
new file mode 100644
index 00000000..2631b4e2
--- /dev/null
+++ b/src/pages/selectSchedule/components/selectPriority/PriorityDropdown.tsx
@@ -0,0 +1,270 @@
+import { useState } from 'react';
+
+import Text from 'components/atomComponents/Text';
+import { Circle1Ic, Circle2Ic, Circle3Ic, DropDownIc, DropUpIc } from 'components/Icon/icon';
+import { addMinutes } from 'components/timetableComponents/utils';
+import {
+ SelectedSlotType,
+ SelectSlotType,
+ useSelectContext,
+} from 'pages/selectSchedule/contexts/useSelectContext';
+import styled from 'styled-components/macro';
+import { theme } from 'styles/theme';
+
+/**
+ *
+ * @desc 기존의 우선순위 Dropdown 컴포넌트를 그대로 가져와서, 따로 리팩토링 없이 새로운 시간표 컴포넌트에 맞게 적용되도록 수정한 컴포넌트입니다.
+ */
+
+function PriorityDropdown() {
+ const { selectedSlots, setSelectedSlots } = useSelectContext();
+ const [timeSelect, setTimeSelect] = useState([false, false, false]);
+
+ const formatDate = (date: string) => {
+ const [month, day, dayOfWeek] = date.split('/');
+ return `${month}/${day}(${dayOfWeek})`;
+ };
+
+ let defaultInput1 = '';
+ let defaultInput2 = '';
+ let defaultInput3 = '';
+ for (const key in selectedSlots) {
+ const item = selectedSlots[key];
+ const date = formatDate(item.date);
+ const endSlot = addMinutes(item.endSlot, 30);
+ if (item.priority === 1) {
+ defaultInput1 = `${date} ${item.startSlot}~${endSlot}`;
+ } else if (item.priority === 2) {
+ defaultInput2 = `${date} ${item.startSlot}~${endSlot}`;
+ } else if (item.priority === 3) {
+ defaultInput3 = `${date} ${item.startSlot}~${endSlot}`;
+ }
+ }
+ const [input_, setInput] = useState([defaultInput1, defaultInput2, defaultInput3]);
+ const handleDropdown = (i: number) => {
+ if (!timeSelect[i]) {
+ setTimeSelect((prevState) => {
+ const updatedTimeSelect = prevState.map((value, index) => index === i);
+ return updatedTimeSelect;
+ });
+ } else {
+ setTimeSelect((prevState) => {
+ const updatedTimeSelect = [...prevState];
+ updatedTimeSelect[i] = !updatedTimeSelect[i];
+ return updatedTimeSelect;
+ });
+ }
+ };
+
+ const handlePriority = (i: number, item: SelectSlotType, itemKey: string) => {
+ let temp: 0 | 1 | 2 | 3 = 0;
+ switch (i) {
+ case 0:
+ temp = 1;
+ break;
+ case 1:
+ temp = 2;
+ break;
+ case 2:
+ temp = 3;
+ break;
+ default:
+ temp = 0;
+ break;
+ }
+
+ setSelectedSlots((prev: SelectedSlotType) => {
+ const updatedSelectedSlots = Object.entries(prev).map(([_, value]) => {
+ if (value.priority === temp) {
+ return { ...value, priority: 0 };
+ }
+ return value;
+ });
+ return updatedSelectedSlots;
+ });
+
+ setSelectedSlots((prev: SelectedSlotType) => {
+ const updatedSelectedSlots = Object.entries(prev).map(([key, value]) => {
+ if (key === itemKey) {
+ return { ...value, priority: temp };
+ }
+ return value;
+ });
+ return updatedSelectedSlots;
+ });
+
+ setInput((prev) => {
+ const updatedInput = [...prev];
+ const endSlot = addMinutes(item.endSlot, 30);
+ const date = formatDate(item.date);
+ if (i === 0) {
+ updatedInput[i] = `${date} ${item.startSlot}~${endSlot}`;
+ } else if (i === 1) {
+ updatedInput[i] = `${date} ${item.startSlot}~${endSlot}`;
+ } else if (i === 2) {
+ updatedInput[i] = `${date} ${item.startSlot}~${endSlot}`;
+ } else {
+ updatedInput[i] = 'error';
+ }
+ return updatedInput;
+ });
+ handleDropdown(i);
+ };
+
+ return (
+
+ {Object.entries(selectedSlots).map(([key, _], i) => {
+ return i < 3 ? (
+
+
+
+
+ {`${i + 1}`}순위
+
+
+ {i === 0 ? (
+
+ ) : i === 1 ? (
+
+ ) : i === 2 ? (
+
+ ) : (
+
+ )}
+
+
+ handleDropdown(i)}
+ value={input_[i]}
+ />
+
+ {timeSelect[i] ? (
+
+ {' '}
+
+ ) : (
+
+
+
+ )}
+
+ {timeSelect[i] && (
+
+ {Object.entries(selectedSlots).map(
+ ([key, value]) =>
+ !value.priority && (
+ handlePriority(i, value, key)}>
+
+ {formatDate(value.date)} {value.startSlot}~{addMinutes(
+ value.endSlot,
+ 30,
+ )}
+
+
+ ),
+ )}
+
+ )}
+
+
+ ) : (
+
+ );
+ })}
+
+ );
+}
+const PriorityDropdownWrapper = styled.div`
+ display: flex;
+
+ flex-direction: column;
+ gap: 1.2rem;
+ justify-content: start;
+
+ margin-top: 3rem;
+ margin-bottom: 7.5rem;
+ width: 100%;
+ height: 18rem;
+`;
+
+const PriorityDropdownSection = styled.div`
+ display: flex;
+ gap: 1.3rem;
+ justify-content: space-between;
+ width: 100%;
+ height: 5.2rem;
+`;
+const CircleWrapper = styled.div`
+ position: relative;
+ width: 4.8rem;
+ height: 4.8rem;
+`;
+
+const TextWrapper = styled.div`
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+`;
+const Circle1Icon = styled(Circle1Ic)``;
+const Circle2Icon = styled(Circle2Ic)``;
+const Circle3Icon = styled(Circle3Ic)``;
+
+const InputWrapper = styled.div`
+ position: relative;
+`;
+const TimeInput = styled.input<{ $drop: boolean }>`
+ appearance: none;
+ outline: none;
+ border: none;
+ border-radius: 0.8rem;
+ border-bottom-left-radius: ${(props) => (props.$drop ? '0rem' : '0.8rem')};
+ border-bottom-right-radius: ${(props) => (props.$drop ? '0rem' : '0.8rem')};
+
+ background-color: ${({ theme }) => theme.colors.grey7};
+ ${({ theme }) => theme.fonts.button1};
+ cursor: pointer;
+ padding-left: 2rem;
+ width: 27.4rem;
+ height: 5.2rem;
+ color: ${({ theme }) => theme.colors.white};
+`;
+const DropDownIconWrapper = styled.div`
+ position: absolute;
+ top: 36%;
+ right: 1rem;
+ cursor: pointer;
+`;
+const DropdownWrapper = styled.div`
+ position: absolute;
+ top: 5.2rem;
+ z-index: 2;
+ border-radius: 0rem 0rem 0.8rem 0.8rem;
+ background-color: ${({ theme }) => theme.colors.grey6};
+ width: 27.4rem;
+ height: fit-content;
+ max-height: 15.6rem;
+
+ overflow-x: hidden;
+ overflow-y: auto;
+`;
+
+const DropDownItem = styled.div`
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid ${({ theme }) => theme.colors.grey7};
+ background-color: ${({ theme }) => theme.colors.grey6};
+
+ cursor: pointer;
+
+ width: 27.4rem;
+ height: 5.2rem;
+`;
+
+export default PriorityDropdown;
diff --git a/src/pages/selectSchedule/components/selectTimeSlot/SelectionColumn.tsx b/src/pages/selectSchedule/components/selectTimeSlot/SelectionColumn.tsx
new file mode 100644
index 00000000..17f97f40
--- /dev/null
+++ b/src/pages/selectSchedule/components/selectTimeSlot/SelectionColumn.tsx
@@ -0,0 +1,51 @@
+import { ColumnStructure } from 'components/timetableComponents/types';
+import { useSelectContext } from 'pages/selectSchedule/contexts/useSelectContext';
+import { theme } from 'styles/theme';
+
+import useSlotSeletion from './hooks/useSlotSelection';
+import Slot from '../../../../components/timetableComponents/parts/Slot';
+
+function SelectionColumn({ date, timeSlots }: ColumnStructure) {
+ const { selectedSlots } = useSelectContext();
+ const selectedSlotsPerDate = Object.entries(selectedSlots).filter(
+ ([, slot]) => slot.date === date,
+ );
+
+ const { startSlot, onClickSlot } = useSlotSeletion();
+
+ const getTimeSlotStyle = (slotId: string, selectedEntryId?: number) => {
+ const isStartSlot = slotId === startSlot;
+ const isSelectedSlot = selectedEntryId !== undefined;
+
+ return `
+ cursor:pointer;
+ ${isStartSlot && `border: 1px dashed ${theme.colors.main5}`};
+ ${
+ isSelectedSlot ? `background-color: ${theme.colors.main1}` : `background-color: transparent`
+ };
+ `;
+ };
+
+ return (
+ <>
+ {timeSlots.map((timeSlot) => {
+ const belongingEntry = selectedSlotsPerDate.find(
+ ([, { startSlot, endSlot }]) => timeSlot >= startSlot && timeSlot <= endSlot,
+ );
+ const selectedEntryId = belongingEntry ? parseInt(belongingEntry[0]) : undefined;
+ const slotId = `${date}/${timeSlot}`;
+
+ return (
+ onClickSlot(slotId, selectedEntryId)}
+ />
+ );
+ })}
+ >
+ );
+}
+
+export default SelectionColumn;
diff --git a/src/pages/selectSchedule/components/selectTimeSlot/TimeSlotCta.tsx b/src/pages/selectSchedule/components/selectTimeSlot/TimeSlotCta.tsx
new file mode 100644
index 00000000..d021e1aa
--- /dev/null
+++ b/src/pages/selectSchedule/components/selectTimeSlot/TimeSlotCta.tsx
@@ -0,0 +1,42 @@
+import Button from 'components/atomComponents/Button';
+import Text from 'components/atomComponents/Text';
+import { useScheduleStepContext } from 'pages/selectSchedule/contexts/useScheduleStepContext';
+import { useSelectContext } from 'pages/selectSchedule/contexts/useSelectContext';
+import styled from 'styled-components';
+
+function TimeSlotCta() {
+ const { setScheduleStep } = useScheduleStepContext();
+ const { selectedSlots } = useSelectContext();
+ const isValidSelection = Object.keys(selectedSlots).length !== 0;
+ return (
+
+
+
+ );
+}
+
+export default TimeSlotCta;
+
+const BtnDim = styled.div`
+ display: flex;
+ position: fixed;
+ bottom: 0;
+ align-items: end;
+ justify-content: center;
+
+ margin-top: 3rem;
+ background: ${({ theme }) => theme.colors.dim_gradient};
+ padding-bottom: 2.9rem;
+
+ width: 100%;
+ height: 16.4rem;
+
+ pointer-events: none;
+`;
diff --git a/src/components/timetableComponents/hooks/useSlotSelection.ts b/src/pages/selectSchedule/components/selectTimeSlot/hooks/useSlotSelection.ts
similarity index 80%
rename from src/components/timetableComponents/hooks/useSlotSelection.ts
rename to src/pages/selectSchedule/components/selectTimeSlot/hooks/useSlotSelection.ts
index 6b171a69..abfadf2a 100644
--- a/src/components/timetableComponents/hooks/useSlotSelection.ts
+++ b/src/pages/selectSchedule/components/selectTimeSlot/hooks/useSlotSelection.ts
@@ -1,7 +1,7 @@
-import { useTimetableContext } from '../context'
+import { useSelectContext } from 'pages/selectSchedule/contexts/useSelectContext';
const useSlotSeletion = () => {
- const {startSlot, setStartSlot, selectedSlots, setSelectedSlots} = useTimetableContext();
+ const {startSlot, setStartSlot, selectedSlots, setSelectedSlots} = useSelectContext();
const handleSelectSlot = (targetSlot: string) => {
setStartSlot(targetSlot);
@@ -10,16 +10,19 @@ const useSlotSeletion = () => {
const handleCompleteSlot = (targetSlot: string) => {
const dateOfStartSlot = startSlot?.substring(0, startSlot.lastIndexOf('/'));
const dateOfTargetSlot = targetSlot.substring(0, targetSlot.lastIndexOf('/'))
- if (dateOfStartSlot === dateOfTargetSlot){
+ if (startSlot && dateOfStartSlot === dateOfTargetSlot){
const newSelectedSlot = {
date:dateOfStartSlot,
startSlot:startSlot?.substring(startSlot.lastIndexOf('/')+1),
- endSlot:targetSlot.substring(targetSlot.lastIndexOf('/')+1)
+ endSlot:targetSlot.substring(targetSlot.lastIndexOf('/')+1),
+ priority:0,
}
const keys = Object.keys(selectedSlots).map(Number)
const newKey = keys.length ? Math.max(...keys) + 1 : 0;
- setSelectedSlots({...selectedSlots, [newKey]:newSelectedSlot})
+ const newSelectedSlots = {...selectedSlots};
+ newSelectedSlots[newKey] = newSelectedSlot;
+ setSelectedSlots(newSelectedSlots)
}
setStartSlot(undefined);
}
diff --git a/src/pages/selectSchedule/contexts/useScheduleStepContext.ts b/src/pages/selectSchedule/contexts/useScheduleStepContext.ts
new file mode 100644
index 00000000..9488eaa4
--- /dev/null
+++ b/src/pages/selectSchedule/contexts/useScheduleStepContext.ts
@@ -0,0 +1,21 @@
+import { Dispatch, SetStateAction, createContext, useContext } from 'react';
+
+import { ScheduleStepType } from '../types';
+
+interface ScheduleStepContextType {
+ scheduleStep: ScheduleStepType;
+ setScheduleStep: Dispatch>;
+}
+
+export const ScheduleStepContext = createContext({
+ scheduleStep: 'selectTimeSlot',
+ setScheduleStep: () => undefined,
+});
+
+export function useScheduleStepContext() {
+ const context = useContext(ScheduleStepContext);
+ if (context == null) {
+ throw new Error('ScheduleStepContext Error');
+ }
+ return context;
+}
diff --git a/src/pages/selectSchedule/contexts/useSelectContext.ts b/src/pages/selectSchedule/contexts/useSelectContext.ts
new file mode 100644
index 00000000..2ab073d0
--- /dev/null
+++ b/src/pages/selectSchedule/contexts/useSelectContext.ts
@@ -0,0 +1,34 @@
+import { Dispatch, SetStateAction, createContext, useContext } from 'react';
+
+export interface SelectSlotType {
+ date: string;
+ startSlot: string;
+ endSlot: string;
+ priority: number;
+}
+
+export interface SelectedSlotType {
+ [key: number]: SelectSlotType;
+}
+
+interface SelectContextType {
+ startSlot: string | undefined;
+ setStartSlot: Dispatch>;
+ selectedSlots: SelectedSlotType;
+ setSelectedSlots: Dispatch>;
+}
+
+export const SelectContext = createContext({
+ startSlot: undefined,
+ setStartSlot: () => undefined,
+ selectedSlots: {},
+ setSelectedSlots: () => undefined,
+});
+
+export function useSelectContext() {
+ const context = useContext(SelectContext);
+ if (context == null) {
+ throw new Error('SelectContext Error');
+ }
+ return context;
+}
diff --git a/src/pages/selectSchedule/types.ts b/src/pages/selectSchedule/types.ts
new file mode 100644
index 00000000..54ceb704
--- /dev/null
+++ b/src/pages/selectSchedule/types.ts
@@ -0,0 +1,14 @@
+import { ReactNode } from 'react';
+
+import { ColumnStructure } from 'components/timetableComponents/types';
+
+export type ScheduleStepType = 'selectTimeSlot' | 'selectPriority';
+export type StepSlotsType = { [key in ScheduleStepType]: (props: ColumnStructure) => ReactNode };
+export type StepbottomItemsType = { [key in ScheduleStepType]: ReactNode };
+
+export interface TitlesType {
+ [key: string]: {
+ main: string;
+ sub?: string;
+ };
+}
diff --git a/src/pages/selectSchedule/utils.ts b/src/pages/selectSchedule/utils.ts
new file mode 100644
index 00000000..f05805a3
--- /dev/null
+++ b/src/pages/selectSchedule/utils.ts
@@ -0,0 +1,95 @@
+import { addMinutes } from 'components/timetableComponents/utils';
+
+import { SelectedSlotType } from './contexts/useSelectContext';
+import { TitlesType } from './types';
+
+export const DURATION = {
+ HALF: '30분',
+ HOUR: '1시간',
+ HOUR_HALF: '1시간 30분',
+ TWO_HOUR: '2시간',
+ TWO_HOUR_HALF: '2시간 30분',
+ THREE_HOUR: '3시간',
+} as const;
+
+export const PLACE = {
+ ONLINE: '온라인',
+ OFFLINE: '오프라인',
+ UNDEFINED: undefined,
+} as const;
+
+export const TITLES: TitlesType = {
+ selectTimeSlot: {
+ main: '가능한 시간대를 등록해주세요',
+ sub: '시작시간과 종료시간을 터치하여 블럭을 생성해주세요',
+ },
+ selectPriority: {
+ main: '우선순위를 입력해주세요',
+ },
+} as const;
+
+/**
+ *
+ * @desc 선택된 슬롯들의 우선순위를 0으로 초기화하는 함수
+ */
+export const resetPriorities = (selectedSlots: SelectedSlotType): SelectedSlotType => {
+ const updatedSlots: SelectedSlotType = {};
+
+ for (const key in selectedSlots) {
+ if (typeof selectedSlots[key] === 'object') {
+ updatedSlots[key] = {
+ ...selectedSlots[key],
+ priority: 0,
+ };
+ }
+ }
+
+ return updatedSlots;
+};
+
+/**
+ *
+ * @desc 방장 시간표 입력 POST를 위한 형식을 맞추는 함수
+ */
+export const formatHostScheduleScheme = (selectedSlots: SelectedSlotType) => {
+ const availableTimes = Object.keys(selectedSlots).map((key) => {
+ const slot = selectedSlots[parseInt(key)];
+ const [month, day, dayOfWeek] = slot.date.split('/');
+
+ return {
+ id: key,
+ month: month.padStart(2, '0'),
+ day: day.padStart(2, '0'),
+ dayOfWeek: dayOfWeek,
+ startTime: slot.startSlot,
+ endTime: addMinutes(slot.endSlot, 30),
+ priority: slot.priority,
+ };
+ });
+ return availableTimes;
+};
+
+/**
+ *
+ * @desc 멤버 시간표 입력 POST를 위한 형식을 맞추는 함수
+ */
+export const formatMemberScheduleScheme = (selectedSlots: SelectedSlotType, userName: string) => {
+ const availableTimes = Object.keys(selectedSlots).map((key) => {
+ const slot = selectedSlots[parseInt(key)];
+ const [month, day, dayOfWeek] = slot.date.split('/');
+
+ return {
+ id: key,
+ month: month.padStart(2, '0'),
+ day: day.padStart(2, '0'),
+ dayOfWeek: dayOfWeek,
+ startTime: slot.startSlot,
+ endTime: addMinutes(slot.endSlot, 30),
+ priority: slot.priority,
+ };
+ });
+ return {
+ name: userName,
+ availableTimes: availableTimes,
+ };
+};
diff --git a/src/utils/apis/bestMeetTimeApi.ts b/src/utils/apis/bestMeetTimeApi.ts
deleted file mode 100644
index a6ea58c7..00000000
--- a/src/utils/apis/bestMeetTimeApi.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-// import { BestMeetTimeResponse, BestMeetTimeRequest } from 'src/types/bestMeetTimeType';
-
-// import { client } from './axios';
-
-// export const BestMeetTimeApi = (BestMeetTimeRequest: BestMeetTimeRequest) => {
-// return client.post(`/meeting/${meetingId}/confirm`, BestMeetTimeRequest, {
-// headers: {
-// Authorization: `[Bearer] ${token}`,
-// },
-// });
-// };
diff --git a/src/utils/apis/availbleScheduleOptionApi.ts b/src/utils/apis/legacy/availbleScheduleOptionApi.ts
similarity index 89%
rename from src/utils/apis/availbleScheduleOptionApi.ts
rename to src/utils/apis/legacy/availbleScheduleOptionApi.ts
index 2f424339..b10400da 100644
--- a/src/utils/apis/availbleScheduleOptionApi.ts
+++ b/src/utils/apis/legacy/availbleScheduleOptionApi.ts
@@ -1,6 +1,5 @@
import { AvailableScheduleOptionResponse } from 'src/types/availbleScheduleType';
-
-import { client } from './axios';
+import { client } from '../axios';
/** 가능 시간 입력 선택지 조회 api */
export const availableScheduleOptionApi = (meetingId?: string) => {
diff --git a/src/utils/apis/createHostAvailableSchedule.ts b/src/utils/apis/legacy/createHostAvailableSchedule.ts
similarity index 88%
rename from src/utils/apis/createHostAvailableSchedule.ts
rename to src/utils/apis/legacy/createHostAvailableSchedule.ts
index c5915f09..0cc98699 100644
--- a/src/utils/apis/createHostAvailableSchedule.ts
+++ b/src/utils/apis/legacy/createHostAvailableSchedule.ts
@@ -1,12 +1,10 @@
-import { AxiosResponse } from 'axios';
import {
HostAvailableSchduleRequestType,
HostAvailableScheduleResponseType,
- UserAvailableScheduleResponseType,
UserAvailableScheduleRequestType,
+ UserAvailableScheduleResponseType,
} from 'src/types/createAvailableSchduleType';
-
-import { authClient, client } from './axios';
+import { authClient, client } from '../axios';
export const hostAvailableApi = (
meetingId: string,
diff --git a/src/utils/apis/createMeetingApi.ts b/src/utils/apis/legacy/createMeetingApi.ts
similarity index 88%
rename from src/utils/apis/createMeetingApi.ts
rename to src/utils/apis/legacy/createMeetingApi.ts
index ce62c47d..54f43623 100644
--- a/src/utils/apis/createMeetingApi.ts
+++ b/src/utils/apis/legacy/createMeetingApi.ts
@@ -1,6 +1,6 @@
import { CreateMeetingRequest, CreateMeetingResponse } from 'src/types/createMeetingType';
-import { client } from './axios';
+import { client } from '../axios';
export const createMeetingApi = (CreateMeetingRequest: CreateMeetingRequest) => {
return client.post(`/meeting`, CreateMeetingRequest);
diff --git a/src/utils/apis/cueCardAPI.ts b/src/utils/apis/legacy/cueCardAPI.ts
similarity index 75%
rename from src/utils/apis/cueCardAPI.ts
rename to src/utils/apis/legacy/cueCardAPI.ts
index f5f1d85f..fd7517ab 100644
--- a/src/utils/apis/cueCardAPI.ts
+++ b/src/utils/apis/legacy/cueCardAPI.ts
@@ -1,4 +1,4 @@
-import { client } from './axios';
+import { client } from '../axios';
export const cueCardApi = (meetingId: string) => {
return client.get(`/meeting/${meetingId}/card`);
diff --git a/src/utils/apis/overallScheduleApi.ts b/src/utils/apis/legacy/overallScheduleApi.ts
similarity index 92%
rename from src/utils/apis/overallScheduleApi.ts
rename to src/utils/apis/legacy/overallScheduleApi.ts
index 9cff94bd..50b40c6d 100644
--- a/src/utils/apis/overallScheduleApi.ts
+++ b/src/utils/apis/legacy/overallScheduleApi.ts
@@ -1,8 +1,8 @@
+import { authClient, client } from '../axios';
+
import { AvailableScheduleOptionResponse } from 'src/types/availbleScheduleType';
import { OverallScheduleResponse } from 'src/types/overallScheduleType';
-import { authClient, client } from './axios';
-
/** 가능 시간 입력 선택지 조회 api */
export const availbleScheduleOptionApi = (meetingId?: string) => {
return client.get(`/meeting/${meetingId}/schedule`);
diff --git a/src/utils/apis/useGetOverallSchedule.ts b/src/utils/apis/useGetOverallSchedule.ts
new file mode 100644
index 00000000..1e501114
--- /dev/null
+++ b/src/utils/apis/useGetOverallSchedule.ts
@@ -0,0 +1,54 @@
+import { useQuery } from '@tanstack/react-query';
+import { isAxiosError } from 'axios';
+import { useNavigate } from 'react-router-dom';
+
+import { authClient } from './axios';
+
+interface Date {
+ month: string;
+ day: string;
+ dayOfWeek: string;
+}
+
+export interface TimeSlot {
+ time: string;
+ userNames: string[];
+ colorLevel: number;
+}
+
+export interface AvailableDateTime extends Date {
+ timeSlots: TimeSlot[];
+}
+
+export interface getOverallScheduleResponse {
+ data: {
+ memberCount: number;
+ totalUserNames: string[];
+ availableDateTimes: AvailableDateTime[];
+ };
+}
+
+const getOverallSchedule = async (meetingId: string) => {
+ try {
+ const res = await authClient.get(`/meeting/${meetingId}/timetable`);
+ return res.data.data;
+ } catch (err) {
+ if (isAxiosError(err) && err.response) {
+ throw new Error(err.response.data.message);
+ }
+ }
+};
+
+export const useGetOverallSchedule = (meetingId?: string) => {
+ const navigate = useNavigate();
+ if (meetingId === undefined) {
+ navigate('/error');
+ throw new Error('잘못된 회의 아이디입니다.');
+ }
+ const { data, isLoading } = useQuery({
+ queryKey: ['getOverallSchedule', meetingId],
+ queryFn: () => getOverallSchedule(meetingId),
+ });
+
+ return { data, isLoading };
+};
diff --git a/src/utils/apis/useGetTimetable.ts b/src/utils/apis/useGetTimetable.ts
new file mode 100644
index 00000000..eb6ae73e
--- /dev/null
+++ b/src/utils/apis/useGetTimetable.ts
@@ -0,0 +1,51 @@
+import { useQuery } from '@tanstack/react-query';
+import { isAxiosError } from 'axios';
+import { DURATION, PLACE } from 'pages/selectSchedule/utils';
+import { useNavigate } from 'react-router-dom';
+
+import { client } from './axios';
+
+interface Date {
+ month: string;
+ day: string;
+ dayOfWeek: string;
+}
+interface TimeSlot {
+ startTime: string;
+ endTime: string;
+}
+
+interface getTimetableResponse {
+ data: {
+ duration: keyof typeof DURATION;
+ place: keyof typeof PLACE;
+ placeDetail: string;
+ availableDates: Date[];
+ preferTimes: TimeSlot[];
+ };
+}
+
+const getTimetable = async (meetingId: string) => {
+ try {
+ const res = await client.get(`/meeting/${meetingId}/schedule`);
+ return res.data.data;
+ } catch (err) {
+ if (isAxiosError(err) && err.response) {
+ throw new Error(err.response.data.message);
+ }
+ }
+};
+
+export const useGetTimetable = (meetingId?: string) => {
+ const navigate = useNavigate();
+ if (meetingId === undefined) {
+ navigate('/error');
+ throw new Error('잘못된 회의 아이디입니다.');
+ }
+ const { data, isLoading } = useQuery({
+ queryKey: ['getTimetable', meetingId],
+ queryFn: () => getTimetable(meetingId),
+ });
+
+ return { data, isLoading };
+};
diff --git a/yarn.lock b/yarn.lock
index 07569bdb..35168db7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1536,6 +1536,30 @@
"@svgr/hast-util-to-babel-ast" "^7.0.0"
svg-parser "^2.0.4"
+"@tanstack/query-core@5.45.0":
+ version "5.45.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.45.0.tgz#47a662d311c2588867341238960ec21dc7f0714e"
+ integrity sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==
+
+"@tanstack/query-devtools@5.37.1":
+ version "5.37.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.37.1.tgz#8dcfa1488b4f2e353be7eede6691b0ad9197183b"
+ integrity sha512-XcG4IIHIv0YQKrexTqo2zogQWR1Sz672tX2KsfE9kzB+9zhx44vRKH5si4WDILE1PIWQpStFs/NnrDQrBAUQpg==
+
+"@tanstack/react-query-devtools@^5.45.1":
+ version "5.45.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.45.1.tgz#bea7ba0ffd509f0930237c2df7feba9209f76aa6"
+ integrity sha512-4mrbk1g5jqlqh0pifZNsKzy7FtgeqgwzMICL4d6IJGayrrcrKq9K4N/OzRNbgRWrTn6YTY63qcAcKo+NJU2QMw==
+ dependencies:
+ "@tanstack/query-devtools" "5.37.1"
+
+"@tanstack/react-query@^5.45.1":
+ version "5.45.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.45.1.tgz#a0ac6bb89b4a2c2b0251f6647a0a370d86f05347"
+ integrity sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==
+ dependencies:
+ "@tanstack/query-core" "5.45.0"
+
"@types/axios@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46"