๐ŸŽ“ ์˜จ๋ผ์ธ ์ฝ”์Šค ์ˆ˜๊ฐ• ํ›„๊ธฐ & ๊ฐ•์‚ฌ ํ‰์  ํ”Œ๋žซํผ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP + JavaScript Laravel + React + Inertia.js AI: Claude

๐ŸŽ“ ์˜จ๋ผ์ธ ์ฝ”์Šค ์ˆ˜๊ฐ• ํ›„๊ธฐ & ๊ฐ•์‚ฌ ํ‰์  ํ”Œ๋žซํผ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… course-review-rating-platform
์กฐํšŒ์ˆ˜ 56 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐Ÿ“š ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์˜จ๋ผ์ธ ๊ฐ•์˜๋ฅผ ๋“ค์€ ์ˆ˜๊ฐ•์ƒ๋“ค์ด ์ฝ”์Šค๋ณ„ ํ›„๊ธฐ๋ฅผ ๋‚จ๊ธฐ๊ณ , ๊ฐ•์‚ฌ์—๊ฒŒ ํ‰์ ์„ ๋งค๊ธฐ๋Š” ํ’€์Šคํƒ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜๋กœ ๊ตฌํ˜„ํ•˜๋ฉฐ, ์‹ค์ œ ์˜จ๋ผ์ธ ๊ต์œก ํ”Œ๋žซํผ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์˜ ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

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

  • ๐Ÿ“ ์ฝ”์Šค ๋ชฉ๋ก ํŽ˜์ด์ง€ (์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ, ํ‰๊ท  ๋ณ„์  ํ‘œ์‹œ)
  • โญ ์ฝ”์Šค๋ณ„ ์ƒ์„ธ ํŽ˜์ด์ง€ (๊ฐ•์‚ฌ ์ •๋ณด, ์ปค๋ฆฌํ˜๋Ÿผ, ์ˆ˜๊ฐ•์ƒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก)
  • โœ๏ธ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ (๋ณ„์  5์  ์ฒ™๋„, ์ƒ์„ธ ํ›„๊ธฐ ํ…์ŠคํŠธ, ์ถ”์ฒœ ์—ฌ๋ถ€ ์„ ํƒ)
  • ๐Ÿ” ๋ฆฌ๋ทฐ ํ•„ํ„ฐ๋ง (๋ณ„์ ๋ณ„, ์ตœ์‹ ์ˆœ/์ถ”์ฒœ์ˆœ ์ •๋ ฌ)
  • ๐Ÿ‘จโ€๐Ÿซ ๊ฐ•์‚ฌ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ (๋‹ด๋‹น ์ฝ”์Šค ๋ชฉ๋ก, ์ „์ฒด ํ‰๊ท  ํ‰์ )
  • ๐Ÿ“Š ์ฝ”์Šค ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ (์ด ๋ฆฌ๋ทฐ ์ˆ˜, ํ‰๊ท  ๋ณ„์ , ๋ณ„์  ๋ถ„ํฌ ์ฐจํŠธ)

๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ:

  • ๐Ÿ‘ ๋ฆฌ๋ทฐ ๋„์›€๋จ/๋„์›€์•ˆ๋จ ํˆฌํ‘œ ๊ธฐ๋Šฅ
  • ๐Ÿ† ๋ฒ ์ŠคํŠธ ๋ฆฌ๋ทฐ ๋ฐฐ์ง€ (์ข‹์•„์š” 10๊ฐœ ์ด์ƒ)
  • ๐Ÿ”” ๊ฐ•์‚ฌ ์‘๋‹ต ๊ธฐ๋Šฅ (๋ฆฌ๋ทฐ์— ๋Œ“๊ธ€ ๋‹ฌ๊ธฐ)
  • ๐Ÿ“ธ ๋ฆฌ๋ทฐ ์ด๋ฏธ์ง€ ์ฒจ๋ถ€ (์ˆ˜๊ฐ• ์ธ์ฆ์ƒท)
  • ๐ŸŽฏ ์ˆ˜๊ฐ• ์™„๋ฃŒ ์ธ์ฆ ๋ฐฐ์ง€ (์ฝ”์Šค 100% ์™„๋ฃŒ ์‹œ์—๋งŒ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ฐ€๋Šฅ)

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • Laravel Eloquent ๊ด€๊ณ„ํ˜• ๋ชจ๋ธ ์„ค๊ณ„ (Course โ†” Instructor โ†” Review)
  • Inertia.js ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐํ™” ๋ฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • React ์ƒํƒœ ๊ด€๋ฆฌ (๋ณ„์  ์ž…๋ ฅ, ํผ ๊ฒ€์ฆ, ํ•„ํ„ฐ ์ƒํƒœ)
  • ํ‰๊ท  ๊ณ„์‚ฐ ๋ฐ ์ง‘๊ณ„ ์ฟผ๋ฆฌ ์ตœ์ ํ™”
  • ๋ฐ˜์‘ํ˜• UI ๋””์ž์ธ (Tailwind CSS ํ™œ์šฉ)
  • ํผ ๊ฒ€์ฆ ๋ฐ ์—๋Ÿฌ ํ•ธ๋“ค๋ง (Laravel Validation + React ์ƒํƒœ)

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฐฑ์—”๋“œ: Laravel 13.x (SQLite)
  • ํ”„๋ก ํŠธ์—”๋“œ: React 19 + Inertia.js
  • ์Šคํƒ€์ผ๋ง: Tailwind CSS
  • ์•„์ด์ฝ˜: Heroicons ๋˜๋Š” Lucide React
  • ํผ: React Hook Form (์„ ํƒ)

๐Ÿ“ฆ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ

์‚ฌ์šฉ์ž๋Š” ๋‹ค์–‘ํ•œ ์˜จ๋ผ์ธ ์ฝ”์Šค๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ , ๊ฐ ์ฝ”์Šค์˜ ๋ฆฌ๋ทฐ๋ฅผ ์ฝ์œผ๋ฉฐ, ์ง์ ‘ ์ˆ˜๊ฐ• ํ›„๊ธฐ๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ•์‚ฌ๋Š” ์ž์‹ ์˜ ํ‰์ ๊ณผ ๋ฆฌ๋ทฐ๋ฅผ ํ™•์ธํ•˜๊ณ , ํ•™์ƒ๋“ค์˜ ํ”ผ๋“œ๋ฐฑ์— ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ Udemy, Coursera ๊ฐ™์€ ํ”Œ๋žซํผ์˜ ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ์„ ๊ฐ„์†Œํ™”ํ•œ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ๊ตฌ์กฐ (๋ณ„๋„ API ๋ผ์šฐํŠธ ์—†์Œ)
  • โ€ข npx create-laravel ๋˜๋Š” Laravel Breeze (React + Inertia) starter kit ์‚ฌ์šฉ
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•„์ˆ˜)
  • โ€ข Tailwind CSS๋กœ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ๊ตฌํ˜„
  • โ€ข ๋ณ„์ ์€ 1~5์  ์ •์ˆ˜ ๊ฐ’์œผ๋กœ ์ €์žฅ
  • โ€ข ํ‰๊ท  ๋ณ„์ ์€ ์†Œ์ˆ˜์  ์ฒซ์งธ ์ž๋ฆฌ๊นŒ์ง€ ํ‘œ์‹œ
  • โ€ข ๋ฆฌ๋ทฐ ์ž‘์„ฑ ์‹œ ๋ณ„์ ๊ณผ ํ›„๊ธฐ ๋‚ด์šฉ ํ•„์ˆ˜ ์ž…๋ ฅ
  • โ€ข ์ฝ”๋“œ ์ฃผ์„์œผ๋กœ ์ฃผ์š” ๋กœ์ง ์„ค๋ช… ํฌํ•จ
  • โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ธˆ์ง€ (HTTP ๊ธฐ๋ฐ˜ ๋Œ“๊ธ€๋งŒ ์‚ฌ์šฉ)

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

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

1
Step 1
mkdir course-review-rating-platform && cd course-review-rating-platform

์ด ํด๋”์—์„œ Laravel 13.x + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์œผ๋กœ Laravel Breeze (React + Inertia) starter kit์„ ์„ค์น˜ํ•˜์„ธ์š”:

```bash
composer create-project laravel/laravel .
composer require laravel/breeze --dev
php artisan breeze:install react
npm install
npm run build
```

ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ค์ •:
1. `.env` ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ SQLite๋กœ ์„ค์ • (`DB_CONNECTION=sqlite`, `database/database.sqlite` ํŒŒ์ผ ์ƒ์„ฑ)
2. `php artisan migrate` ์‹คํ–‰ํ•˜์—ฌ ๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ƒ์„ฑ
3. `npm run dev` ์‹คํ–‰ํ•˜์—ฌ Vite ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹œ์ž‘
4. `php artisan serve` ์‹คํ–‰ํ•˜์—ฌ Laravel ์„œ๋ฒ„ ์‹œ์ž‘

๊ธฐ๋ณธ ๊ตฌ์กฐ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š” (Welcome ํŽ˜์ด์ง€ ๋˜๋Š” Breeze ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ ‘์†).

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x + Breeze (React + Inertia) ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๊ธฐ๋ณธ ํŽ˜์ด์ง€๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ •์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์„ค์ •๋˜๊ณ , Vite + Laravel ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

2
Step 2
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•˜์„ธ์š”.

๋‹ค์Œ ์„ธ ๊ฐœ์˜ ๋ชจ๋ธ๊ณผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

1. **Instructor (๊ฐ•์‚ฌ)**
   - `id` (Primary Key)
   - `name` (๊ฐ•์‚ฌ ์ด๋ฆ„, string)
   - `bio` (์†Œ๊ฐœ, text)
   - `profile_image` (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL, string, nullable)
   - `timestamps`

2. **Course (์ฝ”์Šค)**
   - `id` (Primary Key)
   - `instructor_id` (Foreign Key โ†’ instructors.id)
   - `title` (์ฝ”์Šค ์ œ๋ชฉ, string)
   - `description` (์ฝ”์Šค ์„ค๋ช…, text)
   - `thumbnail` (์ธ๋„ค์ผ ์ด๋ฏธ์ง€ URL, string, nullable)
   - `price` (๊ฐ€๊ฒฉ, decimal 8,2, nullable)
   - `timestamps`

3. **Review (๋ฆฌ๋ทฐ)**
   - `id` (Primary Key)
   - `course_id` (Foreign Key โ†’ courses.id)
   - `user_id` (Foreign Key โ†’ users.id, Breeze ๊ธฐ๋ณธ User ๋ชจ๋ธ ํ™œ์šฉ)
   - `rating` (๋ณ„์ , integer, 1~5)
   - `comment` (ํ›„๊ธฐ ๋‚ด์šฉ, text)
   - `is_recommended` (์ถ”์ฒœ ์—ฌ๋ถ€, boolean, default false)
   - `helpful_count` (๋„์›€๋จ ํˆฌํ‘œ ์ˆ˜, integer, default 0)
   - `timestamps`

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ ๋ช…๋ น:
```bash
php artisan make:model Instructor -m
php artisan make:model Course -m
php artisan make:model Review -m
```

๊ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์— ์œ„ ์Šคํ‚ค๋งˆ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , `php artisan migrate`๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”.

๋ชจ๋ธ ํŒŒ์ผ (`app/Models/`)์— Eloquent ๊ด€๊ณ„๋ฅผ ์ •์˜ํ•˜์„ธ์š”:
- `Instructor` hasMany `Course`
- `Course` belongsTo `Instructor`, hasMany `Review`
- `Review` belongsTo `Course`, belongsTo `User`

์‹œ๋”(Seeder)๋ฅผ ๋งŒ๋“ค์–ด ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š” (๊ฐ•์‚ฌ 3๋ช…, ์ฝ”์Šค 10๊ฐœ, ๋ฆฌ๋ทฐ 30๊ฐœ ์ •๋„):
```bash
php artisan make:seeder DatabaseSeeder
```

`DatabaseSeeder.php`์—์„œ ํŒฉํ† ๋ฆฌ ๋˜๋Š” ์ˆ˜๋™์œผ๋กœ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , `php artisan db:seed` ์‹คํ–‰.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— instructors, courses, reviews ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๊ณ , Eloquent ๊ด€๊ณ„๊ฐ€ ์ •์˜๋ฉ๋‹ˆ๋‹ค. ์‹œ๋” ์‹คํ–‰ ํ›„ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๊ฐ€ DB์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

3
Step 3
์ฝ”์Šค ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

Laravel ์ปจํŠธ๋กค๋Ÿฌ์™€ Inertia ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

1. **์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ:**
```bash
php artisan make:controller CourseController
```

`CourseController`์— `index` ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:
- ๋ชจ๋“  ์ฝ”์Šค๋ฅผ ์กฐํšŒํ•˜๊ณ , ๊ฐ ์ฝ”์Šค์˜ ํ‰๊ท  ๋ณ„์ (`reviews`์˜ `rating` ํ‰๊ท )๊ณผ ๋ฆฌ๋ทฐ ๊ฐœ์ˆ˜๋ฅผ ๊ณ„์‚ฐ
- `withAvg('reviews', 'rating')`, `withCount('reviews')` ์‚ฌ์šฉ
- Instructor ์ •๋ณด๋„ ํ•จ๊ป˜ ๋กœ๋“œ (`with('instructor')`)
- Inertia::render()๋กœ 'Courses/Index' ํŽ˜์ด์ง€์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

2. **๋ผ์šฐํŠธ ๋“ฑ๋ก (`routes/web.php`):**
```php
use App\Http\Controllers\CourseController;

Route::get('/courses', [CourseController::class, 'index'])->name('courses.index');
```

3. **React ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ (`resources/js/Pages/Courses/Index.jsx`):**
- Inertia์˜ `usePage()` ํ›…์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ courses ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
- ์ฝ”์Šค ๋ชฉ๋ก์„ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ(Tailwind CSS grid)์œผ๋กœ ํ‘œ์‹œ
- ๊ฐ ์ฝ”์Šค ์นด๋“œ์— ์ธ๋„ค์ผ, ์ œ๋ชฉ, ๊ฐ•์‚ฌ ์ด๋ฆ„, ํ‰๊ท  ๋ณ„์ (โญ ์•„์ด์ฝ˜), ๋ฆฌ๋ทฐ ์ˆ˜ ํ‘œ์‹œ
- ๋ณ„์ ์€ ์ˆซ์ž ๋˜๋Š” ๋ณ„ ์•„์ด์ฝ˜์œผ๋กœ ์‹œ๊ฐํ™” (์˜ˆ: 4.5 โ†’ โญโญโญโญโ˜†)
- ์ฝ”์Šค ์นด๋“œ ํด๋ฆญ ์‹œ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ (`/courses/{id}`)

Tailwind CSS๋กœ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ์ ์šฉ (๋ชจ๋ฐ”์ผ: 1์—ด, ํƒœ๋ธ”๋ฆฟ: 2์—ด, ๋ฐ์Šคํฌํ†ฑ: 3์—ด).

๋ธŒ๋ผ์šฐ์ €์—์„œ `/courses` ์ ‘์†ํ•˜์—ฌ ์ฝ”์Šค ๋ชฉ๋ก์ด ์ •์ƒ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฝ”์Šค ๋ชฉ๋ก ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๊ณ , ๊ฐ ์ฝ”์Šค์˜ ์ธ๋„ค์ผ, ์ œ๋ชฉ, ๊ฐ•์‚ฌ ์ด๋ฆ„, ํ‰๊ท  ๋ณ„์ , ๋ฆฌ๋ทฐ ์ˆ˜๊ฐ€ ์นด๋“œ ํ˜•ํƒœ๋กœ ๊ทธ๋ฆฌ๋“œ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. **์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ (`CourseController::show`):**
- ํŠน์ • ์ฝ”์Šค ID๋กœ ์ฝ”์Šค ์กฐํšŒ
- Instructor, Reviews(+User ์ •๋ณด) eager loading
- ํ‰๊ท  ๋ณ„์ , ๋ฆฌ๋ทฐ ๊ฐœ์ˆ˜, ๋ณ„์  ๋ถ„ํฌ(1~5์ ๋ณ„ ๊ฐœ์ˆ˜) ๊ณ„์‚ฐ
- Inertia::render()๋กœ 'Courses/Show' ํŽ˜์ด์ง€์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

2. **๋ผ์šฐํŠธ ์ถ”๊ฐ€:**
```php
Route::get('/courses/{course}', [CourseController::class, 'show'])->name('courses.show');
```

3. **React ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ (`resources/js/Pages/Courses/Show.jsx`):**
- ์ฝ”์Šค ์ •๋ณด ์„น์…˜: ์ธ๋„ค์ผ(ํฐ ์ด๋ฏธ์ง€), ์ œ๋ชฉ, ๊ฐ•์‚ฌ ์ด๋ฆ„(ํด๋ฆญ ์‹œ ๊ฐ•์‚ฌ ํ”„๋กœํ•„๋กœ ์ด๋™), ์„ค๋ช…, ๊ฐ€๊ฒฉ
- ํ‰๊ท  ๋ณ„์  ๋ฐ ๋ฆฌ๋ทฐ ํ†ต๊ณ„ ์„น์…˜:
  - ํฐ ๋ณ„์  ์ˆซ์ž (์˜ˆ: 4.3 / 5.0)
  - ๋ณ„์  ๋ถ„ํฌ ๋ฐ” ์ฐจํŠธ (1~5์ ๋ณ„ ๋ฆฌ๋ทฐ ๊ฐœ์ˆ˜๋ฅผ ๊ฐ€๋กœ ๋ง‰๋Œ€๋กœ ํ‘œ์‹œ)
  - ์ด ๋ฆฌ๋ทฐ ๊ฐœ์ˆ˜
- ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์„น์…˜:
  - ๊ฐ ๋ฆฌ๋ทฐ: ์ž‘์„ฑ์ž ์ด๋ฆ„, ๋ณ„์ , ์ž‘์„ฑ์ผ, ํ›„๊ธฐ ๋‚ด์šฉ, ์ถ”์ฒœ ์—ฌ๋ถ€ ๋ฐฐ์ง€
  - ์ตœ์‹ ์ˆœ ์ •๋ ฌ (๋‚˜์ค‘์— ํ•„ํ„ฐ ์ถ”๊ฐ€)
- "๋ฆฌ๋ทฐ ์ž‘์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ (ํด๋ฆญ ์‹œ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ์œผ๋กœ ์ด๋™ ๋˜๋Š” ๋ชจ๋‹ฌ ์—ด๊ธฐ)

Tailwind CSS๋กœ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ (2์—ด ๋ ˆ์ด์•„์›ƒ: ์™ผ์ชฝ ์ฝ”์Šค ์ •๋ณด, ์˜ค๋ฅธ์ชฝ ๋ฆฌ๋ทฐ ๋ชฉ๋ก).

๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฝ”์Šค ์นด๋“œ ํด๋ฆญ โ†’ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ด๋™ ํ™•์ธ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๊ณ , ์ฝ”์Šค ์ •๋ณด, ํ‰๊ท  ๋ณ„์ , ๋ณ„์  ๋ถ„ํฌ ์ฐจํŠธ, ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๋ฒ„ํŠผ์ด ๋ณด์ž…๋‹ˆ๋‹ค.

5
Step 5
๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. **๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ ์ปดํฌ๋„ŒํŠธ (`resources/js/Pages/Reviews/Create.jsx` ๋˜๋Š” ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ):**
- ๋ณ„์  ์„ ํƒ UI (1~5 ๋ณ„ ์•„์ด์ฝ˜ ํด๋ฆญ ๋˜๋Š” ๋ผ๋””์˜ค ๋ฒ„ํŠผ)
- ํ›„๊ธฐ ๋‚ด์šฉ ํ…์ŠคํŠธ ์˜์—ญ (ํ•„์ˆ˜, ์ตœ์†Œ 10์ž)
- ์ถ”์ฒœ ์—ฌ๋ถ€ ์ฒดํฌ๋ฐ•์Šค ("์ด ์ฝ”์Šค๋ฅผ ์ถ”์ฒœํ•˜์‹œ๋‚˜์š”?")
- ์ œ์ถœ ๋ฒ„ํŠผ

2. **์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ (`ReviewController`):**
```bash
php artisan make:controller ReviewController
```

- `create` ๋ฉ”์„œ๋“œ: ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง (course_id๋ฅผ URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ)
- `store` ๋ฉ”์„œ๋“œ:
  - ํผ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ (rating: required|integer|between:1,5, comment: required|min:10)
  - ํ˜„์žฌ ๋กœ๊ทธ์ธ ์œ ์ € ID (Breeze ์ธ์ฆ ์‚ฌ์šฉ, `auth()->id()`)
  - Review ๋ชจ๋ธ์— ๋ฐ์ดํ„ฐ ์ €์žฅ
  - ์ €์žฅ ํ›„ ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (`redirect()->route('courses.show', $course)`)

3. **๋ผ์šฐํŠธ ์ถ”๊ฐ€:**
```php
Route::middleware('auth')->group(function () {
    Route::get('/courses/{course}/reviews/create', [ReviewController::class, 'create'])->name('reviews.create');
    Route::post('/courses/{course}/reviews', [ReviewController::class, 'store'])->name('reviews.store');
});
```

4. **์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ "๋ฆฌ๋ทฐ ์ž‘์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ ์—ฐ๊ฒฐ:**
- `<Link href={route('reviews.create', course.id)}>๋ฆฌ๋ทฐ ์ž‘์„ฑ</Link>` (Inertia Link ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ)
- ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (๋ฏธ๋“ค์›จ์–ด ์ฒ˜๋ฆฌ)

5. **ํผ ์ œ์ถœ ์ฒ˜๋ฆฌ (React):**
- Inertia์˜ `useForm()` ํ›… ์‚ฌ์šฉ
- ํผ ๊ฒ€์ฆ ์—๋Ÿฌ ํ‘œ์‹œ (Laravel ๊ฒ€์ฆ ์—๋Ÿฌ๋ฅผ Inertia๊ฐ€ ์ž๋™ ์ „๋‹ฌ)

๋ฆฌ๋ทฐ ์ž‘์„ฑ ํ›„ ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ์ƒˆ ๋ฆฌ๋ทฐ๊ฐ€ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ "๋ฆฌ๋ทฐ ์ž‘์„ฑ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ๋ณ„์ , ํ›„๊ธฐ ๋‚ด์šฉ, ์ถ”์ฒœ ์—ฌ๋ถ€๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ œ์ถœํ•˜๋ฉด ๋ฆฌ๋ทฐ๊ฐ€ DB์— ์ €์žฅ๋˜๊ณ , ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€ ์ƒˆ ๋ฆฌ๋ทฐ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

6
Step 6
๋ฆฌ๋ทฐ ํ•„ํ„ฐ๋ง ๋ฐ ์ •๋ ฌ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

1. **์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์— ํ•„ํ„ฐ UI ์ถ”๊ฐ€ (`Courses/Show.jsx`):**
- ๋ณ„์  ํ•„ํ„ฐ ๋ฒ„ํŠผ (์ „์ฒด, 5์ , 4์ , 3์ , 2์ , 1์ )
- ์ •๋ ฌ ๋“œ๋กญ๋‹ค์šด (์ตœ์‹ ์ˆœ, ์ถ”์ฒœ์ˆœ, ๋ณ„์  ๋†’์€ ์ˆœ, ๋ณ„์  ๋‚ฎ์€ ์ˆœ)

2. **ํ•„ํ„ฐ/์ •๋ ฌ ์ƒํƒœ ๊ด€๋ฆฌ (React useState):**
- `selectedRating` (null ๋˜๋Š” 1~5)
- `sortBy` ('latest', 'recommended', 'rating_high', 'rating_low')

3. **ํ•„ํ„ฐ ๋ณ€๊ฒฝ ์‹œ Laravel ์ปจํŠธ๋กค๋Ÿฌ์— ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ „๋‹ฌ:**
- Inertia Link ๋˜๋Š” useForm์œผ๋กœ GET ์š”์ฒญ (`/courses/{id}?rating=5&sort=latest`)
- ์ปจํŠธ๋กค๋Ÿฌ์—์„œ `request()->query('rating')`, `request()->query('sort')` ์ฝ๊ธฐ
- ๋ฆฌ๋ทฐ ์กฐํšŒ ์‹œ ์กฐ๊ฑด ์ ์šฉ:
  - rating ํ•„ํ„ฐ: `$query->where('rating', $rating)`
  - sort: `$query->orderBy('created_at', 'desc')` (์ตœ์‹ ์ˆœ), `$query->orderBy('rating', 'desc')` (๋ณ„์  ๋†’์€ ์ˆœ) ๋“ฑ

4. **ํ•„ํ„ฐ ์ ์šฉ ํ›„ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ:**
- Inertia๊ฐ€ ์ž๋™์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋ฉฐ ํ•„ํ„ฐ๋œ ๋ฆฌ๋ทฐ ํ‘œ์‹œ

5. **UI ๊ฐœ์„ :**
- ํ˜„์žฌ ์„ ํƒ๋œ ํ•„ํ„ฐ ๋ฒ„ํŠผ ๊ฐ•์กฐ (Tailwind CSS ์กฐ๊ฑด๋ถ€ ํด๋ž˜์Šค)
- ํ•„ํ„ฐ ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ์ถ”๊ฐ€ ("์ „์ฒด ๋ณด๊ธฐ")

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณ„์  ํ•„ํ„ฐ ํด๋ฆญ โ†’ ํ•ด๋‹น ๋ณ„์ ์˜ ๋ฆฌ๋ทฐ๋งŒ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ๋ณ„์  ํ•„ํ„ฐ ๋ฒ„ํŠผ๊ณผ ์ •๋ ฌ ๋“œ๋กญ๋‹ค์šด์ด ์ž‘๋™ํ•˜๋ฉฐ, ์„ ํƒํ•œ ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ด ๋™์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค. URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.

7
Step 7
๊ฐ•์‚ฌ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. **์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ:**
```bash
php artisan make:controller InstructorController
```

`InstructorController::show` ๋ฉ”์„œ๋“œ:
- ํŠน์ • ๊ฐ•์‚ฌ ID๋กœ ๊ฐ•์‚ฌ ์กฐํšŒ
- ๊ฐ•์‚ฌ๊ฐ€ ๋‹ด๋‹นํ•˜๋Š” ์ฝ”์Šค ๋ชฉ๋ก ์กฐํšŒ (`with('courses')`)
- ๊ฐ•์‚ฌ์˜ ์ „์ฒด ํ‰๊ท  ํ‰์  ๊ณ„์‚ฐ (๋ชจ๋“  ์ฝ”์Šค์˜ ๋ฆฌ๋ทฐ ํ‰๊ท )
- Inertia::render()๋กœ 'Instructors/Show' ํŽ˜์ด์ง€์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

2. **๋ผ์šฐํŠธ ์ถ”๊ฐ€:**
```php
Route::get('/instructors/{instructor}', [InstructorController::class, 'show'])->name('instructors.show');
```

3. **React ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ (`resources/js/Pages/Instructors/Show.jsx`):**
- ๊ฐ•์‚ฌ ํ”„๋กœํ•„ ์„น์…˜:
  - ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ (์›ํ˜• ๋˜๋Š” ์ •์‚ฌ๊ฐํ˜•)
  - ๊ฐ•์‚ฌ ์ด๋ฆ„, ์†Œ๊ฐœ(bio)
  - ์ „์ฒด ํ‰๊ท  ํ‰์  (โญ ์•„์ด์ฝ˜)
- ๋‹ด๋‹น ์ฝ”์Šค ๋ชฉ๋ก ์„น์…˜:
  - ์ฝ”์Šค ์นด๋“œ ๊ทธ๋ฆฌ๋“œ (์ฝ”์Šค ๋ชฉ๋ก ํŽ˜์ด์ง€์™€ ๋™์ผํ•œ ์Šคํƒ€์ผ)
  - ๊ฐ ์ฝ”์Šค ์นด๋“œ ํด๋ฆญ ์‹œ ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™

4. **์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ๊ฐ•์‚ฌ ์ด๋ฆ„ ํด๋ฆญ ์‹œ ๊ฐ•์‚ฌ ํ”„๋กœํ•„๋กœ ์ด๋™:**
- `<Link href={route('instructors.show', course.instructor.id)}>{course.instructor.name}</Link>`

Tailwind CSS๋กœ ํ”„๋กœํ•„ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ (์ƒ๋‹จ: ํ”„๋กœํ•„ ์ •๋ณด, ํ•˜๋‹จ: ์ฝ”์Šค ๊ทธ๋ฆฌ๋“œ).

๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฝ”์Šค ์ƒ์„ธ โ†’ ๊ฐ•์‚ฌ ์ด๋ฆ„ ํด๋ฆญ โ†’ ๊ฐ•์‚ฌ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ํ™•์ธ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๊ฐ•์‚ฌ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๊ณ , ๊ฐ•์‚ฌ ์ •๋ณด, ์ „์ฒด ํ‰๊ท  ํ‰์ , ๋‹ด๋‹น ์ฝ”์Šค ๋ชฉ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ฝ”์Šค ์นด๋“œ ํด๋ฆญ ์‹œ ์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

8
Step 8
๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ: ๋ฆฌ๋ทฐ "๋„์›€๋จ" ํˆฌํ‘œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

1. **๋ฆฌ๋ทฐ ํ…Œ์ด๋ธ”์— `helpful_count` ์ปฌ๋Ÿผ์ด ์ด๋ฏธ ์žˆ๋Š”์ง€ ํ™•์ธ (step 2์—์„œ ์ถ”๊ฐ€).**

2. **์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ (`ReviewController::helpful`):**
- ํŠน์ • ๋ฆฌ๋ทฐ์˜ `helpful_count`๋ฅผ 1 ์ฆ๊ฐ€
- `$review->increment('helpful_count');`
- JSON ์‘๋‹ต์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ ์นด์šดํŠธ ๋ฐ˜ํ™˜ (๋˜๋Š” Inertia ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ)

3. **๋ผ์šฐํŠธ ์ถ”๊ฐ€:**
```php
Route::post('/reviews/{review}/helpful', [ReviewController::class, 'helpful'])->name('reviews.helpful');
```

4. **์ฝ”์Šค ์ƒ์„ธ ํŽ˜์ด์ง€ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์— "๋„์›€๋จ" ๋ฒ„ํŠผ ์ถ”๊ฐ€ (`Courses/Show.jsx`):**
- ๊ฐ ๋ฆฌ๋ทฐ ์นด๋“œ์— ๐Ÿ‘ ๋ฒ„ํŠผ๊ณผ ์นด์šดํŠธ ํ‘œ์‹œ
- ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ Inertia `post` ์š”์ฒญ์œผ๋กœ `/reviews/{review.id}/helpful` ํ˜ธ์ถœ
- ์„ฑ๊ณต ํ›„ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจํ•˜์—ฌ ์นด์šดํŠธ ์—…๋ฐ์ดํŠธ (๋˜๋Š” React ์ƒํƒœ๋กœ ์ฆ‰์‹œ ๋ฐ˜์˜)

5. **๋ฒ ์ŠคํŠธ ๋ฆฌ๋ทฐ ๋ฐฐ์ง€ ์ถ”๊ฐ€:**
- `helpful_count >= 10`์ธ ๋ฆฌ๋ทฐ์— "๐Ÿ† ๋ฒ ์ŠคํŠธ ๋ฆฌ๋ทฐ" ๋ฐฐ์ง€ ํ‘œ์‹œ (Tailwind CSS ๋ฐฐ์ง€ ์Šคํƒ€์ผ)

6. **์ •๋ ฌ ์˜ต์…˜์— "์ถ”์ฒœ์ˆœ" ์ถ”๊ฐ€:**
- ์ปจํŠธ๋กค๋Ÿฌ์—์„œ `orderBy('helpful_count', 'desc')` ์ ์šฉ

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฆฌ๋ทฐ "๋„์›€๋จ" ๋ฒ„ํŠผ ํด๋ฆญ โ†’ ์นด์šดํŠธ ์ฆ๊ฐ€ ํ™•์ธ, 10๊ฐœ ์ด์ƒ ์‹œ ๋ฐฐ์ง€ ํ‘œ์‹œ ํ™•์ธ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฆฌ๋ทฐ ๋ชฉ๋ก์— "๋„์›€๋จ" ๋ฒ„ํŠผ์ด ์ถ”๊ฐ€๋˜๊ณ , ํด๋ฆญ ์‹œ ์นด์šดํŠธ๊ฐ€ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์นด์šดํŠธ๊ฐ€ 10 ์ด์ƒ์ธ ๋ฆฌ๋ทฐ์— ๋ฒ ์ŠคํŠธ ๋ฆฌ๋ทฐ ๋ฐฐ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ •๋ ฌ ์˜ต์…˜์—์„œ ์ถ”์ฒœ์ˆœ ์„ ํƒ ์‹œ ๋„์›€๋จ ์ˆ˜๊ฐ€ ๋งŽ์€ ๋ฆฌ๋ทฐ๊ฐ€ ์ƒ๋‹จ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

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

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

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

๋Œ“๊ธ€ (0)

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