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

๐ŸŽจ ํ”„๋ฆฌ๋žœ์„œ ๋””์ž์ด๋„ˆ ํฌํŠธํด๋ฆฌ์˜ค ์ƒต - ์ž‘ํ’ˆ ๊ฐค๋Ÿฌ๋ฆฌ & ๊ฒฌ์  ๋ฌธ์˜ ํ†ตํ•ฉ ํ”Œ๋žซํผ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… designer-portfolio-inquiry-shop
์กฐํšŒ์ˆ˜ 121 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

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

ํ”„๋ฆฌ๋žœ์„œ ๋””์ž์ด๋„ˆ๋ฅผ ์œ„ํ•œ ํฌํŠธํด๋ฆฌ์˜ค ๊ฒธ ๊ฒฌ์  ๋ฌธ์˜ ํ†ตํ•ฉ ํ”Œ๋žซํผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ์ž‘ํ’ˆ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์„ ๋„˜์–ด, ์ž ์žฌ ๊ณ ๊ฐ์ด ์ง์ ‘ ์›ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ฒฌ์ ์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ์•„ํ‚คํ…์ฒ˜๋กœ ๊ตฌํ˜„ํ•˜๋ฉฐ, Inertia.js๊ฐ€ Laravel ๋ผ์šฐํŠธ์™€ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„ API ์„ค๊ณ„ ์—†์ด Laravel ์ปจํŠธ๋กค๋Ÿฌ์—์„œ Inertia::render()๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

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

  1. ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ: ์ž‘ํ’ˆ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ๋ง (๋กœ๊ณ /์›น๋””์ž์ธ/ํŒจํ‚ค์ง• ๋“ฑ), ๊ทธ๋ฆฌ๋“œ/๋ฆฌ์ŠคํŠธ ๋ทฐ ์ „ํ™˜
  2. ์ž‘ํ’ˆ ์ƒ์„ธ ํŽ˜์ด์ง€: ๋ผ์ดํŠธ๋ฐ•์Šค ๋ชจ๋‹ฌ, ํ”„๋กœ์ ํŠธ ์„ค๋ช…, ์‚ฌ์šฉ ํˆด ํƒœ๊ทธ, ์ด์ „/๋‹ค์Œ ์ž‘ํ’ˆ ๋„ค๋น„๊ฒŒ์ด์…˜
  3. ์„œ๋น„์Šค ๋ฉ”๋‰ด: ์ œ๊ณต ์„œ๋น„์Šค๋ณ„ ๊ฐ€๊ฒฉํ‘œ (๋กœ๊ณ  ๋””์ž์ธ, ๋ช…ํ•จ ๋””์ž์ธ, ์›น ๋ฐฐ๋„ˆ ๋“ฑ), ํŒจํ‚ค์ง€ ์˜ต์…˜
  4. ๊ฒฌ์  ๋ฌธ์˜ ํผ: ์„œ๋น„์Šค ์„ ํƒ, ํ”„๋กœ์ ํŠธ ์ƒ์„ธ ์ž…๋ ฅ, ์˜ˆ์‚ฐ ๋ฒ”์œ„, ํฌ๋ง ์ผ์ •, ์ฐธ๊ณ  ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
  5. ๋ฌธ์˜ ๋‚ด์—ญ ๊ด€๋ฆฌ: ๋””์ž์ด๋„ˆ๊ฐ€ ๋ฐ›์€ ๊ฒฌ์  ์š”์ฒญ ๋ชฉ๋ก ํ™•์ธ, ์ƒํƒœ ๋ณ€๊ฒฝ (๊ฒ€ํ† ์ค‘/๋‹ต๋ณ€์™„๋ฃŒ/์ง„ํ–‰์ค‘/์™„๋ฃŒ)
  6. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ: Tailwind CSS๋กœ ๋ชจ๋ฐ”์ผ ์นœํ™”์  UI

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

  • ์ž‘ํ’ˆ ์ข‹์•„์š” ๊ธฐ๋Šฅ (๋กœ๊ทธ์ธ ์—†์ด localStorage ํ™œ์šฉ)
  • ์ž‘ํ’ˆ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ (์ œ๋ชฉ, ์„ค๋ช…, ํƒœ๊ทธ)
  • ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€
  • ๊ฒฌ์  ์š”์ฒญ ์‹œ ์ž๋™ ์ด๋ฉ”์ผ ์•Œ๋ฆผ (Laravel Mail)
  • ์ž‘ํ’ˆ๋ณ„ ์กฐํšŒ์ˆ˜ ์ง‘๊ณ„

๊ธฐ์ˆ  ์Šคํƒ

  • Backend: Laravel 13.x (Eloquent ORM, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ปจํŠธ๋กค๋Ÿฌ, Validation)
  • Frontend: React 19 (Hooks: useState, useEffect, useForm)
  • Bridge: Inertia.js (SSR ์Šคํƒ€์ผ SPA)
  • Styling: Tailwind CSS 4.x
  • Database: SQLite (๊ฐœ๋ฐœ ํŽธ์˜์„ฑ)
  • File Upload: Laravel Storage (public disk)

ํ•™์Šต ํฌ์ธํŠธ

  • Inertia.js์˜ ์žฅ์ : API ์—†์ด Laravel โ†” React ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ดํ•ด
  • ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ (์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐ ๊ฒ€์ฆ)
  • ํผ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (Laravel Validation + React)
  • ๋™์  ๋ผ์šฐํŒ… ๋ฐ ํŽ˜์ด์ง€๋„ค์ด์…˜
  • ์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ (์นด๋“œ, ๋ชจ๋‹ฌ, ํผ ํ•„๋“œ)

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ๊ตฌ์กฐ ํ•„์ˆ˜
  • โ€ข Laravel Breeze (React + Inertia) starter kit ์‚ฌ์šฉ ๊ถŒ์žฅ
  • โ€ข ๋ณ„๋„ REST API ๋ผ์šฐํŠธ ์ƒ์„ฑ ๊ธˆ์ง€ (Inertia.js ์‚ฌ์šฉ)
  • โ€ข Tailwind CSS 4.x๋กœ ์Šคํƒ€์ผ๋ง
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ ์ž‘์„ฑ)
  • โ€ข ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” Laravel Storage์˜ public disk ํ™œ์šฉ
  • โ€ข ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ํ•„์ˆ˜ (๋ชจ๋ฐ”์ผ ์šฐ์„ )
  • โ€ข ํผ ์ œ์ถœ ์‹œ Laravel Validation์œผ๋กœ ๋ฐฑ์—”๋“œ ๊ฒ€์ฆ
  • โ€ข Inertia Form Helper ํ™œ์šฉํ•˜์—ฌ React์—์„œ ํผ ์ƒํƒœ ๊ด€๋ฆฌ
  • โ€ข ์ฝ”๋“œ ์ฃผ์„ ๋ฐ ๋ช…ํ™•ํ•œ ๋ณ€์ˆ˜๋ช… ์‚ฌ์šฉ

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

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

1
Step 1
mkdir designer-portfolio-inquiry-shop && cd designer-portfolio-inquiry-shop

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

**ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”:**

1. 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 dev
   ```

2. `.env` ํŒŒ์ผ ์„ค์ •:
   - `DB_CONNECTION=sqlite` ๋ณ€๊ฒฝ
   - `DB_DATABASE` ์ฃผ์„ ์ฒ˜๋ฆฌ ๋˜๋Š” ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ `database/database.sqlite` ์ง€์ •

3. SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ ์ƒ์„ฑ:
   ```bash
   touch database/database.sqlite
   ```

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

5. Storage ์‹ฌ๋ณผ๋ฆญ ๋งํฌ ์ƒ์„ฑ (์ด๋ฏธ์ง€ ์—…๋กœ๋“œ์šฉ):
   ```bash
   php artisan storage:link
   ```

6. Tailwind CSS ์„ค์ • ํ™•์ธ (`tailwind.config.js`์— `./resources/**/*.jsx` ๊ฒฝ๋กœ ํฌํ•จ ์—ฌ๋ถ€)

**ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ํ™•์ธ:**

- `app/Http/Controllers/` : Laravel ์ปจํŠธ๋กค๋Ÿฌ
- `app/Models/` : Eloquent ๋ชจ๋ธ
- `database/migrations/` : DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
- `resources/js/Pages/` : React ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ (Inertia.js)
- `resources/js/Components/` : ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ React ์ปดํฌ๋„ŒํŠธ
- `routes/web.php` : ์›น ๋ผ์šฐํŠธ ์ •์˜

**๋‹ค์Œ ๋‹จ๊ณ„ ์ค€๋น„:**

- ํฌํŠธํด๋ฆฌ์˜ค ์ž‘ํ’ˆ(Portfolio)๊ณผ ๊ฒฌ์  ๋ฌธ์˜(Inquiry) ๋ชจ๋ธ ์„ค๊ณ„ ์ค€๋น„
- Breeze์˜ ๊ธฐ๋ณธ ์ธ์ฆ ๊ธฐ๋Šฅ์€ ์œ ์ง€ํ•˜๋˜, ๋””์ž์ด๋„ˆ ์ „์šฉ ๊ด€๋ฆฌ ํŽ˜์ด์ง€๋กœ ํ™œ์šฉ ์˜ˆ์ •

๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ํ›„ `http://localhost:8000` ์ ‘์†ํ•˜์—ฌ ๊ธฐ๋ณธ ํ™”๋ฉด ํ™•์ธ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel Breeze (React + Inertia) ํ”„๋กœ์ ํŠธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜๊ณ , ๊ฐœ๋ฐœ ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ Welcome ํŽ˜์ด์ง€ ๋˜๋Š” Breeze ๊ธฐ๋ณธ ํ™”๋ฉด์ด ํ‘œ์‹œ๋˜๋ฉฐ, Tailwind CSS๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๊ณ  ๊ธฐ๋ณธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜(users, password_resets ๋“ฑ)์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

2
Step 2
**๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ**

ํฌํŠธํด๋ฆฌ์˜ค ์ž‘ํ’ˆ(Portfolio)๊ณผ ๊ฒฌ์  ๋ฌธ์˜(Inquiry) ํ…Œ์ด๋ธ”์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.

1. **Portfolio ๋ชจ๋ธ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:**
   ```bash
   php artisan make:model Portfolio -m
   ```

   `database/migrations/xxxx_create_portfolios_table.php` ํŒŒ์ผ ํŽธ์ง‘:
   ```php
   public function up()
   {
       Schema::create('portfolios', function (Blueprint $table) {
           $table->id();
           $table->string('title'); // ์ž‘ํ’ˆ ์ œ๋ชฉ
           $table->text('description')->nullable(); // ์ž‘ํ’ˆ ์„ค๋ช…
           $table->string('category'); // ์นดํ…Œ๊ณ ๋ฆฌ (๋กœ๊ณ , ์›น๋””์ž์ธ, ํŒจํ‚ค์ง• ๋“ฑ)
           $table->string('image_path'); // ์ด๋ฏธ์ง€ ์ €์žฅ ๊ฒฝ๋กœ
           $table->json('tools')->nullable(); // ์‚ฌ์šฉ ํˆด (JSON ๋ฐฐ์—ด: ["Photoshop", "Illustrator"])
           $table->integer('views')->default(0); // ์กฐํšŒ์ˆ˜
           $table->timestamps();
       });
   }
   ```

2. **Inquiry ๋ชจ๋ธ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:**
   ```bash
   php artisan make:model Inquiry -m
   ```

   `database/migrations/xxxx_create_inquiries_table.php` ํŒŒ์ผ ํŽธ์ง‘:
   ```php
   public function up()
   {
       Schema::create('inquiries', function (Blueprint $table) {
           $table->id();
           $table->string('service'); // ์š”์ฒญ ์„œ๋น„์Šค (๋กœ๊ณ  ๋””์ž์ธ, ๋ช…ํ•จ ๋””์ž์ธ ๋“ฑ)
           $table->string('client_name'); // ์˜๋ขฐ์ธ ์ด๋ฆ„
           $table->string('client_email'); // ์˜๋ขฐ์ธ ์ด๋ฉ”์ผ
           $table->text('project_details'); // ํ”„๋กœ์ ํŠธ ์ƒ์„ธ ์„ค๋ช…
           $table->string('budget_range')->nullable(); // ์˜ˆ์‚ฐ ๋ฒ”์œ„
           $table->date('desired_date')->nullable(); // ํฌ๋ง ์™„๋ฃŒ ์ผ์ •
           $table->string('reference_image')->nullable(); // ์ฐธ๊ณ  ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
           $table->enum('status', ['pending', 'reviewed', 'in_progress', 'completed'])->default('pending'); // ๋ฌธ์˜ ์ƒํƒœ
           $table->timestamps();
       });
   }
   ```

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

4. **๋ชจ๋ธ์— Fillable ์†์„ฑ ์ถ”๊ฐ€:**

   `app/Models/Portfolio.php`:
   ```php
   protected $fillable = ['title', 'description', 'category', 'image_path', 'tools', 'views'];
   protected $casts = ['tools' => 'array']; // JSON์„ ๋ฐฐ์—ด๋กœ ์ž๋™ ๋ณ€ํ™˜
   ```

   `app/Models/Inquiry.php`:
   ```php
   protected $fillable = ['service', 'client_name', 'client_email', 'project_details', 'budget_range', 'desired_date', 'reference_image', 'status'];
   ```

**๋‹ค์Œ ๋‹จ๊ณ„ ์ค€๋น„:**

- ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€์™€ ์ปจํŠธ๋กค๋Ÿฌ ๊ตฌํ˜„
- ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์‹œ๋”ฉ (์„ ํƒ์‚ฌํ•ญ)

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: portfolios์™€ inquiries ํ…Œ์ด๋ธ”์ด SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. Portfolio์™€ Inquiry ๋ชจ๋ธ์— fillable ์†์„ฑ์ด ์ •์˜๋˜์–ด ๋Œ€๋Ÿ‰ ํ• ๋‹น์ด ๊ฐ€๋Šฅํ•ด์ง€๋ฉฐ, tools ํ•„๋“œ๋Š” JSON ๋ฐฐ์—ด๋กœ ์ž๋™ ์บ์ŠคํŒ…๋ฉ๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์˜ค๋ฅ˜ ์—†์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

3
Step 3
**ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€ ๊ตฌํ˜„ (Laravel ์ปจํŠธ๋กค๋Ÿฌ + React ์ปดํฌ๋„ŒํŠธ)**

1. **PortfolioController ์ƒ์„ฑ:**
   ```bash
   php artisan make:controller PortfolioController
   ```

   `app/Http/Controllers/PortfolioController.php`:
   ```php
   <?php

   namespace App\Http\Controllers;

   use App\Models\Portfolio;
   use Illuminate\Http\Request;
   use Inertia\Inertia;

   class PortfolioController extends Controller
   {
       public function index(Request $request)
       {
           $category = $request->query('category');
           
           $portfolios = Portfolio::query()
               ->when($category, function ($query, $category) {
                   return $query->where('category', $category);
               })
               ->latest()
               ->paginate(9);

           $categories = Portfolio::distinct()->pluck('category');

           return Inertia::render('Portfolio/Index', [
               'portfolios' => $portfolios,
               'categories' => $categories,
               'selectedCategory' => $category,
           ]);
       }

       public function show($id)
       {
           $portfolio = Portfolio::findOrFail($id);
           $portfolio->increment('views'); // ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€

           $prev = Portfolio::where('id', '<', $id)->orderBy('id', 'desc')->first();
           $next = Portfolio::where('id', '>', $id)->orderBy('id', 'asc')->first();

           return Inertia::render('Portfolio/Show', [
               'portfolio' => $portfolio,
               'prev' => $prev,
               'next' => $next,
           ]);
       }
   }
   ```

2. **๋ผ์šฐํŠธ ์ถ”๊ฐ€ (`routes/web.php`):**
   ```php
   use App\Http\Controllers\PortfolioController;

   Route::get('/', [PortfolioController::class, 'index'])->name('portfolio.index');
   Route::get('/portfolio/{id}', [PortfolioController::class, 'show'])->name('portfolio.show');
   ```

3. **React ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:**

   `resources/js/Pages/Portfolio/Index.jsx`:
   ```jsx
   import { Head, Link } from '@inertiajs/react';

   export default function Index({ portfolios, categories, selectedCategory }) {
       return (
           <>
               <Head title="ํฌํŠธํด๋ฆฌ์˜ค" />
               <div className="min-h-screen bg-gray-50">
                   <header className="bg-white shadow">
                       <div className="max-w-7xl mx-auto py-6 px-4">
                           <h1 className="text-3xl font-bold text-gray-900">๋””์ž์ด๋„ˆ ํฌํŠธํด๋ฆฌ์˜ค</h1>
                       </div>
                   </header>

                   <main className="max-w-7xl mx-auto py-8 px-4">
                       {/* ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ */}
                       <div className="flex gap-2 mb-6 flex-wrap">
                           <Link
                               href="/"
                               className={`px-4 py-2 rounded-full ${
                                   !selectedCategory
                                       ? 'bg-blue-600 text-white'
                                       : 'bg-white text-gray-700 hover:bg-gray-100'
                               }`}
                           >
                               ์ „์ฒด
                           </Link>
                           {categories.map((cat) => (
                               <Link
                                   key={cat}
                                   href={`/?category=${cat}`}
                                   className={`px-4 py-2 rounded-full ${
                                       selectedCategory === cat
                                           ? 'bg-blue-600 text-white'
                                           : 'bg-white text-gray-700 hover:bg-gray-100'
                                   }`}
                               >
                                   {cat}
                               </Link>
                           ))}
                       </div>

                       {/* ์ž‘ํ’ˆ ๊ทธ๋ฆฌ๋“œ */}
                       <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
                           {portfolios.data.map((item) => (
                               <Link
                                   key={item.id}
                                   href={`/portfolio/${item.id}`}
                                   className="bg-white rounded-lg shadow overflow-hidden hover:shadow-lg transition"
                               >
                                   <img
                                       src={`/storage/${item.image_path}`}
                                       alt={item.title}
                                       className="w-full h-64 object-cover"
                                   />
                                   <div className="p-4">
                                       <h3 className="font-semibold text-lg mb-2">{item.title}</h3>
                                       <p className="text-sm text-gray-600 mb-2">{item.category}</p>
                                       <p className="text-xs text-gray-500">์กฐํšŒ์ˆ˜: {item.views}</p>
                                   </div>
                               </Link>
                           ))}
                       </div>

                       {/* ํŽ˜์ด์ง€๋„ค์ด์…˜ */}
                       <div className="mt-8 flex justify-center gap-2">
                           {portfolios.links.map((link, index) => (
                               <Link
                                   key={index}
                                   href={link.url || '#'}
                                   className={`px-4 py-2 rounded ${
                                       link.active
                                           ? 'bg-blue-600 text-white'
                                           : 'bg-white text-gray-700 hover:bg-gray-100'
                                   }`}
                                   dangerouslySetInnerHTML={{ __html: link.label }}
                               />
                           ))}
                       </div>
                   </main>
               </div>
           </>
       );
   }
   ```

**๋‹ค์Œ ๋‹จ๊ณ„ ์ค€๋น„:**

- ์ž‘ํ’ˆ ์ƒ์„ธ ํŽ˜์ด์ง€ ๊ตฌํ˜„
- ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฐค๋Ÿฌ๋ฆฌ ํ…Œ์ŠคํŠธ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๋ฅผ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์ž‘ํ’ˆ๋งŒ ํ•„ํ„ฐ๋ง๋˜์–ด ๋ณด์ž…๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ์ž‘๋™ํ•˜๋ฉฐ, ์ž‘ํ’ˆ ์นด๋“œ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค (์•„์ง ๊ตฌํ˜„ ์ „์ด๋ฏ€๋กœ 404 ๋˜๋Š” ๋นˆ ํŽ˜์ด์ง€). Tailwind CSS ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์–ด ๊น”๋”ํ•œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
**์ž‘ํ’ˆ ์ƒ์„ธ ํŽ˜์ด์ง€ ๊ตฌํ˜„ (๋ผ์ดํŠธ๋ฐ•์Šค ๋ชจ๋‹ฌ, ์ด์ „/๋‹ค์Œ ๋„ค๋น„๊ฒŒ์ด์…˜)**

`resources/js/Pages/Portfolio/Show.jsx`:
```jsx
import { Head, Link } from '@inertiajs/react';
import { useState } from 'react';

export default function Show({ portfolio, prev, next }) {
    const [isModalOpen, setIsModalOpen] = useState(false);

    return (
        <>
            <Head title={portfolio.title} />
            <div className="min-h-screen bg-gray-50">
                <header className="bg-white shadow">
                    <div className="max-w-7xl mx-auto py-6 px-4">
                        <Link href="/" className="text-blue-600 hover:underline">
                            โ† ๊ฐค๋Ÿฌ๋ฆฌ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
                        </Link>
                    </div>
                </header>

                <main className="max-w-4xl mx-auto py-8 px-4">
                    {/* ์ž‘ํ’ˆ ์ด๋ฏธ์ง€ */}
                    <div className="bg-white rounded-lg shadow overflow-hidden mb-6">
                        <img
                            src={`/storage/${portfolio.image_path}`}
                            alt={portfolio.title}
                            className="w-full cursor-pointer"
                            onClick={() => setIsModalOpen(true)}
                        />
                    </div>

                    {/* ์ž‘ํ’ˆ ์ •๋ณด */}
                    <div className="bg-white rounded-lg shadow p-6 mb-6">
                        <h1 className="text-3xl font-bold mb-2">{portfolio.title}</h1>
                        <p className="text-gray-600 mb-4">{portfolio.category}</p>
                        <p className="text-gray-700 mb-4">{portfolio.description}</p>
                        
                        {portfolio.tools && portfolio.tools.length > 0 && (
                            <div className="mb-4">
                                <h3 className="font-semibold mb-2">์‚ฌ์šฉ ํˆด:</h3>
                                <div className="flex gap-2 flex-wrap">
                                    {portfolio.tools.map((tool, index) => (
                                        <span
                                            key={index}
                                            className="px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm"
                                        >
                                            {tool}
                                        </span>
                                    ))}
                                </div>
                            </div>
                        )}

                        <p className="text-sm text-gray-500">์กฐํšŒ์ˆ˜: {portfolio.views}</p>
                    </div>

                    {/* ์ด์ „/๋‹ค์Œ ๋„ค๋น„๊ฒŒ์ด์…˜ */}
                    <div className="flex justify-between">
                        {prev ? (
                            <Link
                                href={`/portfolio/${prev.id}`}
                                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
                            >
                                โ† ์ด์ „ ์ž‘ํ’ˆ
                            </Link>
                        ) : (
                            <div></div>
                        )}
                        {next ? (
                            <Link
                                href={`/portfolio/${next.id}`}
                                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
                            >
                                ๋‹ค์Œ ์ž‘ํ’ˆ โ†’
                            </Link>
                        ) : (
                            <div></div>
                        )}
                    </div>
                </main>
            </div>

            {/* ๋ผ์ดํŠธ๋ฐ•์Šค ๋ชจ๋‹ฌ */}
            {isModalOpen && (
                <div
                    className="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50"
                    onClick={() => setIsModalOpen(false)}
                >
                    <img
                        src={`/storage/${portfolio.image_path}`}
                        alt={portfolio.title}
                        className="max-w-full max-h-full"
                    />
                    <button
                        className="absolute top-4 right-4 text-white text-4xl"
                        onClick={() => setIsModalOpen(false)}
                    >
                        ร—
                    </button>
                </div>
            )}
        </>
    );
}
```

**ํ…Œ์ŠคํŠธ์šฉ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€:**

`php artisan tinker` ์‹คํ–‰ ํ›„:
```php
App\Models\Portfolio::create([
    'title' => '๋ฏธ๋‹ˆ๋ฉ€ ๋กœ๊ณ  ๋””์ž์ธ',
    'description' => '๊น”๋”ํ•˜๊ณ  ํ˜„๋Œ€์ ์ธ ๋กœ๊ณ  ์ž‘์—…',
    'category' => '๋กœ๊ณ ',
    'image_path' => 'portfolios/sample1.jpg', // public/storage/portfolios/sample1.jpg ์ค€๋น„ ํ•„์š”
    'tools' => ['Illustrator', 'Photoshop'],
]);

App\Models\Portfolio::create([
    'title' => '๋ฐ˜์‘ํ˜• ์›น์‚ฌ์ดํŠธ ๋””์ž์ธ',
    'description' => '๋ชจ๋ฐ”์ผ ์นœํ™”์  UI/UX',
    'category' => '์›น๋””์ž์ธ',
    'image_path' => 'portfolios/sample2.jpg',
    'tools' => ['Figma', 'Adobe XD'],
]);
```

*์ฐธ๊ณ : ์‹ค์ œ ์ด๋ฏธ์ง€๋ฅผ `public/storage/portfolios/` ํด๋”์— ๋ฐฐ์น˜ํ•˜๊ฑฐ๋‚˜, placeholder ์ด๋ฏธ์ง€ ์„œ๋น„์Šค ์‚ฌ์šฉ (์˜ˆ: `https://via.placeholder.com/800x600`)*

**๋‹ค์Œ ๋‹จ๊ณ„ ์ค€๋น„:**

- ๊ฒฌ์  ๋ฌธ์˜ ํผ ํŽ˜์ด์ง€ ๊ตฌํ˜„

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

5
Step 5
**๊ฒฌ์  ๋ฌธ์˜ ํผ ํŽ˜์ด์ง€ ๊ตฌํ˜„ (์„œ๋น„์Šค ์„ ํƒ, ํŒŒ์ผ ์—…๋กœ๋“œ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ)**

1. **InquiryController ์ƒ์„ฑ:**
   ```bash
   php artisan make:controller InquiryController
   ```

   `app/Http/Controllers/InquiryController.php`:
   ```php
   <?php

   namespace App\Http\Controllers;

   use App\Models\Inquiry;
   use Illuminate\Http\Request;
   use Inertia\Inertia;

   class InquiryController extends Controller
   {
       public function create()
       {
           $services = ['๋กœ๊ณ  ๋””์ž์ธ', '๋ช…ํ•จ ๋””์ž์ธ', '์›น ๋ฐฐ๋„ˆ', 'ํŒจํ‚ค์ง• ๋””์ž์ธ', '๋ธŒ๋žœ๋“œ ์•„์ด๋ดํ‹ฐํ‹ฐ'];
           
           return Inertia::render('Inquiry/Create', [
               'services' => $services,
           ]);
       }

       public function store(Request $request)
       {
           $validated = $request->validate([
               'service' => 'required|string',
               'client_name' => 'required|string|max:255',
               'client_email' => 'required|email',
               'project_details' => 'required|string|min:20',
               'budget_range' => 'nullable|string',
               'desired_date' => 'nullable|date|after:today',
               'reference_image' => 'nullable|image|max:5120', // ์ตœ๋Œ€ 5MB
           ]);

           if ($request->hasFile('reference_image')) {
               $path = $request->file('reference_image')->store('inquiries', 'public');
               $validated['reference_image'] = $path;
           }

           Inquiry::create($validated);

           return redirect()->route('inquiry.success');
       }

       public function success()
       {
           return Inertia::render('Inquiry/Success');
       }
   }
   ```

2. **๋ผ์šฐํŠธ ์ถ”๊ฐ€ (`routes/web.php`):**
   ```php
   use App\Http\Controllers\InquiryController;

   Route::get('/inquiry', [InquiryController::class, 'create'])->name('inquiry.create');
   Route::post('/inquiry', [InquiryController::class, 'store'])->name('inquiry.store');
   Route::get('/inquiry/success', [InquiryController::class, 'success'])->name('inquiry.success');
   ```

3. **React ํผ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:**

   `resources/js/Pages/Inquiry/Create.jsx`:
   ```jsx
   import { Head, useForm } from '@inertiajs/react';

   export default function Create({ services }) {
       const { data, setData, post, processing, errors } = useForm({
           service: '',
           client_name: '',
           client_email: '',
           project_details: '',
           budget_range: '',
           desired_date: '',
           reference_image: null,
       });

       function submit(e) {
           e.preventDefault();
           post('/inquiry');
       }

       return (
           <>
               <Head title="๊ฒฌ์  ๋ฌธ์˜" />
               <div className="min-h-screen bg-gray-50">
                   <header className="bg-white shadow">
                       <div className="max-w-7xl mx-auto py-6 px-4">
                           <h1 className="text-3xl font-bold text-gray-900">๊ฒฌ์  ๋ฌธ์˜</h1>
                       </div>
                   </header>

                   <main className="max-w-2xl mx-auto py-8 px-4">
                       <form onSubmit={submit} className="bg-white rounded-lg shadow p-6 space-y-4">
                           {/* ์„œ๋น„์Šค ์„ ํƒ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ์š”์ฒญ ์„œ๋น„์Šค *
                               </label>
                               <select
                                   value={data.service}
                                   onChange={(e) => setData('service', e.target.value)}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               >
                                   <option value="">์„ ํƒํ•˜์„ธ์š”</option>
                                   {services.map((service) => (
                                       <option key={service} value={service}>
                                           {service}
                                       </option>
                                   ))}
                               </select>
                               {errors.service && (
                                   <p className="text-red-600 text-sm mt-1">{errors.service}</p>
                               )}
                           </div>

                           {/* ์ด๋ฆ„ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ์ด๋ฆ„ *
                               </label>
                               <input
                                   type="text"
                                   value={data.client_name}
                                   onChange={(e) => setData('client_name', e.target.value)}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               />
                               {errors.client_name && (
                                   <p className="text-red-600 text-sm mt-1">{errors.client_name}</p>
                               )}
                           </div>

                           {/* ์ด๋ฉ”์ผ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ์ด๋ฉ”์ผ *
                               </label>
                               <input
                                   type="email"
                                   value={data.client_email}
                                   onChange={(e) => setData('client_email', e.target.value)}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               />
                               {errors.client_email && (
                                   <p className="text-red-600 text-sm mt-1">{errors.client_email}</p>
                               )}
                           </div>

                           {/* ํ”„๋กœ์ ํŠธ ์ƒ์„ธ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ํ”„๋กœ์ ํŠธ ์ƒ์„ธ ์„ค๋ช… * (์ตœ์†Œ 20์ž)
                               </label>
                               <textarea
                                   value={data.project_details}
                                   onChange={(e) => setData('project_details', e.target.value)}
                                   rows="5"
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               />
                               {errors.project_details && (
                                   <p className="text-red-600 text-sm mt-1">{errors.project_details}</p>
                               )}
                           </div>

                           {/* ์˜ˆ์‚ฐ ๋ฒ”์œ„ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ์˜ˆ์‚ฐ ๋ฒ”์œ„ (์„ ํƒ)
                               </label>
                               <select
                                   value={data.budget_range}
                                   onChange={(e) => setData('budget_range', e.target.value)}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               >
                                   <option value="">์„ ํƒํ•˜์„ธ์š”</option>
                                   <option value="50๋งŒ์› ์ดํ•˜">50๋งŒ์› ์ดํ•˜</option>
                                   <option value="50๋งŒ์› ~ 100๋งŒ์›">50๋งŒ์› ~ 100๋งŒ์›</option>
                                   <option value="100๋งŒ์› ~ 200๋งŒ์›">100๋งŒ์› ~ 200๋งŒ์›</option>
                                   <option value="200๋งŒ์› ์ด์ƒ">200๋งŒ์› ์ด์ƒ</option>
                               </select>
                           </div>

                           {/* ํฌ๋ง ์™„๋ฃŒ ์ผ์ • */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ํฌ๋ง ์™„๋ฃŒ ์ผ์ • (์„ ํƒ)
                               </label>
                               <input
                                   type="date"
                                   value={data.desired_date}
                                   onChange={(e) => setData('desired_date', e.target.value)}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               />
                               {errors.desired_date && (
                                   <p className="text-red-600 text-sm mt-1">{errors.desired_date}</p>
                               )}
                           </div>

                           {/* ์ฐธ๊ณ  ์ด๋ฏธ์ง€ */}
                           <div>
                               <label className="block text-sm font-medium text-gray-700 mb-1">
                                   ์ฐธ๊ณ  ์ด๋ฏธ์ง€ (์„ ํƒ, ์ตœ๋Œ€ 5MB)
                               </label>
                               <input
                                   type="file"
                                   accept="image/*"
                                   onChange={(e) => setData('reference_image', e.target.files[0])}
                                   className="w-full border border-gray-300 rounded px-3 py-2"
                               />
                               {errors.reference_image && (
                                   <p className="text-red-600 text-sm mt-1">{errors.reference_image}</p>
                               )}
                           </div>

                           {/* ์ œ์ถœ ๋ฒ„ํŠผ */}
                           <button
                               type="submit"
                               disabled={processing}
                               className="w-full bg-blue-600 text-white py-3 rounded hover:bg-blue-700 disabled:bg-gray-400"
                           >
                               {processing ? '์ „์†ก ์ค‘...' : '๊ฒฌ์  ๋ฌธ์˜ ๋ณด๋‚ด๊ธฐ'}
                           </button>
                       </form>
                   </main>
               </div>
           </>
       );
   }
   ```

4. **์„ฑ๊ณต ํŽ˜์ด์ง€ ์ƒ์„ฑ:**

   `resources/js/Pages/Inquiry/Success.jsx`:
   ```jsx
   import { Head, Link } from '@inertiajs/react';

   export default function Success() {
       return (
           <>
               <Head title="๋ฌธ์˜ ์™„๋ฃŒ" />
               <div className="min-h-screen bg-gray-50 flex items-center justify-center">
                   <div className="bg-white rounded-lg shadow p-8 text-center max-w-md">
                       <div className="text-6xl mb-4">โœ…</div>
                       <h1 className="text-2xl font-bold mb-4">๋ฌธ์˜๊ฐ€ ์ ‘์ˆ˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค!</h1>
                       <p className="text-gray-600 mb-6">
                           ๋น ๋ฅธ ์‹œ์ผ ๋‚ด์— ์ด๋ฉ”์ผ๋กœ ๋‹ต๋ณ€ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.
                       </p>
                       <Link
                           href="/"
                           className="inline-block bg-blue-600 text-white px-6 py-3 rounded hover:bg-blue-700"
                       >
                           ๊ฐค๋Ÿฌ๋ฆฌ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
                       </Link>
                   </div>
               </div>
           </>
       );
   }
   ```

**๋‹ค์Œ ๋‹จ๊ณ„ ์ค€๋น„:**

- ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด์— ๊ฒฌ์  ๋ฌธ์˜ ๋งํฌ ์ถ”๊ฐ€
- ๋””์ž์ด๋„ˆ์šฉ ๋ฌธ์˜ ๊ด€๋ฆฌ ํŽ˜์ด์ง€ ๊ตฌํ˜„

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๊ฒฌ์  ๋ฌธ์˜ ํผ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์ž‘๋™ํ•˜๋ฉฐ, ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํ˜•์‹์ด ์ž˜๋ชป๋œ ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ์—…๋กœ๋“œ ์‹œ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๋ฉฐ, 5MB ์ด์ƒ ํŒŒ์ผ์€ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ํผ ์ œ์ถœ ํ›„ ์„ฑ๊ณต ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฌธ์˜ ๋‚ด์—ญ์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

6
Step 6
**๋””์ž์ด๋„ˆ์šฉ ๋ฌธ์˜ ๊ด€๋ฆฌ ํŽ˜์ด์ง€ ๊ตฌํ˜„ (์ธ์ฆ ํ•„์š”, ์ƒํƒœ ๋ณ€๊ฒฝ)**

1. **InquiryController์— ๊ด€๋ฆฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:**

   `app/Http/Controllers/InquiryController.php`์— ์ถ”๊ฐ€:
   ```php
   public function manage()
   {
       $inquiries = Inquiry::latest()->paginate(10);
       
       return Inertia::render('Inquiry/Manage', [
           'inquiries' => $inquiries,
       ]);
   }

   public function updateStatus(Request $request, $id)
   {
       $validated = $request->validate([
           'status' => 'required|in:pending,reviewed,in_progress,completed',
       ]);

       $inquiry = Inquiry::findOrFail($id);
       $inquiry->update($validated);

       return back();
   }
   ```

2. **๋ผ์šฐํŠธ ์ถ”๊ฐ€ (์ธ์ฆ ํ•„์š”):**

   `routes/web.php`:
   ```php
   Route::middleware('auth')->group(function () {
       Route::get('/manage/inquiries', [InquiryController::class, 'manage'])->name('inquiry.manage');
       Route::patch('/manage/inquiries/{id}/status', [InquiryController::class, 'updateStatus'])->name('inquiry.updateStatus');
   });
   ```

3. **React ๊ด€๋ฆฌ ํŽ˜์ด์ง€ ์ƒ์„ฑ:**

   `resources/js/Pages/Inquiry/Manage.jsx`:
   ```jsx
   import { Head, Link, router } from '@inertiajs/react';
   import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';

   export default function Manage({ auth, inquiries }) {
       const statusColors = {
           pending: 'bg-yellow-100 text-yellow-800',
           reviewed: 'bg-blue-100 text-blue-800',
           in_progress: 'bg-purple-100 text-purple-800',
           completed: 'bg-green-100 text-green-800',
       };

       const statusLabels = {
           pending: '๊ฒ€ํ†  ๋Œ€๊ธฐ',
           reviewed: '๊ฒ€ํ†  ์™„๋ฃŒ',
           in_progress: '์ง„ํ–‰ ์ค‘',
           completed: '์™„๋ฃŒ',
       };

       function changeStatus(id, newStatus) {
           router.patch(`/manage/inquiries/${id}/status`, { status: newStatus });
       }

       return (
           <AuthenticatedLayout
               user={auth.user}
               header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">๋ฌธ์˜ ๊ด€๋ฆฌ</h2>}
           >
               <Head title="๋ฌธ์˜ ๊ด€๋ฆฌ" />

               <div className="py-12">
                   <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                       <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                           <div className="p-6">
                               <div className="overflow-x-auto">
                                   <table className="min-w-full divide-y divide-gray-200">
                                       <thead className="bg-gray-50">
                                           <tr>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์„œ๋น„์Šค
                                               </th>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์˜๋ขฐ์ธ
                                               </th>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์ด๋ฉ”์ผ
                                               </th>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์ƒํƒœ
                                               </th>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์ ‘์ˆ˜์ผ
                                               </th>
                                               <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                                   ์•ก์…˜
                                               </th>
                                           </tr>
                                       </thead>
                                       <tbody className="bg-white divide-y divide-gray-200">
                                           {inquiries.data.map((inquiry) => (
                                               <tr key={inquiry.id}>
                                                   <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
                                                       {inquiry.service}
                                                   </td>
                                                   <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                                       {inquiry.client_name}
                                                   </td>
                                                   <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                                       {inquiry.client_email}
                                                   </td>
                                                   <td className="px-6 py-4 whitespace-nowrap">
                                                       <span
                                                           className={`px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${
                                                               statusColors[inquiry.status]
                                                           }`}
                                                       >
                                                           {statusLabels[inquiry.status]}
                                                       </span>
                                                   </td>
                                                   <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                                       {new Date(inquiry.created_at).toLocaleDateString('ko-KR')}
                                                   </td>
                                                   <td className="px-6 py-4 whitespace-nowrap text-sm">
                                                       <select
                                                           value={inquiry.status}
                                                           onChange={(e) => changeStatus(inquiry.id, e.target.value)}
                                                           className="border border-gray-300 rounded px-2 py-1 text-xs"
                                                       >
                                                           <option value="pending">๊ฒ€ํ†  ๋Œ€๊ธฐ</option>
                                                           <option value="reviewed">๊ฒ€ํ†  ์™„๋ฃŒ</option>
                                                           <option value="in_progress">์ง„ํ–‰ ์ค‘</option>
                                                           <option value="completed">์™„๋ฃŒ</option>
                                                       </select>
                                                   </td>
                                               </tr>
                                           ))}
                                       </tbody>
                                   </table>
                               </div>

                               {/* ํŽ˜์ด์ง€๋„ค์ด์…˜ */}
                               <div className="mt-4 flex justify-center gap-2">
                                   {inquiries.links.map((link, index) => (
                                       <Link
                                           key={index}
                                           href={link.url || '#'}
                                           className={`px-4 py-2 rounded ${
                                               link.active
                                                   ? 'bg-blue-600 text-white'
                                                   : 'bg-white text-gray-700 hover:bg-gray-100'
                                           }`}
                                           dangerouslySetInnerHTML={{ __html: link.label }}
                                       />
                                   ))}
                               </div>
                           </div>
                       </div>
                   </div>
               </div>
           </AuthenticatedLayout>
       );
   }
   ```

4. **๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด์— ๋งํฌ ์ถ”๊ฐ€:**

   `resources/js/Layouts/AuthenticatedLayout.jsx`์˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ถ€๋ถ„์— ์ถ”๊ฐ€:
   ```jsx
   <NavLink href={route('inquiry.manage')} active={route().current('inquiry.manage')}>
       ๋ฌธ์˜ ๊ด€๋ฆฌ
   </NavLink>
   ```

5. **๊ณต๊ฐœ ํŽ˜์ด์ง€์— ๊ฒฌ์  ๋ฌธ์˜ ๋ฒ„ํŠผ ์ถ”๊ฐ€:**

   `resources/js/Pages/Portfolio/Index.jsx`์˜ ํ—ค๋”์— ๋ฒ„ํŠผ ์ถ”๊ฐ€:
   ```jsx
   <header className="bg-white shadow">
       <div className="max-w-7xl mx-auto py-6 px-4 flex justify-between items-center">
           <h1 className="text-3xl font-bold text-gray-900">๋””์ž์ด๋„ˆ ํฌํŠธํด๋ฆฌ์˜ค</h1>
           <Link
               href="/inquiry"
               className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
           >
               ๊ฒฌ์  ๋ฌธ์˜ํ•˜๊ธฐ
           </Link>
       </div>
   </header>
   ```

**์ตœ์ข… ํ…Œ์ŠคํŠธ:**

1. ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ (Breeze ๊ธฐ๋ณธ ๊ธฐ๋Šฅ)
2. ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์ž‘ํ’ˆ ํ™•์ธ
3. ๊ฒฌ์  ๋ฌธ์˜ ํผ ์ œ์ถœ
4. ๋กœ๊ทธ์ธ ํ›„ ๋ฌธ์˜ ๊ด€๋ฆฌ ํŽ˜์ด์ง€์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ

**ํ”„๋กœ์ ํŠธ ์™„์„ฑ!**

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋””์ž์ด๋„ˆ๊ฐ€ ๋กœ๊ทธ์ธ ํ›„ ๋ฌธ์˜ ๊ด€๋ฆฌ ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด, ๋ชจ๋“  ๊ฒฌ์  ๋ฌธ์˜ ๋ชฉ๋ก์ด ํ…Œ์ด๋ธ” ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฌธ์˜์˜ ์ƒํƒœ๋ฅผ ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ณ€๊ฒฝ ์ฆ‰์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๊ณต๊ฐœ ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€ ์ƒ๋‹จ์— '๊ฒฌ์  ๋ฌธ์˜ํ•˜๊ธฐ' ๋ฒ„ํŠผ์ด ์ถ”๊ฐ€๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ ํผ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ํ”Œ๋žซํผ์ด ๋ฐ˜์‘ํ˜•์œผ๋กœ ์ž‘๋™ํ•˜๋ฉฐ, Laravel + Inertia.js + React์˜ ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.

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

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

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

๋Œ“๊ธ€ (0)

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