Skip to content

Commit

Permalink
[FE] 날짜만 선택하기 기능 구현 (#366)
Browse files Browse the repository at this point in the history
* refactor: 날짜만 선택 기능 추가에 의한 약속 생성 데이터 타입 추가

* feat(useMeetingType): 체크 상태에 따른 미팅 타입 판별 커스텀 훅 구현

* test(useMeetingType): 로직 검증 테스트 케이스 추가

* refactor: 약속 생성 페이지에 날짜만 선택하는 체크박스 기능 및 UI 구현

* feat: 모달 컴포넌트 구현

* test(ConfirmModal): 스토리북 작성

* refactor(useMeetingType): 파일 확장자 수정

* refactor: 모달 z-index 추가 및 content prop -> children으로 수정

* refactor(groupDates): 로직 및 반환값 수정

* feat: useConfirmModal 훅 구현

* feat(CopyLink): 컴포넌트 분리

* refactor: 입력한 약속 확인 방식 수정 및 CopyLink 컴포넌트 교체

* refactor: Calendar 이름 수정

* refactor: 툴팁 스타일 수정

* refactor: 날짜 비활성화 css 네이밍 수정

* feat(useMeetingConfirmCalendar): 날짜만 선택 시 약속 조회 훅 구현

* feat(MeetingConfirmCalendar): MeetingCalendar 기반 날짜 조회, 등록 컴포넌트 구현

* test(MeetingConfirmCalendar): 달력 기반 날짜 조회, 수정 컴포넌트 스토리북 작성

* refactor(meetingType): 타입 대문자로 수정

* test: 목 데이터 추가 및 API 요청 및 약속 수정 요청 로직 추가

* refactor: useQuery 레이어 분리

* fix: 바뀐 변수명 적용(isPrevDate -> isDisabledDate)

* refactor: useMeetingConfirmCalendar -> useCalendarPick으로 훅 네이밍 수정 및 로직 변경

* refactor(MeetingConfirmCalendar): 기존 로직 삭제 후 Picker, Viewer 구분하여 재구성

* feat: 날짜만 선택했을 때 추천해주는 옵션 카드 컴포넌트 구현

* refactor: type이 필요한 api 요청에 타입 추가 및 추가한 타입을 반영한 로직으로 수정

* refactor: 추천 데이터 타입 수정 및 끝 시간 표기 추가

* refactor: 시간 셀 position relative로 변경

* refactor: 달력 감싸는 컨테이너 요소 flex 추가

* refactor: 날짜만 선택 시, 시간 제외 컴포넌트 구현

* refactor: 약속 날짜 선택 -> 약속 후보 날짜 선택으로 텍스트 수정

* refactor: 단일 선택 날짜 조회 시, 선택된 날짜 체크 이미지로 표현

* refactor: 목데이터 타입 추가, 수정

* refactor: 약속 후보 날짜 선택 필드에 설명 추가

* refactor(CopyLink): 폴더명 수정

* refactor: Checkbox 공통 컴포넌트로 교체
  • Loading branch information
Largopie authored Sep 26, 2024
1 parent aedb159 commit bde448c
Show file tree
Hide file tree
Showing 64 changed files with 1,659 additions and 511 deletions.
3 changes: 2 additions & 1 deletion frontend/src/apis/meetingConfirm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BASE_URL } from '@constants/api';

import { fetchClient } from './_common/fetchClient';
import type { MeetingType } from './meetings';

export interface ConfirmDates {
startDate: string;
Expand All @@ -11,11 +12,11 @@ export interface ConfirmDates {

interface PostMeetingConfirmRequest {
uuid: string;

requests: ConfirmDates;
}

export interface GetConfirmedMeetingInfoResponse extends ConfirmDates {
type: MeetingType;
hostName: string;
meetingName: string;
availableAttendeeNames: string[];
Expand Down
22 changes: 15 additions & 7 deletions frontend/src/apis/meetingRecommend.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { fetchClient } from './_common/fetchClient';
import type { MeetingType } from './meetings';

interface GetMeetingRecommendRequest {
uuid: string;
recommendType: string;
attendeeNames: string[] | undefined;
attendeeNames: string[];
}

export interface MeetingRecommend {
Expand All @@ -16,18 +17,25 @@ export interface MeetingRecommend {
attendeeNames: string[];
rank: string;
}

export interface GetMeetingRecommendResponse {
type: MeetingType;
recommendedSchedules: MeetingRecommend[];
}

export const getMeetingTimeRecommends = async ({
uuid,
recommendType,
attendeeNames,
}: GetMeetingRecommendRequest): Promise<MeetingRecommend[]> => {
if (!attendeeNames) return [];
}: GetMeetingRecommendRequest): Promise<GetMeetingRecommendResponse> => {
const urlParams = new URLSearchParams();

urlParams.append('recommendType', recommendType);
if (attendeeNames) urlParams.append('attendeeNames', attendeeNames.join(','));

const path = `/${uuid}/recommended-schedules?recommendType=${recommendType}&attendeeNames=${attendeeNames.join(
',',
)}`;
const path = `/${uuid}/recommended-schedules?${urlParams.toString()}`;

const data = await fetchClient<MeetingRecommend[]>({
const data = await fetchClient<GetMeetingRecommendResponse>({
path,
method: 'GET',
});
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/apis/meetings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BASE_URL } from '@constants/api';

import { fetchClient } from './_common/fetchClient';

export type MeetingType = 'DAYSONLY' | 'DATETIME';
interface MeetingBaseResponse {
meetingName: string;
firstTime: string;
Expand All @@ -14,6 +15,7 @@ interface MeetingBaseResponse {
hostName: string;
availableDates: string[];
attendeeNames: string[];
type: MeetingType;
}

export interface MeetingBase {
Expand All @@ -24,6 +26,7 @@ export interface MeetingBase {
hostName: string;
availableDates: string[];
attendeeNames: string[];
type: MeetingType;
}

export interface MeetingRequest {
Expand Down Expand Up @@ -60,15 +63,17 @@ export const getMeetingBase = async (uuid: string): Promise<MeetingBase> => {
availableDates: data.availableDates,
attendeeNames: data.attendeeNames,
hostName: data.hostName,
type: data.type,
};
};

interface PostMeetingRequest {
export interface PostMeetingRequest {
hostName: string;
hostPassword: string;
meetingName: string;
availableMeetingDates: string[];
meetingStartTime: string;
type: MeetingType;
meetingEndTime: string;
}

Expand All @@ -79,6 +84,7 @@ interface PostMeetingResponse {
earliestTime: string;
lastTime: string;
availableDates: string[];
type: MeetingType;
}

export const postMeeting = async (request: PostMeetingRequest): Promise<PostMeetingResult> => {
Expand Down
13 changes: 1 addition & 12 deletions frontend/src/components/AttendeeTooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,7 @@ export default function AttendeeTooltip({ attendeeNames, position }: AttendeeToo
</div>
}
visibleStyles={css`
background-image: linear-gradient(
45deg,
#33272a 25%,
transparent 25%,
transparent 50%,
#33272a 50%,
#33272a 75%,
transparent 75%,
transparent
);
background-size: 0.8rem 0.8rem;
border: 0.2rem solid #33272a;
border: 0.3rem dashed #71717a;
`}
>
<div css={s_tooltipTrigger} />
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/MeetingCalendar/Date/Date.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const DAY_SLOT_TEXT_STYLES = {
saturday: css`
color: #8c9eff;
`,
prevDay: css`
disabled: css`
color: ${theme.colors.grey.primary};
`,
default: css`
Expand All @@ -68,16 +68,16 @@ const DAY_SLOT_TEXT_STYLES = {

type DateStatus =
| 'isSelectedDate'
| 'isPrevDate'
| 'isDisabledDate'
| 'isSunday'
| 'isSaturday'
| 'isHoliday'
| 'isToday';

const dateStatusStyleMap: Record<DateStatus, SerializedStyles> = {
isSelectedDate: DAY_SLOT_TEXT_STYLES.selected,
isDisabledDate: DAY_SLOT_TEXT_STYLES.disabled,
isToday: DAY_SLOT_TEXT_STYLES.today,
isPrevDate: DAY_SLOT_TEXT_STYLES.prevDay,
isHoliday: DAY_SLOT_TEXT_STYLES.holiday,
isSunday: DAY_SLOT_TEXT_STYLES.holiday,
isSaturday: DAY_SLOT_TEXT_STYLES.saturday,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/MeetingCalendar/Date/RangeDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function RangeDate({
s_dateText({
isSelectedDate,
isToday,
isPrevDate,
isDisabledDate: isPrevDate,
isHoliday,
isSunday,
isSaturday,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function SingleDate({
s_dateText({
isSelectedDate,
isToday,
isPrevDate,
isDisabledDate: isPrevDate,
isHoliday,
isSunday,
isSaturday,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { css } from '@emotion/react';

export const s_container = css`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 3.6rem;
margin-bottom: 1rem;
`;
48 changes: 48 additions & 0 deletions frontend/src/components/MeetingConfirmCalendar/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
s_monthNavigation,
s_monthNavigationContainer,
s_yearMonthText,
} from '@components/MeetingCalendar/Header/MeetingCalendarHeader.styles';

import { s_container } from './Header.styles';

// 해당 인터페이스 기본 CalendarHeader에서도 사용되는데 통합되는 것이 좋아보입니다.(@낙타)
interface HeaderProps {
currentYear: number;
currentMonth: number;
moveToNextMonth: () => void;
moveToPrevMonth: () => void;
isCurrentMonth?: boolean;
}

// CSS는 대부분 MeetingCalendar를 이용했습니다.
// 추후 리팩터링을 진행할 때, '<'버튼과 '>' 버튼을 없앨지 고민중인데, 로직 분기 고민 + 일관된 사용성을 위해서 유지하기로 결정했습니다.(@낙타)
export default function Header({
currentYear,
currentMonth,
moveToNextMonth,
moveToPrevMonth,
isCurrentMonth,
}: HeaderProps) {
return (
<header css={s_container}>
<div css={s_monthNavigationContainer}>
<button
css={s_monthNavigation}
onClick={moveToPrevMonth}
aria-label="지난 달"
disabled={isCurrentMonth}
>
{'<'}
</button>

<span css={s_yearMonthText}>
{currentYear}{currentMonth + 1}
</span>
<button css={s_monthNavigation} onClick={moveToNextMonth} aria-label="다음 달">
{'>'}
</button>
</div>
</header>
);
}
86 changes: 86 additions & 0 deletions frontend/src/components/MeetingConfirmCalendar/Picker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useContext } from 'react';
import { useParams } from 'react-router-dom';

import { AuthContext } from '@contexts/AuthProvider';
import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider';

import {
s_bottomFixedButtonContainer,
s_fullButtonContainer,
} from '@components/Schedules/Schedules.styles';
import { Button } from '@components/_common/Buttons/Button';
import Calendar from '@components/_common/Calendar';

import useCalendarPick from '@hooks/useCalendarPick/useCalendarPick';

import { usePostScheduleMutation } from '@stores/servers/schedule/mutations';

import { getFullDate } from '@utils/date';

import Header from '../Header/Header';
import SingleDate from '../SingleDate/SingleDate';
import WeekDays from '../WeekDays';

interface PickerProps {
availableDates: string[];
}

export default function Picker({ availableDates }: PickerProps) {
const params = useParams<{ uuid: string }>();
const uuid = params.uuid!;
const { userName } = useContext(AuthContext).state;

const { handleToggleIsTimePickerUpdate } = useContext(TimePickerUpdateStateContext);

const { selectedDates, hasDate, handleSelectedDate } = useCalendarPick(uuid, userName);

const { mutate: postScheduleMutate, isPending } = usePostScheduleMutation(() =>
handleToggleIsTimePickerUpdate(),
);

// 백엔드에 날짜 데이터 보내주기 위해 임시로 generate함수 선언(@낙타)
const generateScheduleTable = (dates: string[]) => {
return dates.map((date) => {
return {
date,
times: ['00:00'],
};
});
};

const handleOnToggle = () => {
postScheduleMutate({ uuid, requestData: generateScheduleTable(selectedDates) });
};

return (
<>
<Calendar>
<Calendar.Header render={(props) => <Header {...props} />} />
<Calendar.WeekDays render={(weekdays) => <WeekDays weekdays={weekdays} />} />
<Calendar.Body
renderDate={(dateInfo, today) => (
<SingleDate
key={dateInfo.key}
isAvailable={availableDates.includes(getFullDate(dateInfo.value))}
dateInfo={dateInfo}
today={today}
hasDate={hasDate}
onDateClick={handleSelectedDate}
/>
)}
/>
</Calendar>

<footer css={s_bottomFixedButtonContainer}>
<div css={s_fullButtonContainer}>
<Button size="full" variant="secondary" onClick={handleToggleIsTimePickerUpdate}>
취소하기
</Button>
<Button size="full" variant="primary" onClick={handleOnToggle} isLoading={isPending}>
등록하기
</Button>
</div>
</footer>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css } from '@emotion/react';

import theme from '@styles/theme';

export const s_additionalText = css`
${theme.typography.captionBold}
color: ${theme.colors.black}
`;

export const s_viewer = css`
background-color: transparent;
`;
Loading

0 comments on commit bde448c

Please sign in to comment.