Skip to content

Commit

Permalink
💄 예약 디자인 적용 및 리팩터링 (#127)
Browse files Browse the repository at this point in the history
* refactor: RoomReservationPage

* refactor: ReservationCalendar

* refactor: NavigateButtons

* feat: MuiDateSelector에 outside clicker 적용

* design: CalendarColumn

* refactor: ReservationDetailModal 에러 처리

* refactor: ReservationModal에서 server action 사용

* design: 예약 조회 모달

* fix: AlertMessage 색 임시 지정

* fix: 캘린더 시간 변환 버그 수정

* refactor: AddReservationModal 로직 훅으로 분리

* design: 예약 추가 모달

* fix: BasicButton 폰트 크기
  • Loading branch information
yeolyi authored Feb 10, 2024
1 parent 1401201 commit 9e932be
Show file tree
Hide file tree
Showing 23 changed files with 807 additions and 723 deletions.
1 change: 0 additions & 1 deletion .eslintcache

This file was deleted.

29 changes: 5 additions & 24 deletions apis/reservation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use server';

import { cookies } from 'next/dist/client/components/headers';

import { Reservation, ReservationPostBody, ReservationPreview } from '@/types/reservation';
Expand Down Expand Up @@ -28,8 +30,9 @@ export const getWeeklyReservation = async (params: {
})) as ReservationPreview[];
};

export const getReservation = async (id: number) =>
getRequest(`${reservationPath}/${id}`) as Promise<Reservation[]>;
export const getReservation = async (id: number) => {
return getRequest(`${reservationPath}/${id}`) as Promise<Reservation>;
};

export const deleteSingleReservation = async (id: number) => {
await deleteRequest(`${reservationPath}/${id}`);
Expand All @@ -38,25 +41,3 @@ export const deleteSingleReservation = async (id: number) => {
export const deleteAllRecurringReservation = async (id: string) => {
await deleteRequest(`${reservationPath}/recurring/${id}`);
};

export const roomNameToId = {
// 세미나실
'301-417': 1,
'301-521': 2,
'301-551-4': 3,
'301-552-1': 4,
'301-552-2': 5,
'301-552-3': 6,
'301-553-6': 7,
'301-317': 8,
'302-308': 9,
'302-309-1': 10,
'302-309-2': 11,
'302-309-3': 12,
// 실습실
'302-311-1': 13,
'302-310-2': 14,
// 공과대학 강의실
'302-208': 15,
'302-209': 16,
} as const;
101 changes: 40 additions & 61 deletions app/[locale]/reservations/[roomType]/[roomName]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { NetworkError } from '@/apis';
import { getWeeklyReservation } from '@/apis/reservation';

import { getWeeklyReservation, roomNameToId } from '@/apis/reservation';

import LoginUserVisible from '@/components/common/LoginUserVisible';
import PageLayout from '@/components/layout/pageLayout/PageLayout';
import ReservationCalendar from '@/components/reservations/ReservationCalendar';

Expand All @@ -11,29 +8,23 @@ interface RoomReservationProps {
searchParams: { selectedDate?: string };
}

export default function RoomReservationPage({ params, searchParams }: RoomReservationProps) {
return (
<PageLayout titleType="big" titleMargin="mb-[2.25rem]">
<LoginedRoomReservationPage params={params} searchParams={searchParams} />
</PageLayout>
);
}

async function LoginedRoomReservationPage({ params, searchParams }: RoomReservationProps) {
const roomId = isValidRoomName(params.roomName) ? roomNameToId[params.roomName] : undefined;
const date = parseDate(searchParams.selectedDate || todayYMDStr());
const TITLE_BOTTOM_MARGIN = 'mb-[2.75rem]';

// TODO: 에러 처리 페이지 디자인
export default async function RoomReservationPage({ params, searchParams }: RoomReservationProps) {
const roomId = roomNameToId[params.roomName];
if (roomId === undefined) {
return (
<PageLayout titleType="big" titleMargin="mb-[2.25rem]">
<PageLayout titleType="big" titleMargin={TITLE_BOTTOM_MARGIN}>
존재하지 않는 시설 아이디입니다.
</PageLayout>
);
}

if (date === undefined) {
const date = searchParams.selectedDate ? new Date(searchParams.selectedDate) : new Date();
if (isNaN(date.valueOf())) {
return (
<PageLayout titleType="big" titleMargin="mb-[2.25rem]">
<PageLayout titleType="big" titleMargin={TITLE_BOTTOM_MARGIN}>
유효하지 않은 날짜입니다.
</PageLayout>
);
Expand All @@ -49,55 +40,43 @@ async function LoginedRoomReservationPage({ params, searchParams }: RoomReservat
});

return (
<ReservationCalendar
startDate={startOfWeek}
selectedDate={date}
reservations={reservations}
roomId={roomId}
/>
<PageLayout titleType="big" titleMargin={TITLE_BOTTOM_MARGIN}>
<ReservationCalendar
startDate={startOfWeek}
selectedDate={date}
reservations={reservations}
roomId={roomId}
/>
</PageLayout>
);
}

const isValidRoomName = (roomName: string): roomName is keyof typeof roomNameToId => {
return Object.keys(roomNameToId).findIndex((x) => x === roomName) !== -1;
};

// 오늘 날짜는 yyyy-mm-dd 형식으로 변환
const todayYMDStr = () => {
const date = new Date();
return [date.getFullYear(), date.getMonth() + 1, date.getDate()]
.map((x) => (x + '').padStart(2, '0'))
.join('-');
};

// yyyy-mm-dd 형식의 date의 유효성 검증
const parseDate = (dateString: string) => {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
return undefined;
}

const [year, month, day] = dateString.split('-').map((x) => +x);

if (year < 1000 || year > 3000 || month == 0 || month > 12) {
return undefined;
}

const monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

// 윤년
if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) monthLength[1] = 29;

if (day > 0 && day <= monthLength[month - 1]) {
return new Date(year, month - 1, day);
} else {
return undefined;
}
};

// 일주일의 시작을 월요일로 간주
/** 일주일의 시작을 월요일로 간주 */
const getStartOfWeek = (date: Date) => {
const ret = new Date(date);
const diff = (date.getDay() || 7) - 1;
ret.setDate(ret.getDate() - diff);
return ret;
};

const roomNameToId: { [roomName: string]: number } = {
// 세미나실
'301-417': 1,
'301-521': 2,
'301-551-4': 3,
'301-552-1': 4,
'301-552-2': 5,
'301-552-3': 6,
'301-553-6': 7,
'301-317': 8,
'302-308': 9,
'302-309-1': 10,
'302-309-2': 11,
'302-309-3': 12,
// 실습실
'302-311-1': 13,
'302-310-2': 14,
// 공과대학 강의실
'302-208': 15,
'302-209': 16,
};
11 changes: 6 additions & 5 deletions components/common/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useReducer, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';

import { useOutClickAlerter } from '@/hooks/useOutClickAlerter';

Expand All @@ -15,7 +15,7 @@ export default function Dropdown({ contents, selectedIndex, onClick, borderStyle

useOutClickAlerter(
ref,
useCallback(() => setExpanded((x) => false), []),
useCallback(() => setExpanded(false), []),
);

const toggleExpanded = () => setExpanded((x) => !x);
Expand Down Expand Up @@ -52,7 +52,7 @@ function DropdownButton({
toggleExpanded,
contents,
selectedIndex,
borderStyle = 'border-neutral-300',
borderStyle = 'border-neutral-200',
}: {
expanded: boolean;
toggleExpanded: () => void;
Expand All @@ -64,6 +64,7 @@ function DropdownButton({
<button
className={`
flex items-center gap-4 py-[.3125rem] pr-[.3125rem] pl-[.625rem] border
bg-white
${expanded ? 'rounded-t-sm' : 'rounded-sm'}
${borderStyle}
`}
Expand All @@ -72,7 +73,7 @@ function DropdownButton({
toggleExpanded();
}}
>
<p className="text-sm font-normal">{contents[selectedIndex]}</p>
<p className="text-md font-normal">{contents[selectedIndex]}</p>
<span className="material-symbols-rounded text-base">
{expanded ? 'expand_less' : 'expand_more'}
</span>
Expand All @@ -85,7 +86,7 @@ function DropdownListWithScroll({
contents,
handleClick,
selectedIndex,
borderStyle = `border-neutral-300`,
borderStyle = `border-neutral-200`,
}: {
className: string;
contents: string[];
Expand Down
2 changes: 1 addition & 1 deletion components/editor/PostEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ function DateInputFieldSet({ date, setDate }: { date: Date; setDate: (date: Date
enablePast
date={date}
setDate={(date) => {
setDate(date);
date && setDate(date);
closeModal();
}}
className="bg-white"
Expand Down
2 changes: 1 addition & 1 deletion components/editor/common/DateSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function DateSelector({
<MuiDateSelector
date={date}
setDate={(date) => {
setDate(date);
date && setDate(date);
closeModal();
}}
className="bg-white"
Expand Down
10 changes: 9 additions & 1 deletion components/layout/pageLayout/PageTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ interface PageTitleProps {
margin: string;
}

export const PAGE_TITLE_LEFT_MARGIN_PX = 100;

export default function PageTitle({ title, currentPage, titleType, margin }: PageTitleProps) {
const titleStyle = titleType === 'big' ? 'text-2xl font-bold' : 'text-lg font-medium';

return (
<div className="bg-neutral-900 px-[100px] pt-[54px]">
<div
className={`bg-neutral-900 pt-[54px]`}
style={{
paddingLeft: PAGE_TITLE_LEFT_MARGIN_PX + 'px',
paddingRight: PAGE_TITLE_LEFT_MARGIN_PX + 'px',
}}
>
<div
className={`w-fit min-w-[15.625rem] max-w-[51.875rem] row-start-1 col-start-1 ${margin}`}
>
Expand Down
2 changes: 1 addition & 1 deletion components/modal/AlertModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function AlertModal({
}

function AlertMessage({ message }: { message: string }) {
return <p className="mt-1 mb-6">{message}</p>;
return <p className="text-neutral-800 mt-1 mb-6">{message}</p>;
}

function CancelButton({ text, onClick }: { text: string; onClick: () => void }) {
Expand Down
42 changes: 20 additions & 22 deletions components/mui/MuiDateSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { LocalizationProvider, DateCalendar } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import dayjs, { Dayjs } from 'dayjs';
import 'dayjs/locale/ko';
import { useRef } from 'react';

const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
import { useOutClickAlerter } from '@/hooks/useOutClickAlerter';

export default function MuiDateSelector({
date,
Expand All @@ -17,10 +13,13 @@ export default function MuiDateSelector({
enablePast,
}: {
date: Date;
setDate: (date: Date) => void;
setDate: (date?: Date) => void;
className?: string;
enablePast?: boolean;
}) {
const ref = useRef(null);
useOutClickAlerter(ref, () => setDate());

const shouldDisableDate = (day: Dayjs) => {
if (enablePast) return false;

Expand All @@ -36,20 +35,19 @@ export default function MuiDateSelector({
};

return (
<ThemeProvider theme={darkTheme}>
<LocalizationProvider adapterLocale="ko" dateAdapter={AdapterDayjs}>
<DateCalendar
className={className}
value={dayjs(date)}
views={['day']}
onChange={(value) => {
const date = value?.toDate();
date && setDate(date);
}}
shouldDisableDate={shouldDisableDate}
showDaysOutsideCurrentMonth
/>
</LocalizationProvider>
</ThemeProvider>
<LocalizationProvider adapterLocale="ko" dateAdapter={AdapterDayjs}>
<DateCalendar
ref={ref}
className={className}
value={dayjs(date)}
views={['day']}
onChange={(value) => {
const date = value?.toDate();
date && setDate(date);
}}
shouldDisableDate={shouldDisableDate}
showDaysOutsideCurrentMonth
/>
</LocalizationProvider>
);
}
4 changes: 1 addition & 3 deletions components/reservations/BasicButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
export default function BasicButton({
type = 'button',
className,
onClick,
children,
...props
}: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>) {
return (
<button
type={type}
className={`text-xs text-neutral-700 rounded-sm border border-neutral-300 ${className}`}
onClick={onClick}
className={`text-xs bg-white text-neutral-700 disabled:text-neutral-300 rounded-sm border border-neutral-200 ${className}`}
{...props}
>
{children}
Expand Down
Loading

0 comments on commit 9e932be

Please sign in to comment.