๐ŸŽฌ ๋…๋ฆฝ์˜ํ™” ์ƒ์˜ํšŒ ํ‹ฐ์ผ“ ์˜ˆ๋งค ํŽ˜์ด์ง€ - ์ขŒ์„ ์„ ํƒ & ์˜ˆ๋งค ํ™•์ธ์„œ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ž…๋ฌธ PHP + JavaScript Laravel + React AI: Claude

๐ŸŽฌ ๋…๋ฆฝ์˜ํ™” ์ƒ์˜ํšŒ ํ‹ฐ์ผ“ ์˜ˆ๋งค ํŽ˜์ด์ง€ - ์ขŒ์„ ์„ ํƒ & ์˜ˆ๋งค ํ™•์ธ์„œ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… indie-film-screening-ticket-booking
์กฐํšŒ์ˆ˜ 99 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐ŸŽฌ ๋…๋ฆฝ์˜ํ™” ์ƒ์˜ํšŒ ํ‹ฐ์ผ“ ์˜ˆ๋งค ํŽ˜์ด์ง€

๋…๋ฆฝ์˜ํ™”๊ด€์ด๋‚˜ ์†Œ๊ทœ๋ชจ ์ƒ์˜ํšŒ๋ฅผ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ํ‹ฐ์ผ“ ์˜ˆ๋งค ์‹œ์Šคํ…œ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. Laravel 13.x ๋ฐฑ์—”๋“œ API์™€ React 19 ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ตฌ์„ฑํ•˜๋ฉฐ, ๊ณ ๊ฐ์ด ์ƒ์˜ ์ผ์ •์„ ํ™•์ธํ•˜๊ณ  ์ขŒ์„์„ ์„ ํƒํ•ด ์˜ˆ๋งคํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ํ’€์Šคํƒ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ๊ธฐ๋Šฅ

  • ์ƒ์˜ ์ผ์ • ๋ชฉ๋ก: ์˜ํ™” ์ œ๋ชฉ, ์ƒ์˜ ๋‚ ์งœ/์‹œ๊ฐ„, ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ ํ‘œ์‹œ
  • ์ขŒ์„ ์„ ํƒ UI: ์ขŒ์„ ๋ฐฐ์น˜๋„์—์„œ ์˜ˆ์•ฝ ๊ฐ€๋Šฅ/๋ถˆ๊ฐ€๋Šฅ ์ขŒ์„ ๊ตฌ๋ถ„ (๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ)
  • ์˜ˆ๋งค ํผ: ์ด๋ฆ„, ์ด๋ฉ”์ผ, ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ
  • ์˜ˆ๋งค ํ™•์ธ ํŽ˜์ด์ง€: ์˜ˆ๋งค ๋ฒˆํ˜ธ, ์˜ํ™” ์ •๋ณด, ์ขŒ์„ ๋ฒˆํ˜ธ, ์˜ˆ๋งค์ž ์ •๋ณด ํ‘œ์‹œ
  • ์˜ˆ๋งค ๋‚ด์—ญ ์กฐํšŒ: ์˜ˆ๋งค ๋ฒˆํ˜ธ๋กœ ์˜ˆ๋งค ์ •๋ณด ํ™•์ธ

๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฐฑ์—”๋“œ: Laravel 13.x (RESTful API ์„œ๋ฒ„, SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)
  • ํ”„๋ก ํŠธ์—”๋“œ: React 19 (Vite ํ”„๋กœ์ ํŠธ, Tailwind CSS)
  • API ํ†ต์‹ : fetch ๋˜๋Š” axios๋กœ Laravel API ํ˜ธ์ถœ

๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ (์„ ํƒ)

  • ์ขŒ์„ ๋“ฑ๊ธ‰๋ณ„ ๊ฐ€๊ฒฉ ์ฐจ๋“ฑ (์ผ๋ฐ˜์„ 10,000์›, VIP์„ 15,000์›)
  • ์˜ˆ๋งค ์ทจ์†Œ ๊ธฐ๋Šฅ (์˜ˆ๋งค ๋ฒˆํ˜ธ๋กœ ์ทจ์†Œ)
  • ์˜ํ™” ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (Laravel Storage ํ™œ์šฉ)
  • ์˜ˆ๋งค ์™„๋ฃŒ ์‹œ ์ž๋™ ์ด๋ฉ”์ผ ๋ฐœ์†ก (Laravel Mail)
  • ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ ์นœํ™”์ )

ํ•™์Šต ๋ชฉํ‘œ

  • Laravel ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ๋ชจ๋ธ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„
  • RESTful API ์—”๋“œํฌ์ธํŠธ ์ž‘์„ฑ (GET, POST)
  • React์—์„œ fetch/axios๋กœ API ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
  • useState๋กœ ์ขŒ์„ ์„ ํƒ ์ƒํƒœ ๊ด€๋ฆฌ
  • ํผ ์ œ์ถœ๊ณผ ์‘๋‹ต ์ฒ˜๋ฆฌ
  • ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง (์˜ˆ์•ฝ ๊ฐ€๋Šฅ/๋ถˆ๊ฐ€๋Šฅ ์ขŒ์„ ํ‘œ์‹œ)

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x์™€ React 19 ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ
  • โ€ข ๋ฐฑ์—”๋“œ(Laravel)์™€ ํ”„๋ก ํŠธ์—”๋“œ(React)๋ฅผ ๋ณ„๋„ ํ”„๋กœ์ ํŠธ๋กœ ๋ถ„๋ฆฌ
  • โ€ข Laravel์€ API ์ „์šฉ ์„œ๋ฒ„๋กœ ๊ตฌ์„ฑ (routes/api.php ์‚ฌ์šฉ)
  • โ€ข React๋Š” Vite๋กœ ์ƒ์„ฑํ•˜๊ณ  fetch/axios๋กœ API ํ˜ธ์ถœ
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๊ฐ„๋‹จํ•œ ์„ค์ •)
  • โ€ข Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง
  • โ€ข ๋ณต์žกํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Redux, Zustand ๋“ฑ) ์‚ฌ์šฉ ๊ธˆ์ง€ - useState๋งŒ ์‚ฌ์šฉ
  • โ€ข ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ(WebSocket) ๊ธˆ์ง€ - HTTP ์š”์ฒญ๋งŒ ์‚ฌ์šฉ
  • โ€ข ์ธ์ฆ/๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์—†์Œ (์ž…๋ฌธ์ž์šฉ ๋‹จ์ˆœํ™”)
  • โ€ข ์ขŒ์„์€ 5x6 ๊ทธ๋ฆฌ๋“œ (30์„) ๊ณ ์ •์œผ๋กœ ์‹œ์ž‘

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
mkdir indie-film-screening-ticket-booking && cd indie-film-screening-ticket-booking

์ด ํด๋”์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

# Laravel 13.x ๋ฐฑ์—”๋“œ API ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

1. `backend` ํด๋”์— Laravel 13.x ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:
   ```bash
   composer create-project laravel/laravel:^13.0 backend
   cd backend
   ```

2. `.env` ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์—ฌ SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •:
   ```
   DB_CONNECTION=sqlite
   # DB_HOST, DB_PORT, DB_DATABASE ๋“ฑ ๋‹ค๋ฅธ DB ์„ค์ •์€ ์ฃผ์„ ์ฒ˜๋ฆฌ
   ```

3. `database/database.sqlite` ํŒŒ์ผ ์ƒ์„ฑ:
   ```bash
   touch database/database.sqlite
   ```

4. CORS ์„ค์ • (React ํ”„๋ก ํŠธ์—”๋“œ์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด):
   - `config/cors.php` ํŒŒ์ผ์—์„œ `'allowed_origins'`๋ฅผ `['http://localhost:5173']`๋กœ ์„ค์ • (Vite ๊ธฐ๋ณธ ํฌํŠธ)
   - ๋˜๋Š” `'supports_credentials' => true` ํ™•์ธ

5. API ๋ผ์šฐํŠธ ํ™•์ธ:
   - `routes/api.php` ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ (Laravel 13.x ๊ธฐ๋ณธ ์ œ๊ณต)

6. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ 2๊ฐœ ์ƒ์„ฑ:
   ```bash
   php artisan make:migration create_screenings_table
   php artisan make:migration create_bookings_table
   ```

7. `database/migrations/xxxx_create_screenings_table.php` ์ž‘์„ฑ:
   - ์ปฌ๋Ÿผ: id, title (์˜ํ™” ์ œ๋ชฉ), screening_date (์ƒ์˜ ๋‚ ์งœ), screening_time (์ƒ์˜ ์‹œ๊ฐ„), poster_url (ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ URL, nullable), total_seats (์ด ์ขŒ์„ ์ˆ˜, ๊ธฐ๋ณธ๊ฐ’ 30), timestamps

8. `database/migrations/xxxx_create_bookings_table.php` ์ž‘์„ฑ:
   - ์ปฌ๋Ÿผ: id, screening_id (์™ธ๋ž˜ํ‚ค), seat_number (์ขŒ์„ ๋ฒˆํ˜ธ, ์˜ˆ: 'A1'), customer_name (์˜ˆ๋งค์ž ์ด๋ฆ„), customer_email, customer_phone, booking_number (์˜ˆ๋งค ๋ฒˆํ˜ธ, ์œ ๋‹ˆํฌ), timestamps
   - ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ: screening_id๋Š” screenings ํ…Œ์ด๋ธ” ์ฐธ์กฐ, onDelete('cascade')

9. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰:
   ```bash
   php artisan migrate
   ```

10. ์‹œ๋” ํŒŒ์ผ ์ƒ์„ฑํ•˜์—ฌ ์ƒ˜ํ”Œ ์ƒ์˜ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€:
    ```bash
    php artisan make:seeder ScreeningSeeder
    ```
    - `database/seeders/ScreeningSeeder.php`์— ์ƒ˜ํ”Œ ์˜ํ™” 2~3๊ฐœ ์ถ”๊ฐ€ (์ œ๋ชฉ, ๋‚ ์งœ, ์‹œ๊ฐ„, ํฌ์Šคํ„ฐ URL์€ Unsplash placeholder ์‚ฌ์šฉ)
    - `database/seeders/DatabaseSeeder.php`์—์„œ ScreeningSeeder ํ˜ธ์ถœ
    - ์‹คํ–‰: `php artisan db:seed`

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ค€๋น„๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x ๋ฐฑ์—”๋“œ ํ”„๋กœ์ ํŠธ๊ฐ€ `backend` ํด๋”์— ์ƒ์„ฑ๋˜๊ณ , SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— `screenings`์™€ `bookings` ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ƒ˜ํ”Œ ์ƒ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹œ๋”๋ฅผ ํ†ตํ•ด ์ถ”๊ฐ€๋˜์–ด API ํ…Œ์ŠคํŠธ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

2
Step 2
# Laravel API ์—”๋“œํฌ์ธํŠธ ์ž‘์„ฑ

`backend` ํด๋”์—์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

1. Screening ๋ชจ๋ธ ์ƒ์„ฑ:
   ```bash
   php artisan make:model Screening
   ```
   - `app/Models/Screening.php`์— `$fillable = ['title', 'screening_date', 'screening_time', 'poster_url', 'total_seats'];` ์ถ”๊ฐ€
   - bookings ๊ด€๊ณ„ ์ •์˜: `public function bookings() { return $this->hasMany(Booking::class); }`

2. Booking ๋ชจ๋ธ ์ƒ์„ฑ:
   ```bash
   php artisan make:model Booking
   ```
   - `app/Models/Booking.php`์— `$fillable = ['screening_id', 'seat_number', 'customer_name', 'customer_email', 'customer_phone', 'booking_number'];` ์ถ”๊ฐ€
   - screening ๊ด€๊ณ„ ์ •์˜: `public function screening() { return $this->belongsTo(Screening::class); }`

3. API ์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ:
   ```bash
   php artisan make:controller Api/ScreeningController
   php artisan make:controller Api/BookingController
   ```

4. `app/Http/Controllers/Api/ScreeningController.php` ์ž‘์„ฑ:
   - `index()` ๋ฉ”์„œ๋“œ: ๋ชจ๋“  ์ƒ์˜ ์ผ์ • ๋ฐ˜ํ™˜ (JSON)
     ```php
     public function index() {
         $screenings = Screening::all();
         return response()->json($screenings);
     }
     ```
   - `show($id)` ๋ฉ”์„œ๋“œ: ํŠน์ • ์ƒ์˜ ์ •๋ณด + ์˜ˆ์•ฝ๋œ ์ขŒ์„ ๋ฒˆํ˜ธ ๋ฐฐ์—ด ๋ฐ˜ํ™˜
     ```php
     public function show($id) {
         $screening = Screening::with('bookings:screening_id,seat_number')->findOrFail($id);
         $bookedSeats = $screening->bookings->pluck('seat_number');
         return response()->json([
             'screening' => $screening,
             'bookedSeats' => $bookedSeats
         ]);
     }
     ```

5. `app/Http/Controllers/Api/BookingController.php` ์ž‘์„ฑ:
   - `store(Request $request)` ๋ฉ”์„œ๋“œ: ์˜ˆ๋งค ์ƒ์„ฑ
     - ์ž…๋ ฅ: screening_id, seat_number, customer_name, customer_email, customer_phone
     - ์ขŒ์„ ์ค‘๋ณต ์ฒดํฌ (์ด๋ฏธ ์˜ˆ์•ฝ๋œ ์ขŒ์„์ธ์ง€ ํ™•์ธ)
     - booking_number ์ƒ์„ฑ (์˜ˆ: 'BK' + timestamp + random 4์ž๋ฆฌ)
     - ์˜ˆ๋งค ์ €์žฅ ํ›„ ์˜ˆ๋งค ์ •๋ณด ๋ฐ˜ํ™˜ (JSON)
   - `show($bookingNumber)` ๋ฉ”์„œ๋“œ: ์˜ˆ๋งค ๋ฒˆํ˜ธ๋กœ ์˜ˆ๋งค ์ •๋ณด ์กฐํšŒ
     - Booking::where('booking_number', $bookingNumber)->with('screening')->firstOrFail() ๋ฐ˜ํ™˜

6. `routes/api.php`์— ๋ผ์šฐํŠธ ์ถ”๊ฐ€:
   ```php
   use App\Http\Controllers\Api\ScreeningController;
   use App\Http\Controllers\Api\BookingController;

   Route::get('/screenings', [ScreeningController::class, 'index']);
   Route::get('/screenings/{id}', [ScreeningController::class, 'show']);
   Route::post('/bookings', [BookingController::class, 'store']);
   Route::get('/bookings/{bookingNumber}', [BookingController::class, 'show']);
   ```

7. Laravel ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰:
   ```bash
   php artisan serve
   ```
   - ๊ธฐ๋ณธ ํฌํŠธ: http://localhost:8000

8. API ํ…Œ์ŠคํŠธ (Postman ๋˜๋Š” curl):
   - GET http://localhost:8000/api/screenings (์ƒ์˜ ๋ชฉ๋ก)
   - GET http://localhost:8000/api/screenings/1 (ํŠน์ • ์ƒ์˜ + ์˜ˆ์•ฝ๋œ ์ขŒ์„)

API๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel API ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์™„์„ฑ๋˜์–ด ์ƒ์˜ ์ผ์ • ์กฐํšŒ, ํŠน์ • ์ƒ์˜์˜ ์˜ˆ์•ฝ๋œ ์ขŒ์„ ์กฐํšŒ, ์˜ˆ๋งค ์ƒ์„ฑ, ์˜ˆ๋งค ์กฐํšŒ ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. Postman์ด๋‚˜ curl๋กœ ํ…Œ์ŠคํŠธํ•˜์—ฌ JSON ์‘๋‹ต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3
Step 3
# React 19 ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ํด๋”(`indie-film-screening-ticket-booking`)๋กœ ๋Œ์•„์™€์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

1. `frontend` ํด๋”์— React 19 ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (Vite ์‚ฌ์šฉ):
   ```bash
   npm create vite@latest frontend -- --template react
   cd frontend
   npm install
   ```

2. Tailwind CSS ์„ค์น˜ ๋ฐ ์„ค์ •:
   ```bash
   npm install -D tailwindcss postcss autoprefixer
   npx tailwindcss init -p
   ```
   - `tailwind.config.js` ์ˆ˜์ •:
     ```js
     content: ["./index.html", "./src/**/*.{js,jsx}"],
     ```
   - `src/index.css`์— Tailwind directives ์ถ”๊ฐ€:
     ```css
     @tailwind base;
     @tailwind components;
     @tailwind utilities;
     ```

3. axios ์„ค์น˜ (API ํ†ต์‹ ์šฉ):
   ```bash
   npm install axios
   ```

4. `src/App.jsx` ์ดˆ๊ธฐ ๊ตฌ์กฐ ์ž‘์„ฑ:
   - useState๋กœ `screenings` ์ƒํƒœ ๊ด€๋ฆฌ
   - useEffect์—์„œ `http://localhost:8000/api/screenings` ํ˜ธ์ถœํ•˜์—ฌ ์ƒ์˜ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
   - ์ƒ์˜ ๋ชฉ๋ก์„ ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ (์˜ํ™” ์ œ๋ชฉ, ๋‚ ์งœ, ์‹œ๊ฐ„, ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€)
   - ๊ฐ ์นด๋“œ์— "์˜ˆ๋งคํ•˜๊ธฐ" ๋ฒ„ํŠผ ์ถ”๊ฐ€ (ํด๋ฆญ ์‹œ ํ•ด๋‹น ์ƒ์˜ ID ์ €์žฅ)

5. Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง:
   - ์นด๋“œ ๋ ˆ์ด์•„์›ƒ: ๊ทธ๋ฆฌ๋“œ ๋˜๋Š” flexbox
   - ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€: aspect-ratio ์œ ์ง€, rounded corners
   - ๋ฒ„ํŠผ: bg-blue-600 hover:bg-blue-700 ๋“ฑ

6. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰:
   ```bash
   npm run dev
   ```
   - ๊ธฐ๋ณธ ํฌํŠธ: http://localhost:5173

7. ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธ:
   - ์ƒ์˜ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธ
   - CORS ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ Laravel `config/cors.php` ์žฌํ™•์ธ

ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ๋ณธ ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: React 19 ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , Tailwind CSS๊ฐ€ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ƒ์˜ ๋ชฉ๋ก์ด ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, Laravel API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

4
Step 4
# ์ขŒ์„ ์„ ํƒ UI ๊ตฌํ˜„

`frontend/src` ํด๋”์—์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

1. `src/components/SeatMap.jsx` ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - props: `bookedSeats` (๋ฐฐ์—ด), `selectedSeat` (๋ฌธ์ž์—ด), `onSeatSelect` (ํ•จ์ˆ˜)
   - ์ขŒ์„ ๋ฐฐ์น˜: 5ํ–‰(A~E) x 6์—ด(1~6) = 30์„ ๊ทธ๋ฆฌ๋“œ ์ƒ์„ฑ
   - ๊ฐ ์ขŒ์„์„ ๋ฒ„ํŠผ์œผ๋กœ ๋ Œ๋”๋ง:
     - ์˜ˆ์•ฝ ๊ฐ€๋Šฅ: ํด๋ฆญ ์‹œ `onSeatSelect` ํ˜ธ์ถœ, ๋ฐฐ๊ฒฝ์ƒ‰ ํšŒ์ƒ‰
     - ์˜ˆ์•ฝ ๋ถˆ๊ฐ€๋Šฅ (`bookedSeats`์— ํฌํ•จ): ํด๋ฆญ ๋ถˆ๊ฐ€, ๋ฐฐ๊ฒฝ์ƒ‰ ๋นจ๊ฐ„์ƒ‰, cursor-not-allowed
     - ์„ ํƒ๋œ ์ขŒ์„ (`selectedSeat`): ๋ฐฐ๊ฒฝ์ƒ‰ ์ดˆ๋ก์ƒ‰
   - Tailwind CSS๋กœ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ (grid grid-cols-6 gap-2)

2. `src/App.jsx` ์ˆ˜์ •:
   - "์˜ˆ๋งคํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ:
     - ์„ ํƒํ•œ ์ƒ์˜ ID๋กœ `http://localhost:8000/api/screenings/{id}` ํ˜ธ์ถœ
     - ์‘๋‹ต์—์„œ `screening` ์ •๋ณด์™€ `bookedSeats` ๋ฐฐ์—ด ์ €์žฅ
     - ์ขŒ์„ ์„ ํƒ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜ (์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง)
   - ์ƒํƒœ ์ถ”๊ฐ€:
     - `selectedScreening` (์„ ํƒํ•œ ์ƒ์˜ ์ •๋ณด)
     - `bookedSeats` (์˜ˆ์•ฝ๋œ ์ขŒ์„ ๋ฐฐ์—ด)
     - `selectedSeat` (์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์ขŒ์„)
   - `SeatMap` ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:
     - `onSeatSelect` ํ•จ์ˆ˜๋กœ `selectedSeat` ์ƒํƒœ ์—…๋ฐ์ดํŠธ

3. ์ขŒ์„ ์„ ํƒ ํ›„ ์˜ˆ๋งค ํผ ํ‘œ์‹œ:
   - ์ขŒ์„ ์„ ํƒ ํ›„ "๋‹ค์Œ" ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
   - ํด๋ฆญ ์‹œ ์˜ˆ๋งค ํผ ํ‘œ์‹œ (์ด๋ฆ„, ์ด๋ฉ”์ผ, ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ ํ•„๋“œ)

4. ์Šคํƒ€์ผ๋ง:
   - ์ขŒ์„ ๋ฒ„ํŠผ: w-10 h-10, ํ…์ŠคํŠธ ์ค‘์•™ ์ •๋ ฌ
   - ๋ฒ”๋ก€ ์ถ”๊ฐ€: ์˜ˆ์•ฝ ๊ฐ€๋Šฅ(ํšŒ์ƒ‰), ์˜ˆ์•ฝ ๋ถˆ๊ฐ€(๋นจ๊ฐ„์ƒ‰), ์„ ํƒ๋จ(์ดˆ๋ก์ƒ‰)

๋ธŒ๋ผ์šฐ์ €์—์„œ ์ขŒ์„ ์„ ํƒ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ขŒ์„ ๋ฐฐ์น˜๋„๊ฐ€ 5x6 ๊ทธ๋ฆฌ๋“œ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ์˜ˆ์•ฝ ๊ฐ€๋Šฅ/๋ถˆ๊ฐ€๋Šฅ ์ขŒ์„์ด ์ƒ‰์ƒ์œผ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ขŒ์„์„ ํด๋ฆญํ•˜๋ฉด ์„ ํƒ ์ƒํƒœ๊ฐ€ ์ดˆ๋ก์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ๋˜๊ณ , ์˜ˆ๋งค ํผ์œผ๋กœ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5
Step 5
# ์˜ˆ๋งค ํผ ์ œ์ถœ ๋ฐ ์˜ˆ๋งค ํ™•์ธ ํŽ˜์ด์ง€

`frontend/src` ํด๋”์—์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

1. `src/components/BookingForm.jsx` ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - props: `screening` (์ƒ์˜ ์ •๋ณด), `selectedSeat` (์„ ํƒํ•œ ์ขŒ์„), `onSubmit` (ํ•จ์ˆ˜)
   - ํผ ํ•„๋“œ:
     - ์ด๋ฆ„ (input text, required)
     - ์ด๋ฉ”์ผ (input email, required)
     - ์ „ํ™”๋ฒˆํ˜ธ (input tel, required)
   - ์ œ์ถœ ๋ฒ„ํŠผ: "์˜ˆ๋งค ์™„๋ฃŒ"
   - ํผ ์ œ์ถœ ์‹œ `onSubmit` ํ•จ์ˆ˜ ํ˜ธ์ถœ (์ž…๋ ฅ๊ฐ’ ์ „๋‹ฌ)

2. `src/App.jsx` ์ˆ˜์ •:
   - `BookingForm` ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง (์ขŒ์„ ์„ ํƒ ํ›„)
   - `handleBookingSubmit` ํ•จ์ˆ˜ ์ž‘์„ฑ:
     - axios POST ์š”์ฒญ: `http://localhost:8000/api/bookings`
     - ์š”์ฒญ ๋ณธ๋ฌธ: { screening_id, seat_number, customer_name, customer_email, customer_phone }
     - ์‘๋‹ต์—์„œ ์˜ˆ๋งค ์ •๋ณด ๋ฐ›์•„ `bookingConfirmation` ์ƒํƒœ์— ์ €์žฅ
     - ์˜ˆ๋งค ํ™•์ธ ํŽ˜์ด์ง€๋กœ ์ „ํ™˜

3. `src/components/BookingConfirmation.jsx` ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - props: `booking` (์˜ˆ๋งค ์ •๋ณด ๊ฐ์ฒด)
   - ํ‘œ์‹œ ๋‚ด์šฉ:
     - "์˜ˆ๋งค๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" ๋ฉ”์‹œ์ง€
     - ์˜ˆ๋งค ๋ฒˆํ˜ธ (ํฐ ๊ธ€์”จ๋กœ ๊ฐ•์กฐ)
     - ์˜ํ™” ์ œ๋ชฉ, ์ƒ์˜ ๋‚ ์งœ/์‹œ๊ฐ„
     - ์ขŒ์„ ๋ฒˆํ˜ธ
     - ์˜ˆ๋งค์ž ์ด๋ฆ„, ์ด๋ฉ”์ผ, ์ „ํ™”๋ฒˆํ˜ธ
   - "์ฒ˜์Œ์œผ๋กœ" ๋ฒ„ํŠผ (์ƒ์˜ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ)

4. `src/App.jsx`์—์„œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง:
   - `bookingConfirmation` ์ƒํƒœ๊ฐ€ ์žˆ์œผ๋ฉด `BookingConfirmation` ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ
   - ์—†์œผ๋ฉด ์ƒ์˜ ๋ชฉ๋ก ๋˜๋Š” ์ขŒ์„ ์„ ํƒ ํ™”๋ฉด ํ‘œ์‹œ

5. ์Šคํƒ€์ผ๋ง:
   - ํผ: ์„ธ๋กœ ๋ ˆ์ด์•„์›ƒ, ์ž…๋ ฅ ํ•„๋“œ ๊ฐ„๊ฒฉ, ๋ผ๋ฒจ ์ถ”๊ฐ€
   - ์˜ˆ๋งค ํ™•์ธ ํŽ˜์ด์ง€: ์นด๋“œ ํ˜•ํƒœ, ์ค‘์•™ ์ •๋ ฌ, ๋ฐฐ๊ฒฝ์ƒ‰ ๊ตฌ๋ถ„

6. ํ…Œ์ŠคํŠธ:
   - ์ƒ์˜ ์„ ํƒ โ†’ ์ขŒ์„ ์„ ํƒ โ†’ ์˜ˆ๋งค ํผ ์ž‘์„ฑ โ†’ ์˜ˆ๋งค ์™„๋ฃŒ โ†’ ํ™•์ธ ํŽ˜์ด์ง€ ํ‘œ์‹œ
   - Laravel ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜ˆ๋งค ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜๋Š”์ง€ ํ™•์ธ (SQLite ๋ธŒ๋ผ์šฐ์ € ๋˜๋Š” `php artisan tinker`๋กœ ํ™•์ธ)

์ „์ฒด ์˜ˆ๋งค ํ๋ฆ„์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์‚ฌ์šฉ์ž๊ฐ€ ์ขŒ์„์„ ์„ ํƒํ•˜๊ณ  ์˜ˆ๋งค ํผ์„ ์ž‘์„ฑํ•˜๋ฉด, Laravel API๋ฅผ ํ†ตํ•ด ์˜ˆ๋งค๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ์˜ˆ๋งค ํ™•์ธ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋งค ๋ฒˆํ˜ธ, ์˜ํ™” ์ •๋ณด, ์ขŒ์„ ์ •๋ณด๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

6
Step 6
# ์˜ˆ๋งค ๋‚ด์—ญ ์กฐํšŒ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ์ตœ์ข… ๋งˆ๋ฌด๋ฆฌ

`frontend/src` ํด๋”์—์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

1. `src/components/BookingLookup.jsx` ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - ์˜ˆ๋งค ๋ฒˆํ˜ธ ์ž…๋ ฅ ํ•„๋“œ (input text)
   - "์กฐํšŒ" ๋ฒ„ํŠผ
   - ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ `http://localhost:8000/api/bookings/{bookingNumber}` GET ์š”์ฒญ
   - ์‘๋‹ต ๋ฐ›์œผ๋ฉด ์˜ˆ๋งค ์ •๋ณด ํ‘œ์‹œ (BookingConfirmation ์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ ๋˜๋Š” ์œ ์‚ฌํ•œ ๋ ˆ์ด์•„์›ƒ)
   - ์˜ˆ๋งค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

2. `src/App.jsx` ์ˆ˜์ •:
   - ์ƒ์˜ ๋ชฉ๋ก ํŽ˜์ด์ง€ ์ƒ๋‹จ์— "์˜ˆ๋งค ์กฐํšŒ" ๋งํฌ ๋˜๋Š” ๋ฒ„ํŠผ ์ถ”๊ฐ€
   - ํด๋ฆญ ์‹œ `BookingLookup` ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ (์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง)
   - "๋’ค๋กœ ๊ฐ€๊ธฐ" ๋ฒ„ํŠผ์œผ๋กœ ์ƒ์˜ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

3. ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๊ฐœ์„ :
   - API ์š”์ฒญ ์‹คํŒจ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ (alert ๋˜๋Š” toast)
   - ์ขŒ์„ ์ค‘๋ณต ์˜ˆ์•ฝ ์‹œ๋„ ์‹œ Laravel์—์„œ ์—๋Ÿฌ ๋ฐ˜ํ™˜ โ†’ React์—์„œ ์ฒ˜๋ฆฌ

4. UI/UX ๊ฐœ์„ :
   - ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ (API ์š”์ฒญ ์ค‘ ์Šคํ”ผ๋„ˆ ๋˜๋Š” "๋กœ๋”ฉ ์ค‘..." ๋ฉ”์‹œ์ง€)
   - ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ํ™•์ธ (๋ชจ๋ฐ”์ผ ํ™”๋ฉด์—์„œ๋„ ์ž˜ ๋ณด์ด๋Š”์ง€)
   - ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ ์—†๋Š” ๊ฒฝ์šฐ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ‘œ์‹œ

5. README.md ์ž‘์„ฑ:
   - ํ”„๋กœ์ ํŠธ ์„ค๋ช…
   - ์„ค์น˜ ๋ฐฉ๋ฒ•:
     ```
     # ๋ฐฑ์—”๋“œ
     cd backend
     composer install
     cp .env.example .env
     php artisan key:generate
     touch database/database.sqlite
     php artisan migrate --seed
     php artisan serve

     # ํ”„๋ก ํŠธ์—”๋“œ (์ƒˆ ํ„ฐ๋ฏธ๋„)
     cd frontend
     npm install
     npm run dev
     ```
   - ์‚ฌ์šฉ ๋ฐฉ๋ฒ• (์Šคํฌ๋ฆฐ์ƒท ์ถ”๊ฐ€ ๊ถŒ์žฅ)
   - ๊ธฐ์ˆ  ์Šคํƒ ๋ช…์‹œ

6. ์ตœ์ข… ํ…Œ์ŠคํŠธ:
   - ์ „์ฒด ์˜ˆ๋งค ํ๋ฆ„ (์ƒ์˜ ์„ ํƒ โ†’ ์ขŒ์„ ์„ ํƒ โ†’ ์˜ˆ๋งค โ†’ ํ™•์ธ)
   - ์˜ˆ๋งค ์กฐํšŒ ๊ธฐ๋Šฅ
   - ์ค‘๋ณต ์ขŒ์„ ์˜ˆ์•ฝ ์‹œ๋„ (์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ™•์ธ)
   - ์—ฌ๋Ÿฌ ์ƒ์˜ ์ผ์ •์— ๋Œ€ํ•ด ์˜ˆ๋งค ํ…Œ์ŠคํŠธ

7. Git ์ปค๋ฐ‹ ๋ฐ GitHub ์—…๋กœ๋“œ:
   ```bash
   git init
   git add .
   git commit -m "Initial commit: ๋…๋ฆฝ์˜ํ™” ์ƒ์˜ํšŒ ํ‹ฐ์ผ“ ์˜ˆ๋งค ์‹œ์Šคํ…œ"
   git branch -M main
   git remote add origin https://github.com/your-username/indie-film-screening-ticket-booking.git
   git push -u origin main
   ```

ํ”„๋กœ์ ํŠธ๊ฐ€ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ์ตœ์ข… ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์˜ˆ๋งค ๋ฒˆํ˜ธ๋กœ ์˜ˆ๋งค ๋‚ด์—ญ์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜๊ณ , ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ๊ฐ€ ๊ฐœ์„ ๋ฉ๋‹ˆ๋‹ค. README.md๊ฐ€ ์ž‘์„ฑ๋˜์–ด ํ”„๋กœ์ ํŠธ ์„ค์น˜ ๋ฐ ์‹คํ–‰ ๋ฐฉ๋ฒ•์ด ๋ช…ํ™•ํ•ด์ง‘๋‹ˆ๋‹ค. ์ „์ฒด ํ”„๋กœ์ ํŠธ๊ฐ€ GitHub์— ์—…๋กœ๋“œ๋˜์–ด ํฌํŠธํด๋ฆฌ์˜ค๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!