๐จ ์จ๋ผ์ธ ์ํ ์๋ขฐ ์ ์ ํผ - ์ปค์คํ ์ผ๋ฌ์คํธ ์ฃผ๋ฌธ ํ๋ซํผ
custom-artwork-order-form
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
์ผ๋ฌ์คํธ๋ ์ดํฐ, ๋์์ด๋, ์๊ฐ๋ฅผ ์ํ ์ํ ์๋ขฐ ์ ์ ์ ์ฉ ๋๋ฉํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ๊ณ ๊ฐ์ด ์ํ๋ ์ํ ์คํ์ผ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ ๋ฑ์ ํผ์ผ๋ก ์ ์ถํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋๊ณ , ์๊ฐ๋ ๊ด๋ฆฌ ํ์ด์ง์์ ์๋ขฐ ๋ชฉ๋ก์ ํ์ธํ ์ ์์ต๋๋ค.
Laravel 13.x + Blade + Tailwind CSS๋ก ๊ตฌํํ๋ฉฐ, React/Vue ๋ฑ SPA ํ๋ ์์ํฌ๋ ์ฌ์ฉํ์ง ์์ต๋๋ค. ์ ๋ฌธ์๋ ๋ฐ๋ผํ ์ ์๋๋ก ๋จ์ํ๊ณ ๋ช ํํ ๊ตฌ์กฐ๋ก ์ค๊ณํฉ๋๋ค.
ํ์ ๊ธฐ๋ฅ
-
์ํ ์๋ขฐ ํผ ํ์ด์ง (๊ณต๊ฐ)
- ๊ณ ๊ฐ ์ด๋ฆ, ์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ ์ ๋ ฅ
- ์ํ ์ข ๋ฅ ์ ํ (์ผ๋ฌ์คํธ/๋ก๊ณ /์บ๋ฆญํฐ ๋์์ธ/๊ธฐํ)
- ์ํ ์ฌ์ด์ฆ ์ ํ (A4/A3/์ ์ฌ๊ฐํ/์ปค์คํ )
- ์ฉ๋ ์ ํ (๊ฐ์ธ์ฉ/์์ ์ฉ/SNS์ฉ/์ธ์์ฉ)
- ์์ฐ ๋ฒ์ ์ ํ (5๋ง์ ์ดํ/5
10๋ง์/1020๋ง์/20๋ง์ ์ด์) - ์์ธ ์์ฒญ์ฌํญ ํ ์คํธ ์ ๋ ฅ (์ ํ)
- ์ฐธ๊ณ ์ด๋ฏธ์ง URL ์ ๋ ฅ (์ ํ)
- ์ ์ถ ๋ฒํผ ํด๋ฆญ ์ DB ์ ์ฅ ํ "์ ์ ์๋ฃ" ๋ฉ์์ง ํ์
-
์๋ขฐ ๋ชฉ๋ก ๊ด๋ฆฌ ํ์ด์ง (์๊ฐ ์ ์ฉ)
- ์ ์๋ ์๋ขฐ ๋ชฉ๋ก ํ ์ด๋ธ๋ก ํ์ (์ต์ ์)
- ๊ฐ ์๋ขฐ ํญ๋ชฉ: ๊ณ ๊ฐ๋ช , ์ํ ์ข ๋ฅ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ, ์ ์์ผ์
- ์์ธ๋ณด๊ธฐ ๋ฒํผ ํด๋ฆญ ์ ์๋ขฐ ์ ์ฒด ๋ด์ฉ ๋ชจ๋ฌ ํ์
- ์๋ขฐ ์ญ์ ๋ฒํผ (ํ์ธ ํ ์ญ์ )
-
UI/UX
- Tailwind CSS๋ก ๋ชจ๋ํ๊ณ ๊น๋ํ ๋์์ธ
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋ ์ด์์
- ํผ ์ ๋ ฅ ํ๋ ์ ํจ์ฑ ๊ฒ์ฌ (Laravel Validation)
- ์ ์ถ ์๋ฃ ์ ์ฑ๊ณต ๋ฉ์์ง (Blade ์ธ์ ํ๋์)
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ์๋ขฐ ์ํ ๊ด๋ฆฌ (์ ์/๊ฒํ ์ค/์๋ฃ/๊ฑฐ์ ) ์ถ๊ฐ
- ์๋ขฐ ๊ฒ์ ๊ธฐ๋ฅ (๊ณ ๊ฐ๋ช , ์ํ ์ข ๋ฅ ํํฐ๋ง)
- ์ด๋ฉ์ผ ์๋ ๋ฐ์ก (์๋ขฐ ์ ์ ์ ๊ณ ๊ฐ์๊ฒ ํ์ธ ๋ฉ์ผ)
- ์ํ ํฌํธํด๋ฆฌ์ค ๊ฐค๋ฌ๋ฆฌ ํ์ด์ง ์ถ๊ฐ (์๋ขฐ ํผ ์์ ์๊ฐ ์ํ ์ํ ํ์)
- ๋คํฌ๋ชจ๋ ํ ๋ง ์ ํ ๋ฒํผ
๊ธฐ์ ์คํ
- ๋ฐฑ์๋: Laravel 13.x (Blade ํ ํ๋ฆฟ)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค: SQLite (๊ฐ๋จํ ๋ก์ปฌ DB)
- ํ๋ก ํธ์๋: Blade + Tailwind CSS 4.x
- ๋ฐฐํฌ: Laravel Valet/Herd (๋ก์ปฌ) ๋๋ Laravel Forge (ํ๋ก๋์ )
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ฌ์ฉ (์ต์ ๋ฒ์ )
- โข Blade ํ ํ๋ฆฟ ์์ง ์ฌ์ฉ (React/Vue ๊ธ์ง)
- โข Tailwind CSS 4.x๋ก ์คํ์ผ๋ง
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ (๊ฐ๋จํ ๋ก์ปฌ DB)
- โข Laravel Validation์ผ๋ก ํผ ์ ๋ ฅ ๊ฒ์ฆ
- โข Blade ์ปดํฌ๋ํธ ํ์ฉํ์ฌ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ ๋์ด๊ธฐ
- โข ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋์์ธ ํ์
- โข ์ธ์ฆ ์์คํ ์ ๊ตฌํํ์ง ์์ (๊ด๋ฆฌ ํ์ด์ง๋ /admin ๊ฒฝ๋ก๋ก ๋จ์ ์ ๊ทผ)
- โข ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ์ ์ธ (URL ์ ๋ ฅ๋ง ๋ฐ์)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir custom-artwork-order-form && cd custom-artwork-order-form
์ด ํด๋์์ Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
๋ค์ ์์
์ ์ํํด์ฃผ์ธ์:
1. Laravel 13.x ํ๋ก์ ํธ ์์ฑ
```bash
composer create-project laravel/laravel .
```
2. Tailwind CSS 4.x ์ค์น ๋ฐ ์ค์
- `npm install -D tailwindcss@latest postcss autoprefixer`
- `npx tailwindcss init -p`
- `tailwind.config.js`์์ content ๊ฒฝ๋ก ์ค์ :
```js
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
]
```
- `resources/css/app.css`์ Tailwind ์ง์๋ฌธ ์ถ๊ฐ:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
3. `.env` ํ์ผ ์์ ํ์ฌ SQLite ์ฌ์ฉ ์ค์
```
DB_CONNECTION=sqlite
# DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD ์ฃผ์ ์ฒ๋ฆฌ
```
- `database/database.sqlite` ํ์ผ ์์ฑ:
```bash
touch database/database.sqlite
```
4. ๊ฐ๋ฐ ์๋ฒ ์คํ ์ค๋น
```bash
npm install
npm run dev
```
(๋ณ๋ ํฐ๋ฏธ๋์์)
```bash
php artisan serve
```
5. ๋ธ๋ผ์ฐ์ ์์ `http://localhost:8000` ์ ์ํ์ฌ Laravel ๊ธฐ๋ณธ ํ๋ฉด ํ์ธ
๋ชจ๋ ์ค์ ์ด ์๋ฃ๋๋ฉด "Laravel ํ๋ก์ ํธ ์์ฑ ๋ฐ Tailwind CSS ์ค์ ์๋ฃ"๋ผ๊ณ ์๋ ค์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , Tailwind CSS 4.x๊ฐ ์ค์น ๋ฐ ์ค์ ๋ฉ๋๋ค. SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ผ์ด ์์ฑ๋๊ณ , ๊ฐ๋ฐ ์๋ฒ๊ฐ ์ ์ ์คํ๋์ด ๋ธ๋ผ์ฐ์ ์์ Laravel ๊ธฐ๋ณธ ํ๋ฉด์ ํ์ธํ ์ ์์ต๋๋ค.
์ด์ ์ํ ์๋ขฐ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ
์ด๋ธ๊ณผ ๋ชจ๋ธ์ ์์ฑํฉ๋๋ค.
๋ค์ ์์
์ ์ํํด์ฃผ์ธ์:
1. `Order` ๋ชจ๋ธ๊ณผ ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์์ฑ
```bash
php artisan make:model Order -m
```
2. `database/migrations/xxxx_create_orders_table.php` ํ์ผ์ ์ด๊ณ ์คํค๋ง ์ ์:
```php
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('customer_name');
$table->string('customer_email');
$table->string('customer_phone');
$table->enum('artwork_type', ['illustration', 'logo', 'character', 'other']);
$table->enum('artwork_size', ['A4', 'A3', 'square', 'custom']);
$table->enum('purpose', ['personal', 'commercial', 'sns', 'print']);
$table->enum('budget', ['under_50k', '50k_100k', '100k_200k', 'over_200k']);
$table->text('details')->nullable();
$table->string('reference_url')->nullable();
$table->timestamps();
});
}
```
3. ๋ง์ด๊ทธ๋ ์ด์
์คํ
```bash
php artisan migrate
```
4. `app/Models/Order.php` ํ์ผ์ ์ด๊ณ fillable ์์ฑ ์ถ๊ฐ:
```php
protected $fillable = [
'customer_name',
'customer_email',
'customer_phone',
'artwork_type',
'artwork_size',
'purpose',
'budget',
'details',
'reference_url',
];
```
๋ง์ด๊ทธ๋ ์ด์
์ด ์ฑ๊ณต์ ์ผ๋ก ์คํ๋๋ฉด "Order ๋ชจ๋ธ ๋ฐ ํ
์ด๋ธ ์์ฑ ์๋ฃ"๋ผ๊ณ ์๋ ค์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Order ๋ชจ๋ธ๊ณผ orders ํ ์ด๋ธ์ด ์์ฑ๋ฉ๋๋ค. ํ ์ด๋ธ์๋ ๊ณ ๊ฐ ์ ๋ณด, ์ํ ์ข ๋ฅ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ, ์์ธ ์์ฒญ์ฌํญ, ์ฐธ๊ณ ์ด๋ฏธ์ง URL ํ๋๊ฐ ํฌํจ๋ฉ๋๋ค. ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์คํ๋์ด SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ์ด ์์ฑ๋ฉ๋๋ค.
์ํ ์๋ขฐ ํผ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ์ด ํ์ด์ง๋ ๊ณ ๊ฐ์ด ์๋ขฐ ์ ๋ณด๋ฅผ ์
๋ ฅํ๊ณ ์ ์ถํ๋ ๊ณต๊ฐ ํ์ด์ง์
๋๋ค.
๋ค์ ์์
์ ์ํํด์ฃผ์ธ์:
1. ๋ผ์ฐํธ ์ ์ (`routes/web.php`)
```php
use App\Http\Controllers\OrderController;
Route::get('/', [OrderController::class, 'create'])->name('orders.create');
Route::post('/orders', [OrderController::class, 'store'])->name('orders.store');
```
2. ์ปจํธ๋กค๋ฌ ์์ฑ
```bash
php artisan make:controller OrderController
```
3. `app/Http/Controllers/OrderController.php`์ create์ store ๋ฉ์๋ ์์ฑ:
```php
public function create()
{
return view('orders.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'customer_name' => 'required|string|max:255',
'customer_email' => 'required|email',
'customer_phone' => 'required|string|max:20',
'artwork_type' => 'required|in:illustration,logo,character,other',
'artwork_size' => 'required|in:A4,A3,square,custom',
'purpose' => 'required|in:personal,commercial,sns,print',
'budget' => 'required|in:under_50k,50k_100k,100k_200k,over_200k',
'details' => 'nullable|string',
'reference_url' => 'nullable|url',
]);
Order::create($validated);
return redirect()->route('orders.create')->with('success', '์๋ขฐ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ์๋์์ต๋๋ค!');
}
```
4. Blade ๋ทฐ ํ์ผ ์์ฑ (`resources/views/orders/create.blade.php`)
- Tailwind CSS๋ก ์คํ์ผ๋ง๋ ํผ ๋ ์ด์์
- ๊ฐ ํ๋๋ฅผ select, input, textarea๋ก ๊ตฌํ
- ์ ์ถ ๋ฒํผ ์ถ๊ฐ
- ์ฑ๊ณต ๋ฉ์์ง ํ์ ์์ญ (session 'success' ์ฌ์ฉ)
- ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ ๋ฉ์์ง ํ์ (@error ์ง์๋ฌธ ์ฌ์ฉ)
ํผ ๋์์ธ ์๊ตฌ์ฌํญ:
- ์ค์ ์ ๋ ฌ, ์ต๋ ๋๋น 600px ์นด๋ ๋ ์ด์์
- ํค๋: "์ํ ์๋ขฐ ์ ์ฒญ" ์ ๋ชฉ + ๊ฐ๋จํ ์ค๋ช
๋ฌธ๊ตฌ
- ๋ชจ๋ ํ์ ํ๋์ * ํ์
- ์ ์ถ ๋ฒํผ์ ํ๋์ ๋ฐฐ๊ฒฝ, ํฐ์ ํ
์คํธ, hover ํจ๊ณผ
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ (ํจ๋ฉ, ํฐํธ ํฌ๊ธฐ ์กฐ์ )
๋ธ๋ผ์ฐ์ ์์ `http://localhost:8000`์ ์ ์ํ์ฌ ํผ์ด ์ ์ ํ์๋๊ณ , ๋ฐ์ดํฐ ์ ์ถ ์ ์ฑ๊ณต ๋ฉ์์ง๊ฐ ๋ํ๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ํ ์๋ขฐ ํผ ํ์ด์ง๊ฐ ์์ฑ๋ฉ๋๋ค. ๊ณ ๊ฐ์ด ์ด๋ฆ, ์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ, ์ํ ์ข ๋ฅ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ, ์์ธ ์์ฒญ์ฌํญ, ์ฐธ๊ณ ์ด๋ฏธ์ง URL์ ์ ๋ ฅํ ์ ์๋ ํผ์ด Tailwind CSS๋ก ๊น๋ํ๊ฒ ๋์์ธ๋ฉ๋๋ค. ํผ ์ ์ถ ์ Laravel Validation์ด ์๋ํ๊ณ , ๋ฐ์ดํฐ๊ฐ DB์ ์ ์ฅ๋๋ฉฐ ์ฑ๊ณต ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
์ด์ ์๊ฐ๊ฐ ์ ์๋ ์๋ขฐ ๋ชฉ๋ก์ ํ์ธํ ์ ์๋ ๊ด๋ฆฌ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค.
๋ค์ ์์
์ ์ํํด์ฃผ์ธ์:
1. ๋ผ์ฐํธ ์ถ๊ฐ (`routes/web.php`)
```php
Route::get('/admin/orders', [OrderController::class, 'index'])->name('orders.index');
Route::delete('/admin/orders/{order}', [OrderController::class, 'destroy'])->name('orders.destroy');
```
2. `OrderController`์ index์ destroy ๋ฉ์๋ ์ถ๊ฐ:
```php
public function index()
{
$orders = Order::latest()->get();
return view('orders.index', compact('orders'));
}
public function destroy(Order $order)
{
$order->delete();
return redirect()->route('orders.index')->with('success', '์๋ขฐ๊ฐ ์ญ์ ๋์์ต๋๋ค.');
}
```
3. Blade ๋ทฐ ํ์ผ ์์ฑ (`resources/views/orders/index.blade.php`)
- ์๋ขฐ ๋ชฉ๋ก์ ํ
์ด๋ธ ํํ๋ก ํ์
- ํ
์ด๋ธ ์ปฌ๋ผ: ๊ณ ๊ฐ๋ช
, ์ํ ์ข
๋ฅ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ, ์ ์์ผ์, ์์ธ๋ณด๊ธฐ/์ญ์ ๋ฒํผ
- ๊ฐ ํ์ "์์ธ๋ณด๊ธฐ" ๋ฒํผ (๋ชจ๋ฌ ํ์
ํธ๋ฆฌ๊ฑฐ)
- ๊ฐ ํ์ "์ญ์ " ๋ฒํผ (ํ์ธ ํ DELETE ์์ฒญ)
- ์์ธ๋ณด๊ธฐ ๋ชจ๋ฌ: ์๋ขฐ ์ ์ฒด ๋ด์ฉ ํ์ (๊ณ ๊ฐ ์ฐ๋ฝ์ฒ, ์์ธ ์์ฒญ์ฌํญ, ์ฐธ๊ณ URL ํฌํจ)
4. ๋ชจ๋ฌ ๊ตฌํ (Alpine.js ๋๋ Tailwind CSS + Blade๋ก ๊ฐ๋จํ)
- ๋ชจ๋ฌ ์ด๊ธฐ/๋ซ๊ธฐ ํ ๊ธ
- ๋ชจ๋ฌ ๋ด๋ถ์ ์๋ขฐ ์์ธ ์ ๋ณด ํ์
- ๋ชจ๋ฌ ๋ฐฐ๊ฒฝ ํด๋ฆญ ์ ๋ซ๊ธฐ
5. ์ญ์ ๋ฒํผ ํ์ธ ๋ฉ์์ง (JavaScript confirm ๋๋ Alpine.js)
```html
<form method="POST" action="{{ route('orders.destroy', $order) }}" onsubmit="return confirm('์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?');">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">์ญ์ </button>
</form>
```
๋์์ธ ์๊ตฌ์ฌํญ:
- ํ
์ด๋ธ์ ์ ์ฒด ๋๋น, ํค๋๋ ํ์ ๋ฐฐ๊ฒฝ
- ๋ชจ๋ฐ์ผ์์๋ ํ
์ด๋ธ ๋์ ์นด๋ ๋ ์ด์์์ผ๋ก ๋ณ๊ฒฝ (๋ฐ์ํ)
- ๋ชจ๋ฌ์ ํ๋ฉด ์ค์์ ํ์, ๋ฐํฌ๋ช
๋ฐฐ๊ฒฝ
- ์ฑ๊ณต ๋ฉ์์ง ํ์ ์์ญ ์ถ๊ฐ
๋ธ๋ผ์ฐ์ ์์ `http://localhost:8000/admin/orders`์ ์ ์ํ์ฌ ์๋ขฐ ๋ชฉ๋ก์ด ํ์๋๊ณ , ์์ธ๋ณด๊ธฐ ๋ชจ๋ฌ๊ณผ ์ญ์ ๊ธฐ๋ฅ์ด ์ ์ ์๋ํ๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ์๊ฐ ์ ์ฉ ๊ด๋ฆฌ ํ์ด์ง๊ฐ ์์ฑ๋ฉ๋๋ค. ์ ์๋ ์๋ขฐ ๋ชฉ๋ก์ด ํ ์ด๋ธ๋ก ํ์๋๋ฉฐ, ๊ฐ ์๋ขฐ์ ๊ธฐ๋ณธ ์ ๋ณด(๊ณ ๊ฐ๋ช , ์ํ ์ข ๋ฅ, ์ฌ์ด์ฆ, ์ฉ๋, ์์ฐ, ์ ์์ผ์)๋ฅผ ํ๋์ ๋ณผ ์ ์์ต๋๋ค. ์์ธ๋ณด๊ธฐ ๋ฒํผ ํด๋ฆญ ์ ๋ชจ๋ฌ์ด ์ด๋ฆฌ๊ณ ์๋ขฐ ์ ์ฒด ๋ด์ฉ์ด ํ์๋ฉ๋๋ค. ์ญ์ ๋ฒํผ ํด๋ฆญ ์ ํ์ธ ํ ์๋ขฐ๊ฐ DB์์ ์ญ์ ๋ฉ๋๋ค. ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋ ์ด์์์ด ์ ์ฉ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!