Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 모달창 추가, 예약상태 추가 #116

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
30 changes: 24 additions & 6 deletions app/(kahlua)/reservation/page.tsx
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,29 @@ import Banner from '@/components/reservation/Banner';
import CalendarUI from '@/components/reservation/CalendarUI';
import ReservationForm from '@/components/reservation/ReservationForm';
import RoomNotice from '@/components/reservation/RoomNotice';
import TimeTable from '@/components/reservation/TimeTable';
import TimeTable, { Reservation } from '@/components/reservation/TimeTable';
import React, { useState } from 'react';

// Dummy data
const dummyReservations: Reservation[] = [
{ timeRange: '10:00 ~ 10:30', status: 'unavailable' },
{ timeRange: '10:30 ~ 11:00', status: 'booked', name: '홍길동' },
{ timeRange: '11:00 ~ 11:30', status: 'myReservation' },
{ timeRange: '11:30 ~ 12:00', status: 'available' },
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
];

const page = () => {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [selectedTime, setSelectedTime] = useState<string | null>(null);
const [isFormVisible, setIsFormVisible] = useState(true);
const [reservations, setReservations] = useState<Reservation[]>([]);

// 날짜 선택
const handleDateSelect = (date: Date) => setSelectedDate(date);
const handleDateSelect = (date: Date) => {
setSelectedDate(date);

console.log(date);
};

// 시간 선택
const handleTimeSelect = (time: string) => setSelectedTime(time);
Expand All @@ -24,13 +37,14 @@ const page = () => {
};

// 예약 정보 서버로 전송 함수 (추가수정필요)
const handleReservationSubmit = async (name: string) => {
const handleReservationSubmit = async (reservationName: string) => {
const reservationData = {
date: selectedDate?.toISOString(),
time: selectedTime,
name,
name: reservationName,
};
window.location.reload(); // 페이지 새로고침
console.log('예약 정보:', reservationData);
// window.location.reload(); // 페이지 새로고침
// 서버로 전송하는 로직 추가 필요
};

Expand All @@ -40,7 +54,11 @@ const page = () => {
{isFormVisible ? (
<div className="mx-4 pad:m-0 flex flex-col items-center gap-y-6">
<CalendarUI onSelectDate={handleDateSelect} />
<TimeTable date={selectedDate} onSelectTime={handleTimeSelect} />
<TimeTable
date={selectedDate}
onSelectTime={handleTimeSelect}
reservations={reservations}
/>
<RoomNotice />
<button
onClick={handleNext}
Expand Down
6 changes: 3 additions & 3 deletions components/reservation/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const Banner = () => {
return (
<section className="pad:h-[320px] ph:h-[258px] text-center bg-gray-90 mt-20 pad:rounded-3xl ph:rounded-none">
<h1 className="pad:pt-16 ph:pt-8 font-semibold leading-[130%] text-gray-0 pad:text-[64px] ph:text-[36px]">
<section className="h-[258px] pad:h-[320px] text-center bg-gray-90 mt-20 pad:rounded-3xl ph:rounded-none">
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
<h1 className="pt-8 pad:pt-16 font-semibold leading-[130%] text-gray-0 text-2xl pad:text-64px">
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
ClubRoom Reservation
</h1>
<div className="pt-8 leading-6 pad:text-xl ph:text-base mx-4">
<div className="pt-8 leading-6 text-base pad:text-xl mx-4">
<p className="text-gray-20">
깔루아 멤버들은 당일 기준 2주 내의 동아리 사용 예약을 신청할 수
있습니다.
Expand Down
10 changes: 5 additions & 5 deletions components/reservation/CalendarUI.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,17 @@
padding-top: 10px;
font-size: 24px;
}
.react-calendar__navigation__label {
font-size: 20px;
}
}

@media (max-width: 834px) {
.react-calendar__tile {
height: 60px;
font-size: 14px;
}
.react-calendar__month-view__weekdays {
font-size: 18px;
.react-calendar__month-view__weekdays__weekday {
font-size: 23px;
}
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
.react-calendar__navigation__label {
font-size: 20px;
}
}
32 changes: 32 additions & 0 deletions components/reservation/ReservationFailureModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interface ReservationFailureModalProps {
formattedDateTime: string;
onClose: () => void;
}

const ReservationFailureModal = ({
onClose,
}: ReservationFailureModalProps) => {
const handleOnClose = () => {
onClose();
window.location.reload();
};

return (
<div className="w-[328px] h-[230px] pad:w-[600px] pad:h-[300px] flex flex-col text-center gap-6 py-[52px] px-[50px] pad:py-20 pad:px-10 bg-gray-0 rounded-3xl relative z-50">
<img
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
src="/image/reservation/tabler_x.svg"
className="w-6 h-6 cursor-pointer absolute top-4 right-10"
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
onClick={handleOnClose}
alt="close"
/>
<div className="text-xl pad:text-2xl font-semibold">예약 실패</div>
<div className="text-base pad:text-lg font-medium">
이미 예약이 완료된 시간대입니다.
<br />
다른 시간을 선택하여 예약을 진행해주세요.
</div>
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};

export default ReservationFailureModal;
42 changes: 35 additions & 7 deletions components/reservation/ReservationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from 'react';
import ReservationNotice from './ReservationNotice';
import ReservationSuccessModal from './ReservationSuccessModal';
import ReservationFailureModal from './ReservationFailureModal';

interface ReservationFormProps {
selectedDate: Date | null;
Expand All @@ -15,16 +17,22 @@ const ReservationForm = ({
const [name, setName] = useState<string>('');
const [teamName, setTeamName] = useState<string>('');
const [isPersonal, setIsPersonal] = useState(true); // 개인/팀 선택 상태
const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false);
const [isFailureModalOpen, setIsFailureModalOpen] = useState(false);

// submit 제출시
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

if ((name||teamName) && selectedDate && selectedTime) {
onSubmit(name); // onSubmit 구현 필요 (page.tsx)
alert('예약이 완료되었습니다!');
const reservationName = isPersonal ? name : teamName;

if (reservationName && selectedDate && selectedTime) {
onSubmit(reservationName); // onSubmit 구현 필요 (page.tsx)
// alert('예약이 완료되었습니다!');
setIsSuccessModalOpen(true);
} else {
alert('모든 필드를 입력해 주세요.');
setIsFailureModalOpen(true);
}
};

Expand All @@ -46,13 +54,17 @@ const ReservationForm = ({
setIsPersonal(isPersonal);
setName('');
setTeamName('');
}
};

return (
<div className="flex flex-col">
<p className="text-gray-90 text-xl font-semibold my-10">{formattedDateTime()}</p>
<div className='py-10 border-t border-gray-30'>
<h3 className="text-[18px] pad:text-xl font-semibold mb-6">예약자 정보 입력</h3>
<p className="text-gray-90 text-xl font-semibold my-10">
{formattedDateTime()}
</p>
<div className="py-10 border-t border-gray-30">
<h3 className="text-[18px] pad:text-xl font-semibold mb-6">
예약자 정보 입력
</h3>
<form onSubmit={handleSubmit} className="flex flex-col">
<div className="flex items-center gap-2 mb-4">
<input
Expand Down Expand Up @@ -105,6 +117,22 @@ const ReservationForm = ({
</button>
</form>
</div>
{isSuccessModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
<ReservationSuccessModal
formattedDateTime={formattedDateTime()}
onClose={() => setIsSuccessModalOpen(false)}
/>
</div>
)}
{isFailureModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
<ReservationFailureModal
formattedDateTime={formattedDateTime()}
onClose={() => setIsFailureModalOpen(false)}
/>
</div>
)}
</div>
);
};
Expand Down
62 changes: 62 additions & 0 deletions components/reservation/ReservationSuccessModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
interface ReservationSuccessModalProps {
formattedDateTime: string;
onClose: () => void;
}

const ReservationSuccessModal = ({
formattedDateTime,
onClose,
}: ReservationSuccessModalProps) => {
const handleOnClose = () => {
onClose();
window.location.reload();
};

// 날짜와 시간을 분리(모바일 전용)
const datePart =
formattedDateTime.split(' ')[0] +
' ' +
formattedDateTime.split(' ')[1] +
' ' +
formattedDateTime.split(' ')[2];
const timePart =
formattedDateTime.split(' ')[3] + ' ' + formattedDateTime.split(' ')[4] + ' ' + formattedDateTime.split(' ')[5];

return (
<div className="w-[328px] pad:w-[600px] pad:h-[300px] flex flex-col text-center gap-6 py-[52px] px-[50px] pad:py-20 pad:px-10 bg-gray-0 rounded-3xl relative z-50">
<img
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
src="/image/reservation/tabler_x.svg"
className="w-6 h-6 cursor-pointer absolute top-4 right-10"
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
onClick={handleOnClose}
alt="close"
/>
<div>
<div className="hidden pad:inline pad:text-2xl font-semibold">
{formattedDateTime}
</div>
<div className="pad:hidden text-xl font-semibold">
<div>{datePart}</div>
<div>{timePart}</div>
</div>
<div className="text-lg pad:text-22px font-medium">
예약이 확정되었습니다.
</div>
</div>
<div className="text-base pad:text-lg font-medium">
<span className="hidden pad:inline">
동아리방 도어락 비밀번호는
<span className="font-semibold">[5896]</span> 입니다.
</span>
<span className="pad:hidden">
동아리방 도어락 비밀번호는
<br />
<span className="font-semibold">[5896]</span> 입니다.
</span>
<br />
비밀번호를 입력해야 출입이 가능하니 꼭 기억해주세요!
</div>
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};

export default ReservationSuccessModal;
60 changes: 45 additions & 15 deletions components/reservation/TimeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
// TimeTable.tsx
import React, { useState, useEffect } from 'react';

// 예약 상태 타입
export interface Reservation {
timeRange: string;
status: 'unavailable' | 'booked' | 'myReservation' | 'available';
name?: string; // 예약자의 이름 (예약 마감 상태일 때만 표시)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reservation interface 만들어두는거 완전 굳인데 앞에서 한 리뷰처럼 dto에 맞게 수정해두면 좋을듯 !!


interface TimeTableProps {
date: Date | null;
onSelectTime: (timeRange: string) => void;
reservations: Reservation[];
}

const TimeTable = ({ date, onSelectTime }: TimeTableProps) => {
const TimeTable = ({ date, onSelectTime, reservations }: TimeTableProps) => {
const hours = Array.from({ length: 12 }, (_, i) => i + 10); // 10시부터 22시까지
const [selectedTimes, setSelectedTimes] = useState<string[]>([]);
const [startTime, setStartTime] = useState<string | null>(null); // 시작 시간 추적
const [endTime, setEndTime] = useState<string | null>(null); // 종료 시간 추적

// 시간 선택 및 해제
const handleTimeClick = (startTimeStr: string, endTimeStr: string) => {
// 시간 선택 및 해제
const handleTimeClick = (startTimeStr: string, endTimeStr: string) => {
if (!date) {
alert('날짜를 선택해 주세요!');
return;
}

// 이미 시작과 종료 시간이 선택된 상태에서 다시 클릭하면 초기화
if (startTime && endTime) {
setSelectedTimes([]);
Expand All @@ -31,7 +39,7 @@ const TimeTable = ({ date, onSelectTime }: TimeTableProps) => {
if (!startTime) {
setStartTime(startTimeStr);
setSelectedTimes([`${startTimeStr} ~ ${endTimeStr}`]);
}
}
// 시작 시간이 설정된 상태에서 두 번째 클릭: 종료 시간으로 설정
else {
setEndTime(endTimeStr);
Expand Down Expand Up @@ -99,31 +107,33 @@ const TimeTable = ({ date, onSelectTime }: TimeTableProps) => {
};

return (
<div className="flex flex-col w-full pb-10 border-b border-gray-15">
<div className="flex flex-col w-full">
<p className="text-black text-base mb-2">* 30분 단위 예약 가능</p>
<div className="flex flex-row py-2 border-b border-gray-15 overflow-x-scroll">
<div className="flex flex-row py-2 mb-6 overflow-x-scroll">
{hours.map((hour) => (
<div key={hour} className="flex flex-col dt:w-[100px] ph:w-[64px]">
<span className="text-base mb-1">{hour}시</span>
<div className="flex flex-row">
<div
key={`${hour}:00`}
className={`pad:flex-1 h-[60px] w-[32px] cursor-pointer ${
date ?
(getTimeSlotStatus(`${hour}:00`, `${hour}:30`) === 'selected'
date
? getTimeSlotStatus(`${hour}:00`, `${hour}:30`) ===
'selected'
? 'bg-primary-50 text-white'
: 'bg-gray-5')
: 'bg-gray-5'
: 'bg-gray-7 cursor-not-allowed'
}`}
onClick={() => handleTimeClick(`${hour}:00`, `${hour}:30`)}
></div>
<div
key={`${hour}:30`}
className={`pad:flex-1 h-[60px] w-[32px] cursor-pointer mr-[1px] ${
date ?
(getTimeSlotStatus(`${hour}:30`, `${hour + 1}:00`) === 'selected'
date
? getTimeSlotStatus(`${hour}:30`, `${hour + 1}:00`) ===
'selected'
? 'bg-primary-50 text-white'
: 'bg-gray-5')
: 'bg-gray-5'
: 'bg-gray-7 cursor-not-allowed'
}`}
onClick={() => handleTimeClick(`${hour}:30`, `${hour + 1}:00`)}
Expand All @@ -132,9 +142,29 @@ const TimeTable = ({ date, onSelectTime }: TimeTableProps) => {
</div>
))}
</div>
<div className="mt-4 text-black pad:text-2xl">
{formattedReservation()}
<div className="flex flex-wrap pad:flex-nowrap gap-6 pb-10 border-b border-gray-15 text-sm pad:text-base">
<div className="flex items-center">
<span className="inline-block w-4 h-4 pad:w-6 pad:h-6 bg-gray-15 mr-2"></span>
예약 불가능
</div>
<div className="flex items-center">
<span className="inline-block w-4 h-4 pad:w-6 pad:h-6 bg-primary-10 mr-2"></span>
예약 마감
</div>
<div className="flex items-center">
<span className="inline-block w-4 h-4 pad:w-6 pad:h-6 bg-warning-10 mr-2"></span>
내예약
</div>
<div className="flex items-center">
<span className="inline-block w-4 h-4 pad:w-6 pad:h-6 bg-gray-5 mr-2"></span>
예약 가능
</div>
</div>
boogiewooki02 marked this conversation as resolved.
Show resolved Hide resolved
{formattedReservation() && (
<div className="mt-4 text-black pad:text-2xl pb-10 border-b border-gray-15">
{formattedReservation()}
</div>
)}
</div>
);
};
Expand Down
Loading