[PWA] (24) 예약취소 로직 변경

Kimmy·2025년 5월 27일

PWA_PROJECT

목록 보기
36/47

🛠️ 기능 개선 개발기록 – 예약 취소 로직 개선

📌 기존 문제점

  1. 취소 사유 미노출 문제
    마이페이지에서 사용자가 '취소하기' 버튼을 누를 경우, 취소 사유 입력 모달이 뜨지 않고, 바로 취소 요청 상태로 변경됨.

  2. 취소 요청 함수 중복
    동일한 기능을 수행하는 취소 요청 함수가 코드 내 중복되어 관리가 어려움.

  3. 취소 승인 프로세스의 비효율성

  • 장점: 관리자는 사용자 요청을 승인하는 구조로 통제 가능
  • 단점: 사용자 입장에서는 취소 승인을 기다려야 하므로 UX가 불편

✅ 개선 방향 및 구현 사항

  1. 취소 프로세스 단순화 및 사용자 편의 강화
  • 사용자가 ‘취소하기’ 클릭 시, 취소 사유 선택 모달을 띄움
  • 사유 선택 후, 곧바로 예약이 ‘취소 완료’ 상태로 변경됨 (관리자 승인 없이 즉시 처리)
  1. 관리자 UI 개선
  • 사용자가 직접 취소한 예약은 별도 리스트에서 확인 가능
  • 취소 사유가 “일정 변경”, “서비스 변경”, “기타”일 경우, 사용자 직접 취소로 간주
  • 그 외 사유는 관리자가 취소한 것으로 구분
  1. 상태 코드 및 UI 정리
  • ‘취소 요청’ 상태는 삭제
  • 상태값 통일: 전체 상태, 예약 확정, 예약 대기, 예약 취소
  • 관련 코드:
  <span
                        className={`px-2 py-1 text-xs rounded-full ${
                          reservation.status === "예약 확정"
                            ? "bg-green-100 text-green-800"
                            : reservation.status === "예약 완료"
                            ? "bg-red-100 text-red-800"
                            : reservation.status === "취소"
                            ? "bg-orange-100 text-orange-800"
                            : "bg-yellow-100 text-yellow-800"
                        }`}
                      >

  1. 예약 필터 드롭다운 수정
 {/* 필터 섹션 */}
              <div className="mb-6 flex flex-wrap gap-4">
                <select
                  className="border rounded-md px-3 py-2"
                  value={statusFilter}
                  onChange={(e) => setStatusFilter(e.target.value)}
                >
                  <option value="all">전체 상태</option>
                  <option value="예약 확정">예약 확정</option>
                  <option value="예약 대기">예약 대기</option>
                  <option value="취소">예약 취소</option>
                </select>

일단, 사용자가 취소하기 누르면 -> 취소 완료 목록 으로 추가되고(이미 이 시점에서 취소는 완료됨), 대신 관리자가 취소요청이 온것을 확인한다는 의미로, 취소승인 대신 '확인' 버튼을 추가. 클릭하면 해당건은 취소된 예약 목록으로 이동함.
~~
--> 위 프로세스도 수정!!!

☑️ 취소 사유 선택한 후에 취소 처리하기

취소하기 버튼을 누르면, 취소 사유를 선택한 후에 상태가 취소로 바뀌어야 하는데, 취소하기 누르면 상태가 취소로 먼저 바뀌고 취소 사유를 그 다음에 선택하도록 잘못되어있음

실제로 예약 상태를 바꾸지 않음

올바른 흐름은:

1."취소하기" 버튼 클릭 →
2.취소 사유 모달이 먼저 뜨고
3.사유를 선택/입력한 뒤
4.확인 시 상태가 "취소 요청" 등으로 바뀌어야함.

-사유를 먼저 선택하고
-닫기하면 닫히고
-사유선택후 취소하면 상태 취소로 변경

수정 방법
1. handleCancelRequest에서 상태 변경을 제거하고, 취소하기 버튼 클릭 시 모달만 열기

⭐실제로 예약 상태를 바꾸지 않음!!!

// 예약 취소 요청 처리
const handleCancelRequest = (reservationId) => {
  // 선택된 예약 저장
  setSelectedReservation(reservationId);
  // 취소 사유 모달 열기
  setIsCancelModalOpen(true);
};
  1. 모달에서 사용자가 사유를 입력하고 '확인'을 눌렀을 때 이때만 예약 상태를 '취소'로 바꾸고, 사유와 취소일시를 저장
const handleCancelConfirm = (reason) => {
  const updatedReservations = reservations.map((reservation) =>
    reservation.id === selectedReservation
      ? {
          ...reservation,
          status: "취소",
          cancelReason: reason,
        }
      : reservation
  );
  setReservations(updatedReservations);
  localStorage.setItem("reservations", JSON.stringify(updatedReservations));
  setIsCancelModalOpen(false);
};
  1. 취소하기 버튼에서 handleCancelRequest에 reservation.id만 넘기기
<button
  className="px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 hover:bg-red-200"
  onClick={() => handleCancelRequest(reservation.id)}
>
  취소하기
</button>

🐤정리
함수 2개를 써서, 하나로 통합해야 하는거 아닌가 했는데, 찾아보니 이 구조가 안전하고 표준적인 방법이라고 한다. 다행이다.

handleCancelRequest: 예약 id만 저장하고 모달만 연다(상태 변경 X)
handleCancelConfirm: 모달에서 사유 입력 후, 이때 상태와 사유를 함께 변경

☑️ 취소된 예약 목록에 취소사유 추가하기

관리자가 취소하면 localStorage 에 rejectReason생기고 직접 입력한 취소사유는 저장되는데, 사용자가 취소하면, cancelReason이 저장이 안되는 이유 찾아서 수정하기

rejectReason은 관리자가 예약을 "거절"할 때 사용하는 사유이고,
cancelReason은 고객이 직접 "취소"할 때 입력하는 사유임.

✔️ cancelReason이 저장되는 시점

고객이 마이페이지 등에서 예약 취소 시,
아래와 같이 예약 객체에 cancelReason을 추가해서 localStorage에 저장.

const updatedReservations = reservations.map((reservation) =>
  reservation.id === selectedReservation
    ? {
        ...reservation,
        status: "취소",
        cancelReason: reason, // ← 여기서 저장
        canceledAt: new Date().toLocaleString(),
      }
    : reservation
);
setReservations(updatedReservations);
localStorage.setItem("reservations", JSON.stringify(updatedReservations));

admin에서 쓰이는 rejectReason: reson 은 설정해둬서 저장해놨는데, user단에서는 이 작업을 안해놨기때문에 저장이 안됬었던 것. 수정하니 잘된다.

☑️ 예약 상태가 '취소'로 바뀌면 즉시 해당 시간이 예약 가능하도록 수정

 //admin 예약 취소 요청 승인
  const handleDeleteCanceledReservation = (reservationId) => {
    const updatedCanceledReservations = canceledReservations.filter(
      (reservation) => reservation.id !== reservationId
    );

    // 로컬 스토리지 업데이트
    const allReservations = JSON.parse(
      localStorage.getItem("reservations") || "[]"
    );
    const updatedAllReservations = allReservations.filter(
      (reservation) => reservation.id !== reservationId
    );
    localStorage.setItem(
      "reservations",
      JSON.stringify(updatedAllReservations)
    );

    setCanceledReservations(updatedCanceledReservations);
  };

이 코드에서 이미 예약 상태는 '취소'인데, 관리자가 '취소'된 예약목록에서 하나하나 삭제해야, 그 시간이 다시 예약가능시간으로 풀린다.
처음에는 관리자를 위해서 이 방법으로 개발했는데, 사용자 입장에서는 "취소했는데 왜 바로 예약이 안 되지?"라는 혼란이 생길 수 있을 것 같다는 생각이 들었다.

예약 상태가 '취소'로 바뀌면 즉시 해당 시간이 예약 가능하도록 한다
(= 취소된 순간, 바로 그 시간대가 다시 예약 가능 목록에 노출됨)

✔️ 이 방식의 장점

  • 사용자가 취소하면 바로 다른 사람이 그 시간에 예약할 수 있음
  • 관리자가 별도로 삭제하지 않아도 됨
  • UX가 빠르고 직관적임

vs 단점

  • 실수로 취소했을 때 바로 풀려버리므로, 취소 취소(undo) 같은 기능이 필요할 수 있음
  • 관리자가 취소 내역을 확인하기 어렵다면 로그 관리가 필요함

☑️ 예약 가능 시간 계산(중복 체크) 함수에서 '취소' 상태 제외

취소된 예약은 삭제하지 말고, 상태만 '취소'로 두고, 예약 가능 시간 계산에서만 제외하기!
예약 가능 시간 계산, 예약 중복 체크, 예약 시간대 필터링 등 모든 곳에서 reservation.status !== "취소" 조건을 꼭 넣어주기.

💙예약 중복 체크 함수

// 해당 시간대가 이미 예약되었는지 확인
  const isTimeSlotTaken = (time) => {
    return existingReservations.some((reservation) => {
      return (
        reservation.date === selectedDate &&
        reservation.time === time &&
        reservation.statue !== "취소"
      );
    });
  };

reservation.status !== "취소"
이 코드를 예약 중복 체크 함수에 추가했는데, 계속 시간이 정상적으로 안나오길래 다시 보니 오타가있었다..ㅎ
statue -> status 로 수정하니 잘 나온다.

이렇게 하면, 관리자 페이지에서 굳이 '취소' 예약을 삭제하지 않아도 예약이 '취소'로 바뀌는 즉시 해당 시간이 다시 예약 가능해진다.

☑️ 예약 대기 -> 예약 완료

위와 같은 맥락으로, 예약을 하고 나면, 상태가 예약 대기가 아닌 예약 완료로 보이도록 추가적으로 수정해준다.
전체검색으로 예약 대기 인 부분을 예약 완료로 모두 바꿔주었다.

그리고 admin에서 취소 요청 목록, 취소 완료 목록은 모두 삭제한다.

☑️ new Date().toLocaleString()

new Date().toLocaleString()
new Date().toLocaleDateString()

둘의 차이점!?

🍦new Date().toLocaleString()

날짜와 시간을 모두 문자열로 반환함.
예시: 2025. 5. 22. 오후 3:45:12

🍦new Date().toLocaleDateString()

날짜만 문자열로 반환함.
예시: 2025. 5. 22.

profile
바리바리 개바리 🌼

0개의 댓글