๐ŸŽจ DIY ๊ตฟ์ฆˆ ์ œ์ž‘ ์ฃผ๋ฌธ ํ”Œ๋žซํผ - ์ปค์Šคํ…€ ๋””์ž์ธ ์—…๋กœ๋“œ & ๊ฒฌ์  ์ž๋™ ์‚ฐ์ถœ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP + JavaScript Laravel + React + Inertia.js AI: Claude

๐ŸŽจ DIY ๊ตฟ์ฆˆ ์ œ์ž‘ ์ฃผ๋ฌธ ํ”Œ๋žซํผ - ์ปค์Šคํ…€ ๋””์ž์ธ ์—…๋กœ๋“œ & ๊ฒฌ์  ์ž๋™ ์‚ฐ์ถœ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… custom-merch-order-platform
์กฐํšŒ์ˆ˜ 118 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

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

๊ฐœ์ธ ํฌ๋ฆฌ์—์ดํ„ฐ๋‚˜ ์†Œ๊ทœ๋ชจ ๋ธŒ๋žœ๋“œ๊ฐ€ ๊ตฟ์ฆˆ(ํ‹ฐ์…”์ธ , ์Šคํ‹ฐ์ปค, ์—์ฝ”๋ฐฑ ๋“ฑ)๋ฅผ ์ œ์ž‘ ์˜๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ๋ฌธ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ์ด ์ง์ ‘ ๋””์ž์ธ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ , ์ƒํ’ˆ ์ข…๋ฅ˜์™€ ์ˆ˜๋Ÿ‰์„ ์„ ํƒํ•˜๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒฌ์ ์ด ๊ณ„์‚ฐ๋ฉ๋‹ˆ๋‹ค. Laravel + Inertia.js + React ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ์œผ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ๋ณ„๋„ API ์—†์ด ํ†ตํ•ฉ๋œ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

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

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

  • ์ƒํ’ˆ ์นดํƒˆ๋กœ๊ทธ: ํ‹ฐ์…”์ธ , ํ›„๋“œ, ์Šคํ‹ฐ์ปค, ์—์ฝ”๋ฐฑ ๋“ฑ ๊ตฟ์ฆˆ ์ข…๋ฅ˜๋ณ„ ์ƒํ’ˆ ๋ชฉ๋ก (DB ๊ด€๋ฆฌ)
  • ์ฃผ๋ฌธ์„œ ์ž‘์„ฑ ํผ: ์ƒํ’ˆ ์„ ํƒ, ์ˆ˜๋Ÿ‰ ์ž…๋ ฅ, ๋””์ž์ธ ํŒŒ์ผ ์—…๋กœ๋“œ (์ด๋ฏธ์ง€ ํŒŒ์ผ ์ €์žฅ)
  • ์‹ค์‹œ๊ฐ„ ๊ฒฌ์  ๊ณ„์‚ฐ: ์ˆ˜๋Ÿ‰๋ณ„ ๋‹จ๊ฐ€ ์ž๋™ ๊ณ„์‚ฐ (JavaScript ๋˜๋Š” Laravel ์ปจํŠธ๋กค๋Ÿฌ)
  • ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ: ๋‚ด๊ฐ€ ์‹ ์ฒญํ•œ ์ฃผ๋ฌธ ํžˆ์Šคํ† ๋ฆฌ (Inertia ํŽ˜์ด์ง€๋„ค์ด์…˜)
  • ์ฃผ๋ฌธ ์ƒํƒœ ๊ด€๋ฆฌ: ๋Œ€๊ธฐ์ค‘/๊ฒ€ํ† ์ค‘/์ œ์ž‘์ค‘/์™„๋ฃŒ ์ƒํƒœ ํ‘œ์‹œ (Enum ๋˜๋Š” status ์ปฌ๋Ÿผ)
  • ๋ฐ˜์‘ํ˜• UI: ๋ชจ๋ฐ”์ผ/๋ฐ์Šคํฌํ†ฑ ์ตœ์ ํ™” (Tailwind CSS)

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

  • ๋””์ž์ธ ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ด๋ฏธ์ง€ ์ธ๋„ค์ผ ์ƒ์„ฑ)
  • ์ฃผ๋ฌธ ํ•„ํ„ฐ๋ง/๊ฒ€์ƒ‰ (์ƒํ’ˆ ์ข…๋ฅ˜, ์ƒํƒœ๋ณ„)
  • ๊ด€๋ฆฌ์ž ์ฃผ๋ฌธ ์Šน์ธ/๊ฑฐ๋ถ€ ๊ธฐ๋Šฅ (Policy/Gate ํ™œ์šฉ)
  • ์ด๋ฉ”์ผ ์•Œ๋ฆผ (์ฃผ๋ฌธ ์ ‘์ˆ˜ ํ™•์ธ, ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ Laravel Mail)
  • ๊ฒฌ์ ์„œ PDF ๋‹ค์šด๋กœ๋“œ (Laravel Snappy ๋˜๋Š” DomPDF)

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

  • ๋ฐฑ์—”๋“œ: Laravel 13.x (Eloquent ORM, ํŒŒ์ผ ์Šคํ† ๋ฆฌ์ง€)
  • ํ”„๋ก ํŠธ์—”๋“œ: React 19 + Inertia.js (๋ชจ๋…ธ๋ฆฌ์Šค SPA)
  • ์Šคํƒ€์ผ๋ง: Tailwind CSS
  • DB: SQLite (๊ฐœ๋ฐœ์šฉ) ๋˜๋Š” MySQL
  • ํŒŒ์ผ ์—…๋กœ๋“œ: Laravel Storage (local/public disk)

๐Ÿ“‹ ์ œ์•ฝ ์กฐ๊ฑด

  • Inertia.js ๊ธฐ๋ฐ˜ ๋ชจ๋…ธ๋ฆฌ์Šค ๊ตฌ์กฐ (๋ณ„๋„ REST API ์—†์Œ)
  • ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ์ด๋ฏธ์ง€๋งŒ ํ—ˆ์šฉ (jpg, png, pdf, ์ตœ๋Œ€ 5MB)
  • ์ฃผ๋ฌธ์ž ์ •๋ณด๋Š” ๊ฐ„๋‹จํžˆ ์ด๋ฆ„/์ด๋ฉ”์ผ/์—ฐ๋ฝ์ฒ˜๋งŒ ์ˆ˜์ง‘
  • ๊ฒฐ์ œ ์—ฐ๋™ ์—†์Œ (๊ฒฌ์  ํ™•์ธ ํ›„ ๋ณ„๋„ ์—ฐ๋ฝ)
  • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์—†์Œ (์ฃผ๋ฌธ ๋ฌธ์˜๋Š” ๋ฉ”๋ชจ ํ•„๋“œ๋กœ ๋Œ€์ฒด)

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ๊ตฌ์กฐ
  • โ€ข npx create laravel ๋˜๋Š” Laravel Breeze (React + Inertia) starter kit ์‚ฌ์šฉ
  • โ€ข Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง
  • โ€ข SQLite ๋˜๋Š” MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
  • โ€ข ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” Laravel Storage ํ™œ์šฉ (์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ, ์ตœ๋Œ€ 5MB)
  • โ€ข Inertia::render()๋กœ Laravel ์ปจํŠธ๋กค๋Ÿฌ์—์„œ React ์ปดํฌ๋„ŒํŠธ์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ์—†์Œ (HTTP ๊ธฐ๋ฐ˜ ์ฃผ๋ฌธ ๋ฉ”๋ชจ๋กœ ๋Œ€์ฒด)
  • โ€ข ๊ฒฐ์ œ ์—ฐ๋™ ์—†์Œ (๊ฒฌ์  ํ™•์ธ ํ›„ ๋ณ„๋„ ํ˜‘์˜)

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

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

1
Step 1
mkdir custom-merch-order-platform && cd custom-merch-order-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 dev
```

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ๋“ค์„ ์ƒ์„ฑํ•˜์„ธ์š”:

1. **products ํ…Œ์ด๋ธ”** (๊ตฟ์ฆˆ ์ƒํ’ˆ ์นดํƒˆ๋กœ๊ทธ):
   - id (primary key)
   - name (string, ์ƒํ’ˆ๋ช…: ํ‹ฐ์…”์ธ , ํ›„๋“œ, ์Šคํ‹ฐ์ปค ๋“ฑ)
   - category (string, ์นดํ…Œ๊ณ ๋ฆฌ: ์˜๋ฅ˜/๋ฌธ๊ตฌ/๊ธฐํƒ€)
   - base_price (decimal, ๊ธฐ๋ณธ ๋‹จ๊ฐ€)
   - description (text, nullable, ์ƒํ’ˆ ์„ค๋ช…)
   - image_url (string, nullable, ์ƒํ’ˆ ์ด๋ฏธ์ง€ URL)
   - timestamps

2. **orders ํ…Œ์ด๋ธ”** (์ฃผ๋ฌธ ์ •๋ณด):
   - id (primary key)
   - product_id (foreign key to products)
   - customer_name (string, ์ฃผ๋ฌธ์ž ์ด๋ฆ„)
   - customer_email (string, ์ด๋ฉ”์ผ)
   - customer_phone (string, ์—ฐ๋ฝ์ฒ˜)
   - quantity (integer, ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰)
   - design_file_path (string, ์—…๋กœ๋“œ๋œ ๋””์ž์ธ ํŒŒ์ผ ๊ฒฝ๋กœ)
   - total_price (decimal, ์ด ๊ฒฌ์  ๊ธˆ์•ก)
   - notes (text, nullable, ์ฃผ๋ฌธ ๋ฉ”๋ชจ/์š”์ฒญ์‚ฌํ•ญ)
   - status (enum: pending, reviewing, producing, completed, ๊ธฐ๋ณธ๊ฐ’ pending)
   - timestamps

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜๊ณ , Product ๋ชจ๋ธ๊ณผ Order ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜์„ธ์š”. Product ๋ชจ๋ธ์—๋Š” `orders()` hasMany ๊ด€๊ณ„๋ฅผ, Order ๋ชจ๋ธ์—๋Š” `product()` belongsTo ๊ด€๊ณ„๋ฅผ ์ •์˜ํ•˜์„ธ์š”.

์‹œ๋”๋ฅผ ๋งŒ๋“ค์–ด ์ƒ˜ํ”Œ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅผ 5๊ฐœ ์ •๋„ ์ถ”๊ฐ€ํ•˜์„ธ์š” (ํ‹ฐ์…”์ธ , ํ›„๋“œ, ์—์ฝ”๋ฐฑ, ์Šคํ‹ฐ์ปค, ๋จธ๊ทธ์ปต ๋“ฑ).

`.env` ํŒŒ์ผ์—์„œ DB_CONNECTION=sqlite๋กœ ์„ค์ •ํ•˜๊ณ  `database/database.sqlite` ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ ํ›„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜์„ธ์š”.

๋งˆ์ง€๋ง‰์œผ๋กœ Laravel Storage์˜ public ๋””์Šคํฌ๋ฅผ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋กœ ์—ฐ๊ฒฐํ•˜์„ธ์š”:
```bash
php artisan storage:link
```

์™„๋ฃŒ๋˜๋ฉด `php artisan serve`๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  http://localhost:8000 ์—์„œ Laravel Breeze ๊ธฐ๋ณธ ํ™”๋ฉด์ด ๋œจ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel + Inertia + React ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ. products, orders ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ๋ชจ๋ธ ์ƒ์„ฑ. ์ƒ˜ํ”Œ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ ์‹œ๋” ์ถ”๊ฐ€. Storage ์‹ฌ๋ณผ๋ฆญ ๋งํฌ ์„ค์ • ์™„๋ฃŒ. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ๊ฐ€๋Šฅ.

2
Step 2
์ƒํ’ˆ ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

**๋ฐฑ์—”๋“œ (Laravel)**:
- `app/Http/Controllers/ProductController.php`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  `index()` ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
- `Product::all()`๋กœ ๋ชจ๋“  ์ƒํ’ˆ์„ ์กฐํšŒํ•˜๊ณ  `Inertia::render('Products/Index', ['products' => $products])`๋กœ React ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜์„ธ์š”.
- `routes/web.php`์— `Route::get('/products', [ProductController::class, 'index'])->name('products.index');` ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

**ํ”„๋ก ํŠธ์—”๋“œ (React + Inertia)**:
- `resources/js/Pages/Products/Index.jsx` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.
- Inertia์˜ `usePage()` ํ›…์œผ๋กœ `products` props๋ฅผ ๋ฐ›์•„์˜ค์„ธ์š”.
- ์ƒํ’ˆ ๋ชฉ๋ก์„ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ(Tailwind grid)์œผ๋กœ ํ‘œ์‹œํ•˜์„ธ์š”. ๊ฐ ์ƒํ’ˆ ์นด๋“œ์—๋Š” ๋‹ค์Œ ์ •๋ณด๋ฅผ ํฌํ•จ:
  - ์ƒํ’ˆ ์ด๋ฏธ์ง€ (image_url์ด ์žˆ์œผ๋ฉด ํ‘œ์‹œ, ์—†์œผ๋ฉด placeholder)
  - ์ƒํ’ˆ๋ช… (name)
  - ์นดํ…Œ๊ณ ๋ฆฌ (category) ๋ฐฐ์ง€
  - ๊ธฐ๋ณธ ๋‹จ๊ฐ€ (base_price) - "โ‚ฉ10,000๋ถ€ํ„ฐ" ํ˜•์‹์œผ๋กœ ํ‘œ์‹œ
  - "์ฃผ๋ฌธํ•˜๊ธฐ" ๋ฒ„ํŠผ (ํด๋ฆญ ์‹œ `/orders/create?product_id={id}` ํŽ˜์ด์ง€๋กœ ์ด๋™)
- ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ: 1์—ด, ํƒœ๋ธ”๋ฆฟ: 2์—ด, ๋ฐ์Šคํฌํ†ฑ: 3์—ด)
- Tailwind CSS๋กœ ๊น”๋”ํ•œ ์นด๋“œ ๋””์ž์ธ (hover ํšจ๊ณผ, shadow ๋“ฑ)

์ƒ๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜์— "์ƒํ’ˆ ๋ชฉ๋ก" ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ํŽ˜์ด์ง€ ์ œ๋ชฉ์„ "DIY ๊ตฟ์ฆˆ ์ œ์ž‘ ์ฃผ๋ฌธ"์œผ๋กœ ํ‘œ์‹œํ•˜์„ธ์š”.

์™„๋ฃŒ ํ›„ http://localhost:8000/products ์—์„œ ์ƒํ’ˆ ๋ชฉ๋ก์ด ์ž˜ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ƒํ’ˆ ๋ชฉ๋ก ํŽ˜์ด์ง€ ์™„์„ฑ. Laravel ์ปจํŠธ๋กค๋Ÿฌ์—์„œ Inertia๋กœ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ. React ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ์ƒํ’ˆ ์นด๋“œ ํ‘œ์‹œ. "์ฃผ๋ฌธํ•˜๊ธฐ" ๋ฒ„ํŠผ์œผ๋กœ ์ฃผ๋ฌธ ํŽ˜์ด์ง€ ์ด๋™ ๊ฐ€๋Šฅ.

3
Step 3
์ฃผ๋ฌธ์„œ ์ž‘์„ฑ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

**๋ฐฑ์—”๋“œ (Laravel)**:
- `app/Http/Controllers/OrderController.php`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  `create()` ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
- URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ `product_id`๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์ƒํ’ˆ์„ ์กฐํšŒํ•˜์„ธ์š”: `$product = Product::findOrFail(request('product_id'));`
- `Inertia::render('Orders/Create', ['product' => $product])`๋กœ React ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜์„ธ์š”.
- `routes/web.php`์— `Route::get('/orders/create', [OrderController::class, 'create'])->name('orders.create');` ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

**ํ”„๋ก ํŠธ์—”๋“œ (React + Inertia)**:
- `resources/js/Pages/Orders/Create.jsx` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.
- ์ฃผ๋ฌธ์„œ ํผ์„ ๋งŒ๋“œ์„ธ์š”. ๋‹ค์Œ ํ•„๋“œ๋ฅผ ํฌํ•จ:
  - ์„ ํƒ๋œ ์ƒํ’ˆ ์ •๋ณด ํ‘œ์‹œ (์ฝ๊ธฐ ์ „์šฉ, ์ƒํ’ˆ๋ช…/์ด๋ฏธ์ง€/๊ธฐ๋ณธ ๋‹จ๊ฐ€)
  - ์ฃผ๋ฌธ์ž ์ด๋ฆ„ (input text, required)
  - ์ด๋ฉ”์ผ (input email, required)
  - ์—ฐ๋ฝ์ฒ˜ (input tel, required)
  - ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰ (input number, min=1, max=1000, ๊ธฐ๋ณธ๊ฐ’ 10)
  - ๋””์ž์ธ ํŒŒ์ผ ์—…๋กœ๋“œ (input file, accept="image/png,image/jpeg,application/pdf", required)
  - ์ฃผ๋ฌธ ๋ฉ”๋ชจ/์š”์ฒญ์‚ฌํ•ญ (textarea, optional)
  - **์‹ค์‹œ๊ฐ„ ๊ฒฌ์  ๊ณ„์‚ฐ ํ‘œ์‹œ**: ์ˆ˜๋Ÿ‰์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค `์ด ๊ฒฌ์  = base_price * quantity * (์ˆ˜๋Ÿ‰ ํ• ์ธ์œจ)` ๊ณ„์‚ฐ
    - ์˜ˆ: 10๊ฐœ ๋ฏธ๋งŒ 100%, 10~49๊ฐœ 95%, 50~99๊ฐœ 90%, 100๊ฐœ ์ด์ƒ 85% ๋‹จ๊ฐ€ ์ ์šฉ
    - ๊ณ„์‚ฐ๋œ ์ด์•ก์„ ํฐ ๊ธ€์”จ๋กœ ํ‘œ์‹œ ("์˜ˆ์ƒ ๊ฒฌ์ : โ‚ฉ50,000")
- useState๋กœ ํผ ์ƒํƒœ ๊ด€๋ฆฌ (name, email, phone, quantity, file, notes)
- ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ ์‹œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒฌ์  ์žฌ๊ณ„์‚ฐ (useEffect ๋˜๋Š” onChange ํ•ธ๋“ค๋Ÿฌ)
- "์ฃผ๋ฌธ ์‹ ์ฒญํ•˜๊ธฐ" ๋ฒ„ํŠผ (์•„์ง ์ œ์ถœ ๊ธฐ๋Šฅ์€ ์—†์Œ, ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ๊ตฌํ˜„)
- Tailwind CSS๋กœ ๊น”๋”ํ•œ ํผ ๋ ˆ์ด์•„์›ƒ (label, input ์Šคํƒ€์ผ๋ง)

์™„๋ฃŒ ํ›„ http://localhost:8000/orders/create?product_id=1 ์—์„œ ์ฃผ๋ฌธ ํผ์ด ์ž˜ ํ‘œ์‹œ๋˜๊ณ , ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ ์‹œ ๊ฒฌ์ ์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฃผ๋ฌธ์„œ ์ž‘์„ฑ ํŽ˜์ด์ง€ ์™„์„ฑ. ์ƒํ’ˆ ์ •๋ณด ํ‘œ์‹œ, ์ฃผ๋ฌธ์ž ์ •๋ณด ์ž…๋ ฅ ํ•„๋“œ, ๋””์ž์ธ ํŒŒ์ผ ์—…๋กœ๋“œ, ์‹ค์‹œ๊ฐ„ ๊ฒฌ์  ๊ณ„์‚ฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„. ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ ์‹œ ์ด์•ก ์ž๋™ ์—…๋ฐ์ดํŠธ.

4
Step 4
์ฃผ๋ฌธ ์ œ์ถœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

**๋ฐฑ์—”๋“œ (Laravel)**:
- `OrderController`์— `store()` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.
- ํผ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ (Validation):
  ```php
  $validated = $request->validate([
      'product_id' => 'required|exists:products,id',
      'customer_name' => 'required|string|max:100',
      'customer_email' => 'required|email',
      'customer_phone' => 'required|string|max:20',
      'quantity' => 'required|integer|min:1|max:1000',
      'design_file' => 'required|file|mimes:jpg,jpeg,png,pdf|max:5120',
      'notes' => 'nullable|string|max:1000',
  ]);
  ```
- ๋””์ž์ธ ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ:
  - `$request->file('design_file')->store('designs', 'public')`๋กœ ํŒŒ์ผ์„ `storage/app/public/designs` ํด๋”์— ์ €์žฅ
  - ์ €์žฅ๋œ ๊ฒฝ๋กœ๋ฅผ `$filePath` ๋ณ€์ˆ˜์— ์ €์žฅ
- ์ด ๊ฒฌ์  ๊ธˆ์•ก ๊ณ„์‚ฐ (๋ฐฑ์—”๋“œ์—์„œ๋„ ๋™์ผํ•œ ๋กœ์ง ์ ์šฉ):
  ```php
  $product = Product::findOrFail($validated['product_id']);
  $quantity = $validated['quantity'];
  $discount = 1.0;
  if ($quantity >= 100) $discount = 0.85;
  elseif ($quantity >= 50) $discount = 0.90;
  elseif ($quantity >= 10) $discount = 0.95;
  $totalPrice = $product->base_price * $quantity * $discount;
  ```
- Order ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ:
  ```php
  Order::create([
      'product_id' => $validated['product_id'],
      'customer_name' => $validated['customer_name'],
      'customer_email' => $validated['customer_email'],
      'customer_phone' => $validated['customer_phone'],
      'quantity' => $quantity,
      'design_file_path' => $filePath,
      'total_price' => $totalPrice,
      'notes' => $validated['notes'],
      'status' => 'pending',
  ]);
  ```
- ์„ฑ๊ณต ์‹œ ์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ: `return redirect()->route('orders.index')->with('success', '์ฃผ๋ฌธ์ด ์ ‘์ˆ˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');`
- `routes/web.php`์— `Route::post('/orders', [OrderController::class, 'store'])->name('orders.store');` ๋ผ์šฐํŠธ ์ถ”๊ฐ€

**ํ”„๋ก ํŠธ์—”๋“œ (React + Inertia)**:
- `Orders/Create.jsx`์—์„œ Inertia์˜ `useForm()` ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ํผ ์ œ์ถœ ์ฒ˜๋ฆฌ:
  ```jsx
  import { useForm } from '@inertiajs/react';
  const { data, setData, post, processing, errors } = useForm({
      product_id: product.id,
      customer_name: '',
      customer_email: '',
      customer_phone: '',
      quantity: 10,
      design_file: null,
      notes: '',
  });
  ```
- ๊ฐ input์˜ onChange์—์„œ `setData()` ํ˜ธ์ถœ
- ํŒŒ์ผ input์˜ onChange์—์„œ `setData('design_file', e.target.files[0])`
- "์ฃผ๋ฌธ ์‹ ์ฒญํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ `post(route('orders.store'))` ํ˜ธ์ถœ
- ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ (errors ๊ฐ์ฒด ํ™œ์šฉ)
- ์ œ์ถœ ์ค‘์ผ ๋•Œ ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” (processing ์ƒํƒœ ํ™œ์šฉ)

์™„๋ฃŒ ํ›„ ์ฃผ๋ฌธ์„ ์ œ์ถœํ•˜๋ฉด DB์— ์ €์žฅ๋˜๊ณ  ์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. `storage/app/public/designs` ํด๋”์— ํŒŒ์ผ์ด ์—…๋กœ๋“œ๋˜์—ˆ๋Š”์ง€๋„ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฃผ๋ฌธ ์ œ์ถœ ๊ธฐ๋Šฅ ์™„์„ฑ. ํผ ๊ฒ€์ฆ, ํŒŒ์ผ ์—…๋กœ๋“œ, ๊ฒฌ์  ๊ณ„์‚ฐ, DB ์ €์žฅ ๋ชจ๋‘ ๋™์ž‘. Inertia useForm์œผ๋กœ ํผ ์ œ์ถœ ์ฒ˜๋ฆฌ. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์„ฑ๊ณต ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ตฌํ˜„.

5
Step 5
์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

**๋ฐฑ์—”๋“œ (Laravel)**:
- `OrderController`์— `index()` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.
- ๋ชจ๋“  ์ฃผ๋ฌธ์„ ์ตœ์‹ ์ˆœ์œผ๋กœ ์กฐํšŒํ•˜๊ณ  ์ƒํ’ˆ ์ •๋ณด๋„ ํ•จ๊ป˜ ๋กœ๋“œ:
  ```php
  $orders = Order::with('product')
      ->orderBy('created_at', 'desc')
      ->paginate(10);
  ```
- `Inertia::render('Orders/Index', ['orders' => $orders])`๋กœ ์ „๋‹ฌ
- `routes/web.php`์— `Route::get('/orders', [OrderController::class, 'index'])->name('orders.index');` ๋ผ์šฐํŠธ ์ถ”๊ฐ€

**ํ”„๋ก ํŠธ์—”๋“œ (React + Inertia)**:
- `resources/js/Pages/Orders/Index.jsx` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.
- ์ฃผ๋ฌธ ๋ชฉ๋ก์„ ํ…Œ์ด๋ธ” ๋˜๋Š” ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•˜์„ธ์š”. ๊ฐ ์ฃผ๋ฌธ์— ๋‹ค์Œ ์ •๋ณด ํฌํ•จ:
  - ์ฃผ๋ฌธ ๋ฒˆํ˜ธ (id)
  - ์ƒํ’ˆ๋ช… (product.name)
  - ์ฃผ๋ฌธ์ž ์ด๋ฆ„ (customer_name)
  - ์ˆ˜๋Ÿ‰ (quantity)
  - ์ด ๊ฒฌ์  ๊ธˆ์•ก (total_price) - "โ‚ฉ50,000" ํ˜•์‹
  - ์ฃผ๋ฌธ ์ƒํƒœ (status) - ๋ฐฐ์ง€๋กœ ํ‘œ์‹œ (pending: ๋…ธ๋ž€์ƒ‰, reviewing: ํŒŒ๋ž€์ƒ‰, producing: ์ฃผํ™ฉ์ƒ‰, completed: ์ดˆ๋ก์ƒ‰)
  - ์ฃผ๋ฌธ ๋‚ ์งœ (created_at) - "2025-01-15" ํ˜•์‹
  - "์ƒ์„ธ๋ณด๊ธฐ" ๋ฒ„ํŠผ (ํด๋ฆญ ์‹œ `/orders/{id}` ํŽ˜์ด์ง€๋กœ ์ด๋™)
- Inertia์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ปดํฌ๋„ŒํŠธ ํ™œ์šฉ:
  ```jsx
  import { Link } from '@inertiajs/react';
  // ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋งํฌ ๋ Œ๋”๋ง
  orders.links.map(link => <Link href={link.url}>...</Link>)
  ```
- ์ฃผ๋ฌธ์ด ์—†์„ ๋•Œ "์•„์ง ์ฃผ๋ฌธ์ด ์—†์Šต๋‹ˆ๋‹ค" ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
- "์ƒˆ ์ฃผ๋ฌธ ์‹ ์ฒญ" ๋ฒ„ํŠผ ์ถ”๊ฐ€ (์ƒํ’ˆ ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ์ด๋™)
- Tailwind CSS๋กœ ๊น”๋”ํ•œ ํ…Œ์ด๋ธ”/์นด๋“œ ๋””์ž์ธ

๋„ค๋น„๊ฒŒ์ด์…˜์— "์ฃผ๋ฌธ ๋ชฉ๋ก" ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

์™„๋ฃŒ ํ›„ http://localhost:8000/orders ์—์„œ ์ฃผ๋ฌธ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜๊ณ , ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€ ์™„์„ฑ. ๋ชจ๋“  ์ฃผ๋ฌธ์„ ํ…Œ์ด๋ธ”/์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ. ์ƒํƒœ๋ณ„ ๋ฐฐ์ง€, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„. "์ƒ์„ธ๋ณด๊ธฐ" ๋ฒ„ํŠผ์œผ๋กœ ์ฃผ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ด๋™ ๊ฐ€๋Šฅ.

6
Step 6
์ฃผ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

**๋ฐฑ์—”๋“œ (Laravel)**:
- `OrderController`์— `show($id)` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.
- ํŠน์ • ์ฃผ๋ฌธ์„ ์ƒํ’ˆ ์ •๋ณด์™€ ํ•จ๊ป˜ ์กฐํšŒ:
  ```php
  $order = Order::with('product')->findOrFail($id);
  ```
- `Inertia::render('Orders/Show', ['order' => $order])`๋กœ ์ „๋‹ฌ
- `routes/web.php`์— `Route::get('/orders/{id}', [OrderController::class, 'show'])->name('orders.show');` ๋ผ์šฐํŠธ ์ถ”๊ฐ€

**ํ”„๋ก ํŠธ์—”๋“œ (React + Inertia)**:
- `resources/js/Pages/Orders/Show.jsx` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.
- ์ฃผ๋ฌธ ์ƒ์„ธ ์ •๋ณด๋ฅผ 2๋‹จ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ํ‘œ์‹œ:
  - **์™ผ์ชฝ**: ์ฃผ๋ฌธ ์ •๋ณด
    - ์ฃผ๋ฌธ ๋ฒˆํ˜ธ
    - ์ฃผ๋ฌธ ๋‚ ์งœ
    - ์ฃผ๋ฌธ ์ƒํƒœ (ํฐ ๋ฐฐ์ง€๋กœ ํ‘œ์‹œ)
    - ์ฃผ๋ฌธ์ž ์ •๋ณด (์ด๋ฆ„, ์ด๋ฉ”์ผ, ์—ฐ๋ฝ์ฒ˜)
    - ์ฃผ๋ฌธ ๋ฉ”๋ชจ/์š”์ฒญ์‚ฌํ•ญ (์žˆ์„ ๊ฒฝ์šฐ)
  - **์˜ค๋ฅธ์ชฝ**: ์ƒํ’ˆ ๋ฐ ๊ฒฌ์  ์ •๋ณด
    - ์ƒํ’ˆ ์ด๋ฏธ์ง€ (product.image_url)
    - ์ƒํ’ˆ๋ช… (product.name)
    - ์นดํ…Œ๊ณ ๋ฆฌ (product.category)
    - ๊ธฐ๋ณธ ๋‹จ๊ฐ€ (product.base_price)
    - ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰ (order.quantity)
    - ์ด ๊ฒฌ์  ๊ธˆ์•ก (order.total_price) - ํฐ ๊ธ€์”จ๋กœ ๊ฐ•์กฐ
- **๋””์ž์ธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ** ์„น์…˜:
  - ์—…๋กœ๋“œ๋œ ๋””์ž์ธ ํŒŒ์ผ ์ธ๋„ค์ผ ํ‘œ์‹œ (์ด๋ฏธ์ง€์ธ ๊ฒฝ์šฐ)
  - "๋””์ž์ธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ" ๋งํฌ (href="/storage/{order.design_file_path}" download)
- "๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ" ๋ฒ„ํŠผ (์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ์ด๋™)
- Tailwind CSS๋กœ ๊น”๋”ํ•œ 2๋‹จ ๋ ˆ์ด์•„์›ƒ (grid ๋˜๋Š” flex)

์™„๋ฃŒ ํ›„ ์ฃผ๋ฌธ ๋ชฉ๋ก์—์„œ "์ƒ์„ธ๋ณด๊ธฐ"๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ฃผ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€๊ฐ€ ์ž˜ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ๋””์ž์ธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋งํฌ๊ฐ€ ๋™์ž‘ํ•˜๋Š”์ง€๋„ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฃผ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€ ์™„์„ฑ. ์ฃผ๋ฌธ ์ •๋ณด, ์ƒํ’ˆ ์ •๋ณด, ๊ฒฌ์  ์ •๋ณด๋ฅผ 2๋‹จ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ํ‘œ์‹œ. ๋””์ž์ธ ํŒŒ์ผ ์ธ๋„ค์ผ ๋ฐ ๋‹ค์šด๋กœ๋“œ ๋งํฌ ๊ตฌํ˜„. ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€.

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

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

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

๋Œ“๊ธ€ (0)

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