헬스클럽 PT 예약 시스템 레시피
📋 프로젝트 정보
난이도: 중급
스택: PHP + JavaScript / Laravel + React + Inertia.js
리포지토리명: pt-booking-system
💪 문제 설명
개인 PT(Personal Training) 예약을 관리하는 풀스택 시스템입니다. "Laravel 13.x + Inertia.js + React 19 모노리스 아키텍처로 구현하며, 관리자가 등록한 수업 슬롯에 회원이 예약하고, 이용권 기간/횟수 기반으로 예약을 제한하며, 출석 체크 및 운동 기록(리플) 기능을 제공합니다."
필수 기능
- 관리자가 날짜/시간별 PT 슬롯 등록 및 관리
- 회원 이용권(멤버십) 관리: 기간 + 횟수 기반
- 이용권 기간 내에서만 예약 가능, 잔여 횟수 차감
- 회원이 관리자가 등록한 슬롯 중 선택하여 예약
- 예약 시간 경과 후 자동 횟수 차감
- 관리자 출석 체크 기능
- 관리자 운동 기록 리플(코멘트) 작성
- 관리자 예약 현황 조회 (일별/주별/월별)
보너스 기능
- 예약 시 자동 알림 (Laravel Mail 또는 SMS)
- 예약 전날 리마인더
- 이용권 만료 임박 알림
- 회원별 운동 기록 히스토리 조회
- 관리자 대시보드 통계 (출석률, 이용권 현황 등)
- 예약 취소 정책 (수업 24시간 전까지만 취소 가능 등)
⚙️ 제약 조건
- Laravel 13.x + Inertia.js + React 19 모노리스 풀스택 (별도 API 라우트 없음)
- Laravel Breeze (React + Inertia) starter kit 사용
- SQLite 데이터베이스
- Tailwind CSS 스타일링
- 역할 기반 접근 제어: admin(관리자) / member(회원)
- 슬롯은 관리자만 생성 가능, 회원은 선택만 가능
- 이용권 기간 만료 시 잔여 횟수와 무관하게 예약 불가
- 예약 시간 경과 후 횟수 차감 (Laravel Scheduler 또는 수동 처리)
- Eloquent 관계 (hasMany, belongsTo, morphMany 등) 활용
- Carbon (Laravel) 및 date-fns (React) 활용
📝 프롬프트 레시피
Step 1: 프로젝트 초기화
mkdir pt-booking-system && cd pt-booking-system
이 폴더에서 헬스클럽 PT 예약 시스템 프로젝트를 시작합니다.
Laravel 13.x + Inertia.js + React 19 기반 모노리스 풀스택 프로젝트를 생성해주세요.
1. `npx create laravel` 명령으로 Laravel 13.x 프로젝트 생성 (현재 폴더에)
2. Laravel Breeze (React + Inertia) starter kit 설치:
- `php artisan breeze:install react`
- `npm install && npm run dev`
3. SQLite 데이터베이스 설정:
- `.env` 파일에서 `DB_CONNECTION=sqlite`
- `touch database/database.sqlite`
4. 프로젝트 구조 확인:
- `resources/js/Pages/` (React 페이지 컴포넌트)
- `app/Http/Controllers/` (Laravel 컨트롤러)
- `routes/web.php` (Inertia 라우트)
5. Tailwind CSS가 이미 설정되어 있는지 확인
6. 기본 인증 시스템(로그인/회원가입) 동작 확인
프로젝트가 정상적으로 생성되고 `npm run dev` 및 `php artisan serve`로 실행되면
브라우저에서 기본 화면을 확인해주세요.
기대 결과: Laravel 13.x + Breeze (React + Inertia) 프로젝트가 생성되고,
기본 인증 페이지(로그인/회원가입)가 정상 작동합니다.
SQLite 데이터베이스 파일이 생성되고, Vite 개발 서버가 실행됩니다.
Step 2: 데이터베이스 스키마 설계
데이터베이스 스키마를 설계하고 마이그레이션을 생성해주세요.
필요한 테이블:
1. **users 테이블 수정** (기존 users 테이블에 컬럼 추가)
- role (역할: 'admin' 또는 'member', default 'member')
- phone (전화번호, string, nullable)
2. **memberships 테이블** (회원 이용권 정보)
- id (primary key)
- user_id (foreign key to users)
- total_sessions (총 PT 횟수, integer, 예: 10회, 20회, 30회)
- remaining_sessions (잔여 PT 횟수, integer)
- starts_at (이용권 시작일, date)
- expires_at (이용권 만료일, date)
- is_active (활성화 여부, boolean, default true)
- memo (관리자 메모, text, nullable)
- timestamps
3. **slots 테이블** (관리자가 등록하는 PT 가능 슬롯)
- id (primary key)
- slot_date (수업 날짜, date)
- start_time (시작 시간, time, 예: '10:00')
- end_time (종료 시간, time, 예: '11:00')
- max_members (최대 수용 인원, integer, default 1 — 1:1 PT 기본)
- is_available (예약 가능 여부, boolean, default true)
- timestamps
4. **bookings 테이블** (회원 예약 정보)
- id (primary key)
- user_id (foreign key to users — 예약한 회원)
- slot_id (foreign key to slots — 예약한 슬롯)
- membership_id (foreign key to memberships — 사용하는 이용권)
- status (예약 상태: 'reserved', 'completed', 'cancelled', 'no_show', default 'reserved')
- attended (출석 여부, boolean, default false)
- session_deducted (횟수 차감 여부, boolean, default false)
- cancelled_at (취소 시간, timestamp, nullable)
- timestamps
- unique index on (user_id, slot_id) — 동일 슬롯 중복 예약 방지
5. **comments 테이블** (관리자 운동 기록 리플)
- id (primary key)
- booking_id (foreign key to bookings)
- admin_id (foreign key to users — 작성한 관리자)
- content (운동 기록 내용, text)
- timestamps
마이그레이션 파일을 생성하고 `php artisan migrate` 실행까지 해주세요.
모델도 함께 생성하고 Eloquent 관계를 설정해주세요:
- User hasMany Memberships, hasMany Bookings
- Membership belongsTo User, hasMany Bookings
- Slot hasMany Bookings
- Booking belongsTo User, belongsTo Slot, belongsTo Membership, hasMany Comments
- Comment belongsTo Booking, belongsTo User (admin)
기대 결과: 모든 테이블이 생성되고, 모델 간 Eloquent 관계가 올바르게 설정됩니다.
데이터베이스 마이그레이션이 성공적으로 실행됩니다.
Step 3: 역할 기반 접근 제어 및 시더
관리자/회원 역할 기반 접근 제어와 시더 데이터를 설정해주세요.
1. **미들웨어 생성** (AdminMiddleware):
- 사용자의 role이 'admin'인지 확인
- admin이 아닌 경우 403 에러 또는 대시보드로 리다이렉트
- `app/Http/Middleware/AdminMiddleware.php` 생성 후 커널에 등록
2. **시더(Seeder) 생성**:
a. **AdminSeeder**:
- 관리자 계정 1개 생성
(email: [email protected], password: password, role: admin)
b. **MemberSeeder**:
- 테스트 회원 3명 생성
(예: 김철수, 이영희, 박지민 — role: member)
- 각 회원에게 이용권(Membership) 1개씩 부여:
* 김철수: 10회권, 오늘부터 30일간, 잔여 8회
* 이영희: 20회권, 오늘부터 60일간, 잔여 15회
* 박지민: 10회권, 7일 전 만료, 잔여 3회 (만료된 이용권 테스트용)
c. **SlotSeeder**:
- 이번 주 월~금, 매일 09:00~18:00 사이에 1시간 단위 슬롯 생성
(09:00-10:00, 10:00-11:00, ..., 17:00-18:00)
- 일부 슬롯에 더미 예약 데이터 3~5개 추가
3. `DatabaseSeeder`에서 모든 시더를 호출하도록 설정하고
`php artisan migrate:fresh --seed` 실행해주세요.
기대 결과: 관리자 1명, 회원 3명(이용권 포함), 이번 주 PT 슬롯들이 생성됩니다.
[email protected] 으로 로그인하면 관리자 기능에 접근 가능합니다.
Step 4: 관리자 — 슬롯 관리 페이지
관리자가 PT 슬롯을 등록/수정/삭제할 수 있는 페이지를 구현해주세요.
1. **SlotController 생성** (admin 전용):
- `index()`: 날짜별 슬롯 목록 조회 (기본: 오늘 이후 슬롯)
* 각 슬롯의 예약 현황(예약 수 / 최대 인원) 함께 표시
- `store(Request $request)`: 슬롯 생성
* 유효성 검증: slot_date, start_time, end_time 필수
* 동일 날짜/시간에 중복 슬롯 방지
* 한 번에 여러 슬롯 일괄 생성 가능 (날짜 범위 + 시간 범위 선택)
- `destroy($id)`: 슬롯 삭제 (예약이 없는 경우만)
- `toggleAvailability($id)`: 예약 가능/불가능 토글
2. **라우트 설정** (admin 미들웨어 적용):
- `GET /admin/slots` → SlotController@index
- `POST /admin/slots` → SlotController@store
- `DELETE /admin/slots/{id}` → SlotController@destroy
- `PATCH /admin/slots/{id}/toggle` → SlotController@toggleAvailability
3. **React 컴포넌트 생성**:
- `resources/js/Pages/Admin/Slots/Index.jsx`: 슬롯 관리 페이지
* 날짜별로 그룹화된 슬롯 목록 표시
* 각 슬롯: 시간, 예약 현황 (예: "1/1 예약됨"), 상태
* 예약된 슬롯은 예약자 이름 표시
* "슬롯 추가" 버튼 → 모달 또는 인라인 폼
- 슬롯 생성 폼:
* 날짜 선택 (단일 날짜 또는 날짜 범위)
* 시작 시간 ~ 종료 시간 (30분 또는 1시간 단위)
* 일괄 생성 옵션: "월~금 반복" 체크 시 선택한 시간대를 평일 전체에 생성
* 최대 인원 설정 (기본값 1)
- 날짜 네비게이션: 이전/다음 날짜 이동 버튼
4. Tailwind CSS로 깔끔하게 스타일링해주세요.
기대 결과: 관리자가 /admin/slots 페이지에서 PT 슬롯을 등록/조회/삭제할 수 있습니다.
일괄 생성 기능으로 한 주치 슬롯을 한 번에 만들 수 있습니다.
각 슬롯의 예약 현황이 한눈에 보입니다.
Step 5: 관리자 — 회원 및 이용권 관리
관리자가 회원과 이용권을 관리할 수 있는 페이지를 구현해주세요.
1. **MemberController 생성** (admin 전용):
- `index()`: 전체 회원 목록 조회
* 각 회원의 활성 이용권 정보 (잔여 횟수, 만료일) 함께 표시
- `show($id)`: 회원 상세 정보 (이용권 목록, 예약 이력, 출석 현황)
2. **MembershipController 생성** (admin 전용):
- `store(Request $request)`: 이용권 발급
* user_id, total_sessions, starts_at, expires_at 필수
* remaining_sessions = total_sessions로 초기 설정
- `update($id)`: 이용권 수정 (잔여 횟수 조정, 기간 연장 등)
3. **라우트 설정** (admin 미들웨어 적용):
- `GET /admin/members` → MemberController@index
- `GET /admin/members/{id}` → MemberController@show
- `POST /admin/memberships` → MembershipController@store
- `PUT /admin/memberships/{id}` → MembershipController@update
4. **React 컴포넌트 생성**:
- `resources/js/Pages/Admin/Members/Index.jsx`: 회원 목록
* 회원 이름, 연락처, 활성 이용권 잔여 횟수/만료일 표시
* 이용권 없거나 만료된 회원은 "이용권 없음" 표시 (빨간색)
* "이용권 발급" 버튼
- `resources/js/Pages/Admin/Members/Show.jsx`: 회원 상세
* 회원 기본 정보
* 이용권 이력 (현재 + 과거 이용권 목록)
* 최근 예약 및 출석 이력 (최근 30일)
* 이용권 수정 폼 (잔여 횟수 조정, 기간 연장)
- 이용권 발급 모달/폼:
* 회원 선택, 총 횟수, 시작일, 만료일 입력
* 일반적인 옵션 프리셋: 10회/1개월, 20회/2개월, 30회/3개월
5. Tailwind CSS로 스타일링해주세요.
기대 결과: 관리자가 회원 목록에서 각 회원의 이용권 현황을 한눈에 확인하고,
이용권을 발급/수정할 수 있습니다.
회원 상세 페이지에서 예약 이력과 출석 현황을 확인할 수 있습니다.
Step 6: 회원 — 예약 페이지
회원이 관리자가 등록한 슬롯에 예약할 수 있는 페이지를 구현해주세요.
1. **BookingController 생성** (회원용):
- `index()`: 예약 가능한 슬롯 목록 조회
* 오늘 이후의 슬롯만 표시
* is_available이 true이고 max_members에 여유가 있는 슬롯만
* 날짜별로 그룹화하여 표시
* 현재 회원의 활성 이용권 정보도 함께 전달
- `store(Request $request)`: 예약 생성
* 유효성 검증:
- 회원에게 활성 이용권이 있는지 확인
- 이용권 기간(starts_at ~ expires_at) 내인지 확인
- 잔여 횟수(remaining_sessions)가 1 이상인지 확인
- 해당 슬롯에 여유가 있는지 확인
- 이미 예약한 슬롯인지 중복 체크
* 검증 통과 시 예약 생성 (status: 'reserved')
* **주의: 예약 시점에는 횟수를 차감하지 않음** (수업 완료 후 차감)
- `myBookings()`: 내 예약 목록 조회
- `cancel($id)`: 예약 취소
* 해당 예약이 본인 것인지 확인
* 수업 시작 전인 예약만 취소 가능
* status를 'cancelled'로 변경
2. **라우트 설정** (auth 미들웨어):
- `GET /bookings/available` → BookingController@index
- `POST /bookings` → BookingController@store
- `GET /bookings/my` → BookingController@myBookings
- `DELETE /bookings/{id}` → BookingController@cancel
3. **React 컴포넌트 생성**:
- `resources/js/Pages/Bookings/Available.jsx`: 예약 가능 슬롯 목록
* 상단에 내 이용권 정보 카드: 잔여 횟수, 만료일, 상태
* 이용권이 없거나 만료된 경우 "이용권이 없습니다" 안내 메시지
* 날짜별로 그룹화된 슬롯 목록
* 각 슬롯: 날짜, 시간, 잔여 자리
* "예약하기" 버튼 클릭 시 확인 후 예약 생성
* 이용권 기간 밖의 슬롯은 비활성화 + "이용권 기간 외" 표시
- `resources/js/Pages/Bookings/My.jsx`: 내 예약 목록
* 예약 목록 (날짜, 시간, 상태)
* 상태별 색상: reserved(파란색), completed(초록색), cancelled(회색), no_show(빨간색)
* 예약 취소 버튼 (수업 시작 전만)
* 각 예약에 관리자 리플(운동 기록)이 있으면 함께 표시
4. 네비게이션 바에 회원용 메뉴 추가:
- "예약하기" → /bookings/available
- "내 예약" → /bookings/my
기대 결과: 회원이 예약 가능한 슬롯을 날짜별로 확인하고, 이용권 조건이 충족되면
예약할 수 있습니다. 이용권이 없거나 만료/소진된 경우 예약이 거부됩니다.
내 예약 페이지에서 예약 현황 확인 및 취소가 가능합니다.
Step 7: 관리자 — 예약 현황 대시보드
관리자가 예약 현황을 일별/주별/월별로 조회할 수 있는 대시보드를 구현해주세요.
1. **DashboardController 생성** (admin 전용):
- `index(Request $request)`: 예약 현황 대시보드
* view 파라미터: 'day', 'week', 'month' (기본: 'day')
* date 파라미터: 기준 날짜 (기본: 오늘)
* 선택한 뷰에 따라 해당 기간의 모든 슬롯과 예약 정보 조회
* 예약 통계: 총 슬롯 수, 예약된 슬롯 수, 출석 완료 수, 노쇼 수
2. **라우트 설정**:
- `GET /admin/dashboard` → DashboardController@index
3. **React 컴포넌트 생성**:
- `resources/js/Pages/Admin/Dashboard.jsx`: 관리자 대시보드
* 상단: 뷰 전환 탭 (일별 | 주별 | 월별)
* 상단: 날짜 네비게이션 (이전/다음 버튼 + 날짜 표시)
**일별 뷰 (Day View)**:
* 선택한 날짜의 시간대별 슬롯 목록
* 각 슬롯: 시간, 예약자 이름(또는 "빈 슬롯"), 출석 상태, 리플 유무
* 한눈에 하루 스케줄 파악 가능한 타임라인 형태
**주별 뷰 (Week View)**:
* 월~일 7일 그리드
* 각 날짜 셀에 슬롯 수, 예약된 수 요약
* 날짜 클릭 시 해당 일별 뷰로 이동
**월별 뷰 (Month View)**:
* 달력 형태로 한 달 표시
* 각 날짜 셀에 "예약 3/5" 형태로 요약
* 날짜 클릭 시 해당 일별 뷰로 이동
* 통계 카드: 총 슬롯, 예약률, 출석률, 노쇼율
4. Tailwind CSS로 대시보드를 깔끔하게 스타일링해주세요.
기대 결과: 관리자가 /admin/dashboard에서 일별/주별/월별로 예약 현황을 확인할 수 있습니다.
일별 뷰에서는 시간대별 상세 스케줄을, 주별/월별 뷰에서는 요약 정보를 볼 수 있습니다.
Step 8: 관리자 — 출석 체크 및 횟수 차감
관리자가 출석 체크를 하고, 완료 시 이용권 횟수가 차감되는 기능을 구현해주세요.
1. **AttendanceController 생성** (admin 전용):
- `checkIn($bookingId)`: 출석 체크
* booking의 attended를 true로 변경
* status를 'completed'로 변경
* 연결된 membership의 remaining_sessions를 1 감소
* session_deducted를 true로 변경
* 성공 메시지 반환
- `noShow($bookingId)`: 노쇼 처리
* status를 'no_show'로 변경
* 노쇼도 횟수 차감할지 여부는 설정에 따라 (기본: 차감)
* session_deducted를 true로 변경
- `undoCheckIn($bookingId)`: 출석 체크 취소 (실수 대비)
* attended를 false로, status를 'reserved'로 복원
* remaining_sessions를 1 증가
* session_deducted를 false로 변경
2. **라우트 설정** (admin 미들웨어):
- `PATCH /admin/bookings/{id}/check-in` → AttendanceController@checkIn
- `PATCH /admin/bookings/{id}/no-show` → AttendanceController@noShow
- `PATCH /admin/bookings/{id}/undo` → AttendanceController@undoCheckIn
3. **관리자 대시보드 일별 뷰 수정** (Dashboard.jsx):
- 각 예약 항목에 출석 관리 버튼 추가:
* 예약 상태가 'reserved'일 때: "출석" 버튼(초록), "노쇼" 버튼(빨간)
* 예약 상태가 'completed'일 때: "출석완료" 표시 + "취소" 버튼(회색)
* 예약 상태가 'no_show'일 때: "노쇼" 표시 + "취소" 버튼(회색)
- 출석 체크 시 해당 회원의 잔여 횟수도 함께 표시
(예: "김철수 — 잔여 7/10회")
4. **자동 횟수 차감 (Laravel Scheduler)** — 선택사항:
- 예약 시간이 지났는데 출석 처리가 안 된 예약을 자동으로 노쇼 처리
- `app/Console/Kernel.php`에 스케줄 등록:
* 매시간 실행: 시작 시간이 지난 'reserved' 상태 예약을 'no_show'로 변경 및 횟수 차감
기대 결과: 관리자가 대시보드에서 각 예약의 출석/노쇼를 체크할 수 있고,
체크 시 이용권 잔여 횟수가 자동으로 차감됩니다.
실수로 잘못 체크한 경우 취소(되돌리기)도 가능합니다.
Step 9: 관리자 — 운동 기록 리플(코멘트)
관리자가 완료된 예약에 운동 기록 리플(코멘트)을 작성할 수 있는 기능을 구현해주세요.
1. **CommentController 생성** (admin 전용):
- `store(Request $request, $bookingId)`: 리플 작성
* 유효성 검증: content 필수
* 해당 booking의 status가 'completed'인 경우만 작성 가능
* admin_id는 auth()->id()로 자동 설정
- `update(Request $request, $id)`: 리플 수정
- `destroy($id)`: 리플 삭제
2. **라우트 설정** (admin 미들웨어):
- `POST /admin/bookings/{bookingId}/comments` → CommentController@store
- `PUT /admin/comments/{id}` → CommentController@update
- `DELETE /admin/comments/{id}` → CommentController@destroy
3. **관리자 대시보드 수정** (Dashboard.jsx):
- 출석 완료된('completed') 예약 항목에 리플 섹션 추가:
* 기존 리플 목록 표시 (작성 시간, 내용)
* 리플 작성 폼: textarea + "등록" 버튼
* 리플 수정/삭제 버튼
- 리플 작성 폼은 인라인으로 펼침/접기 가능하게 구현
- 리플 내용 예시 가이드:
"오늘 운동 내용: 스쿼트 3x10, 데드리프트 3x8, 레그프레스 3x12"
4. **회원 내 예약 페이지 수정** (Bookings/My.jsx):
- 완료된 예약에 관리자 리플이 있으면 표시
- 리플 내용은 읽기 전용 (회원은 수정/삭제 불가)
- 리플이 있는 예약은 아이콘(💬 또는 메모 아이콘)으로 구분
기대 결과: 관리자가 출석 완료된 예약에 운동 기록 리플을 작성/수정/삭제할 수 있습니다.
회원은 내 예약 페이지에서 관리자가 작성한 운동 기록을 확인할 수 있습니다.
Step 10: 네비게이션 및 역할별 레이아웃 정리
관리자와 회원의 네비게이션 및 레이아웃을 역할에 맞게 정리해주세요.
1. **네비게이션 바 수정** (resources/js/Layouts/AuthenticatedLayout.jsx):
- 로그인한 사용자의 role에 따라 메뉴 분기:
**관리자 (role: 'admin') 메뉴**:
- 대시보드 → /admin/dashboard
- 슬롯 관리 → /admin/slots
- 회원 관리 → /admin/members
- (선택) 설정
**회원 (role: 'member') 메뉴**:
- 예약하기 → /bookings/available
- 내 예약 → /bookings/my
- (선택) 내 정보
2. **로그인 후 리다이렉트 분기**:
- admin으로 로그인 → /admin/dashboard로 이동
- member로 로그인 → /bookings/available로 이동
- `app/Http/Controllers/Auth/AuthenticatedSessionController.php` 수정
또는 `RouteServiceProvider`의 HOME 상수 수정
3. **공통 UI 개선**:
- 플래시 메시지 컴포넌트: 성공/에러 알림 표시 (Inertia flash messages)
- 로딩 상태 표시 (Inertia progress indicator)
- 확인 모달 컴포넌트 (예약 취소, 삭제 등에 사용)
- 빈 상태 컴포넌트 ("데이터가 없습니다" 표시용)
4. **반응형 디자인 확인**:
- 모바일에서도 예약 목록과 대시보드가 잘 보이는지 확인
- 필요 시 모바일용 레이아웃 조정
기대 결과: 관리자와 회원이 각각의 역할에 맞는 메뉴를 보고,
로그인 후 적절한 페이지로 리다이렉트됩니다.
플래시 메시지와 확인 모달이 전체 앱에서 일관되게 동작합니다.
📊 추가 정보
- 총 소요 시간: 약 2~3시간
- 권장 도구: Claude Code, Cursor, Bolt 등 AI 코딩 도구
- 테스트 계정:
- 관리자: [email protected] / password
- 회원: 시더에서 생성된 회원 계정
총 10단계로 구성했습니다. 핵심 흐름은:
- Step 1~3: 프로젝트 초기화, DB 설계, 역할/시더
- Step 4~5: 관리자 기능 (슬롯 관리, 회원/이용권 관리)
- Step 6: 회원 기능 (예약 생성/조회/취소)
- Step 7: 관리자 대시보드 (일/주/월별 현황)
- Step 8~9: 출석 체크 + 횟수 차감, 운동 기록 리플
- Step 10: 네비게이션 및 UI 정리
|
댓글을 작성하시려면 로그인이 필요합니다.
댓글 1개
위 의뢰 내용을 바탕으로 바이브코딩 레시피를 만들어 봤습니다
아래는 결과물입니다
https://github.com/kagla/pt-booking-system