๐ช ์์ปค์ค๋จ ๊ณต์ฐ ์ผ์ ์บ๋ฆฐ๋ - ํฌ์ด ๋์๋ณ ํฐ์ผ ์๋งค ์๋ด
circus-tour-schedule-calendar
๋ฌธ์ ์ค๋ช
๐ช ์์ปค์ค๋จ ๊ณต์ฐ ์ผ์ ์บ๋ฆฐ๋
์ ๊ตญ์ ์ํํ๋ ์์ปค์ค๋จ์ ๊ณต์ฐ ์ผ์ ์ ํ๋์ ๋ณด์ฌ์ฃผ๋ ์บ๋ฆฐใใผ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ๋ฐฉ๋ฌธ์๋ ๋์๋ณ๋ก ๊ณต์ฐ ๋ ์ง์ ์๊ฐ์ ํ์ธํ๊ณ , ๊ฐ ๊ณต์ฐ์ ์์ธ ์ ๋ณด(์ฅ์, ํฐ์ผ ๊ฐ๊ฒฉ, ๋จ์ ์ข์)๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
ํ์ ๊ธฐ๋ฅ
- โ ์๋ณ ์บ๋ฆฐ๋ ๋ทฐ (์ด์ /๋ค์ ๋ฌ ์ด๋ ๋ฒํผ)
- โ ๊ณต์ฐ ์ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ (๋ ์ง, ๋์, ์ฅ์, ์๊ฐ, ํฐ์ผ ๊ฐ๊ฒฉ, ์์ฌ์)
- โ ์บ๋ฆฐ๋์์ ๊ณต์ฐ์ด ์๋ ๋ ์ง์ ๋ฐฐ์ง ํ์
- โ ๋ ์ง ํด๋ฆญ ์ ํด๋น ๋ ์ง์ ๊ณต์ฐ ์์ธ ์ ๋ณด ๋ชจ๋ฌ ํ์
- โ ๊ณต์ฐ ์ถ๊ฐ ํผ (๊ด๋ฆฌ์์ฉ ๊ฐ๋จ ์ ๋ ฅ ํ์ด์ง)
- โ ์์ฌ์์ด 10์ ์ดํ์ผ ๋ "๋งค์ง ์๋ฐ" ๋ผ๋ฒจ ํ์
- โ Tailwind CSS๋ก ์์ปค์ค ๋๋์ ์ปฌ๋ฌํํ ๋์์ธ
๋ณด๋์ค ๊ธฐ๋ฅ (์ ํ)
- ๐ ๋์๋ณ ํํฐ๋ง (๋๋กญ๋ค์ด์ผ๋ก ํน์ ๋์ ๊ณต์ฐ๋ง ๋ณด๊ธฐ)
- ๐ "์ค๋์ ๊ณต์ฐ" ํ์ด๋ผ์ดํธ (๋น์ผ ๊ณต์ฐ ๊ฐ์กฐ ํ์)
- ๐ ๊ณต์ฐ ๊ฒ์ ๊ธฐ๋ฅ (๋์๋ช ๋๋ ์ฅ์๋ช ์ผ๋ก ๊ฒ์)
- ๐ ํฐ์ผ ์๋งค ๋งํฌ ์ฐ๊ฒฐ (์ธ๋ถ ์๋งค ์ฌ์ดํธ URL)
์ด ํ๋ก์ ํธ๋ Laravel 13.x + Blade ํ ํ๋ฆฟ์ผ๋ก ๊ตฌํํ๋ฉฐ, SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค. ํ๋ก๊ทธ๋๋ฐ ์ด๋ณด์๋ AI ๋๊ตฌ์ ๋์์ ๋ฐ์ ๋จ๊ณ๋ณ๋ก ์์ฑํ ์ ์์ต๋๋ค.
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ฌ์ฉ (์ต์ ๋ฒ์ )
- โข Blade ํ ํ๋ฆฟ ์์ง์ผ๋ก UI ๊ตฌ์ฑ (React/Vue ์ฌ์ฉ ๊ธ์ง)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ
- โข ์๋ณ ์บ๋ฆฐ๋ ๋ ๋๋ง ๋ก์ง ๊ตฌํ (Carbon ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ)
- โข ๋ชจ๋ฌ ํ์ ์ Alpine.js ๋๋ ์์ JavaScript๋ก ๊ตฌํ
- โข ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ/ํ๋ธ๋ฆฟ ๋์)
- โข ์ค์๊ฐ ์ฑํ ๊ธฐ๋ฅ ๊ธ์ง
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir circus-tour-schedule-calendar && cd circus-tour-schedule-calendar ์ด ํด๋์์ Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค. # Laravel ํ๋ก์ ํธ ์์ฑ composer create-project laravel/laravel:^13.0 . # ํ๊ฒฝ ์ค์ - .env ํ์ผ์์ DB_CONNECTION์ sqlite๋ก ๋ณ๊ฒฝ - database/database.sqlite ํ์ผ ์์ฑ - APP_NAME์ 'Circus Tour Calendar'๋ก ๋ณ๊ฒฝ # Tailwind CSS ์ค์น npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p # tailwind.config.js ์ค์ content: [ "./resources/**/*.blade.php", "./resources/**/*.js", ] # resources/css/app.css์ Tailwind ์ง์์ด ์ถ๊ฐ @tailwind base; @tailwind components; @tailwind utilities; # ๋ง์ด๊ทธ๋ ์ด์ ์คํ php artisan migrate ํ๋ก์ ํธ ๊ตฌ์กฐ๊ฐ ์ค๋น๋๋ฉด ๋ค์ ๋จ๊ณ๋ก ์งํํ๊ฒ ์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ค์ ๋๋ฉฐ, Tailwind CSS๊ฐ ์ค์น๋ ์ํ. ๊ธฐ๋ณธ ๋ง์ด๊ทธ๋ ์ด์ ์ด ์คํ๋์ด users ํ ์ด๋ธ ๋ฑ์ด ์์ฑ๋จ.
# ๊ณต์ฐ ์ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ
## ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์์ฑ
php artisan make:migration create_performances_table
## database/migrations/xxxx_create_performances_table.php ์์ฑ
Schema::create('performances', function (Blueprint $table) {
$table->id();
$table->date('performance_date'); // ๊ณต์ฐ ๋ ์ง
$table->time('performance_time'); // ๊ณต์ฐ ์๊ฐ
$table->string('city'); // ๋์๋ช
$table->string('venue'); // ๊ณต์ฐ์ฅ ์ด๋ฆ
$table->integer('ticket_price'); // ํฐ์ผ ๊ฐ๊ฒฉ (์)
$table->integer('remaining_seats'); // ์์ฌ์
$table->string('booking_url')->nullable(); // ์๋งค ๋งํฌ (์ ํ)
$table->timestamps();
$table->index('performance_date'); // ๋ ์ง ๊ฒ์ ์ต์ ํ
});
## ๋ง์ด๊ทธ๋ ์ด์
์คํ
php artisan migrate
## ๋ชจ๋ธ ์์ฑ
php artisan make:model Performance
## app/Models/Performance.php
protected $fillable = [
'performance_date',
'performance_time',
'city',
'venue',
'ticket_price',
'remaining_seats',
'booking_url',
];
protected $casts = [
'performance_date' => 'date',
'performance_time' => 'datetime',
];
## ์๋ ์์ฑ (์ํ ๋ฐ์ดํฐ)
php artisan make:seeder PerformanceSeeder
## database/seeders/PerformanceSeeder.php
// 2025๋
1์~3์ ์ํ ๊ณต์ฐ 10๊ฐ ์ ๋ ์์ฑ
// ์์ธ, ๋ถ์ฐ, ๋๊ตฌ, ๊ด์ฃผ, ๋์ ๋ฑ ๋ค์ํ ๋์
// ์์ฌ์์ ๋๋ค (5~100์)
php artisan db:seed --class=PerformanceSeeder
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํ ๋ฐ์ดํฐ๊ฐ ์ค๋น๋๋ฉด ๋ค์ ๋จ๊ณ๋ก ์งํํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: performances ํ ์ด๋ธ์ด ์์ฑ๋๊ณ , Performance ๋ชจ๋ธ์ด ์ค๋น๋๋ฉฐ, 10๊ฐ ์ ๋์ ์ํ ๊ณต์ฐ ๋ฐ์ดํฐ๊ฐ ์๋ฉ๋จ. ๋ ์ง, ๋์, ์์ฌ์ ์ ๋ณด๊ฐ ํฌํจ๋ ํ ์คํธ ๋ฐ์ดํฐ ํ์ธ ๊ฐ๋ฅ.
# ์บ๋ฆฐ๋ ๋ทฐ ์ปจํธ๋กค๋ฌ ๋ฐ ๋ผ์ฐํธ ๊ตฌํ
## ์ปจํธ๋กค๋ฌ ์์ฑ
php artisan make:controller CalendarController
## app/Http/Controllers/CalendarController.php
public function index(Request $request)
{
// URL ์ฟผ๋ฆฌ์์ year, month ๊ฐ์ ธ์ค๊ธฐ (๊ธฐ๋ณธ๊ฐ: ํ์ฌ ๋
์)
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
// Carbon์ผ๋ก ํด๋น ์์ ์ฒซ๋ /๋ง์ง๋ง๋ ๊ณ์ฐ
$startOfMonth = Carbon::create($year, $month, 1);
$endOfMonth = $startOfMonth->copy()->endOfMonth();
// ํด๋น ์์ ๊ณต์ฐ ๋ฐ์ดํฐ ์กฐํ
$performances = Performance::whereBetween('performance_date', [
$startOfMonth->format('Y-m-d'),
$endOfMonth->format('Y-m-d')
])->orderBy('performance_date')->orderBy('performance_time')->get();
// ๋ ์ง๋ณ๋ก ๊ทธ๋ฃนํ (Blade์์ ์ฝ๊ฒ ํ์ํ๊ธฐ ์ํด)
$performancesByDate = $performances->groupBy(function($item) {
return $item->performance_date->format('Y-m-d');
});
// ์ด์ /๋ค์ ๋ฌ ๊ณ์ฐ
$prevMonth = $startOfMonth->copy()->subMonth();
$nextMonth = $startOfMonth->copy()->addMonth();
return view('calendar', compact(
'year',
'month',
'startOfMonth',
'endOfMonth',
'performancesByDate',
'prevMonth',
'nextMonth'
));
}
## routes/web.php
use App\Http\Controllers\CalendarController;
Route::get('/', [CalendarController::class, 'index'])->name('calendar');
๋ค์ ๋จ๊ณ์์ Blade ๋ทฐ๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: CalendarController๊ฐ ์์ฑ๋๊ณ , index ๋ฉ์๋๊ฐ ํด๋น ์์ ๊ณต์ฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ์ฌ ๋ทฐ์ ์ ๋ฌ. ๋ผ์ฐํธ๊ฐ ๋ฑ๋ก๋์ด / ๊ฒฝ๋ก๋ก ์ ๊ทผ ๊ฐ๋ฅ.
# ์บ๋ฆฐ๋ Blade ๋ทฐ ์์ฑ ## resources/views/calendar.blade.php ์์ฑ ### ๋ ์ด์์ ๊ตฌ์กฐ - ์๋จ: ์์ปค์ค ํ ๋ง์ ํค๋ (๐ช ์ ๋ชฉ, ์๋ธํ์ดํ) - ์ ๋ค๋น๊ฒ์ด์ : ์ด์ ๋ฌ / ํ์ฌ ๋ ์ ํ์ / ๋ค์ ๋ฌ ๋ฒํผ - ์บ๋ฆฐ๋ ๊ทธ๋ฆฌ๋: 7์ด(์ผ~ํ ) ํ ์ด๋ธ ํํ - ๊ฐ ๋ ์ง ์ ์ ๊ณต์ฐ์ด ์์ผ๋ฉด ์์ ๋ฐฐ์ง ํ์ (์: "๐ญ 2๊ฑด") - ๋ ์ง ํด๋ฆญ ์ ๋ชจ๋ฌ ์ด๊ธฐ (Alpine.js ๋๋ JavaScript) - ๋ชจ๋ฌ: ์ ํํ ๋ ์ง์ ๊ณต์ฐ ์์ธ ์ ๋ณด ๋ฆฌ์คํธ - ๊ณต์ฐ ์๊ฐ, ๋์, ์ฅ์, ํฐ์ผ ๊ฐ๊ฒฉ, ์์ฌ์ - ์์ฌ์ 10์ ์ดํ์ผ ๋ "โ ๏ธ ๋งค์ง ์๋ฐ" ๋นจ๊ฐ ๋ผ๋ฒจ - ์๋งค ๋งํฌ ๋ฒํผ (์์ผ๋ฉด ํ์) ### Tailwind CSS ์คํ์ผ๋ง - ๋ฐฐ๊ฒฝ: gradient (์ฃผํฉ-ํํฌ-๋ณด๋ผ ๋ฑ ์์ปค์ค ๋๋) - ์บ๋ฆฐ๋ ์ : ํธ๋ฒ ์ ํ๋ ํจ๊ณผ - ๊ณต์ฐ ๋ฐฐ์ง: ์๊ณ ๊ท์ฌ์ด ๋ฅ๊ทผ ๋ผ๋ฒจ - ๋ชจ๋ฌ: ์ค์ ์ค๋ฒ๋ ์ด, ์ ๋๋ฉ์ด์ (fade-in) ### Alpine.js ๋๋ JavaScript๋ก ๋ชจ๋ฌ ๊ตฌํ - x-data๋ก selectedDate, showModal ์ํ ๊ด๋ฆฌ - ๋ ์ง ํด๋ฆญ ์ ํด๋น ๋ ์ง ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ฌ์ ํ์ ### ์บ๋ฆฐ๋ ๊ทธ๋ฆฌ๋ ๋ ๋๋ง ๋ก์ง (Blade) - $startOfMonth->dayOfWeek๋ก ์ฒซ๋ ์์ผ ๊ณ์ฐ - ๋น ์ ์ฑ์ฐ๊ธฐ (์ด์ ๋ฌ ๋ง์ง๋ง ๋ ์ง๋ค) - 1์ผ๋ถํฐ ๋ง์ง๋ง ๋ ๊น์ง ๋ฃจํ - ๊ฐ ๋ ์ง์ ํด๋นํ๋ ๊ณต์ฐ ๋ฐ์ดํฐ $performancesByDate์์ ์กฐํ ## resources/views/layouts/app.blade.php (์ ํ) ๊ฐ๋จํ ๋ ์ด์์ ํ์ผ๋ก head, body ๊ตฌ์กฐ ๋ถ๋ฆฌ npm run dev๋ก Vite ๋น๋ ์คํํ์ฌ Tailwind ์ ์ฉ ํ์ธ php artisan serve๋ก ์๋ฒ ์คํ ํ http://localhost:8000 ์ ์ํ์ฌ ์บ๋ฆฐ๋ ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ์๋ณ ์บ๋ฆฐ๋๊ฐ ํ๋ฉด์ ํ์๋๊ณ , ๊ณต์ฐ์ด ์๋ ๋ ์ง์ ๋ฐฐ์ง๊ฐ ๋ณด์. ๋ ์ง ํด๋ฆญ ์ ๋ชจ๋ฌ์ด ์ด๋ฆฌ๋ฉฐ ํด๋น ๋ ์ง์ ๊ณต์ฐ ์์ธ ์ ๋ณด(์๊ฐ, ์ฅ์, ๊ฐ๊ฒฉ, ์์ฌ์)๊ฐ ํ์๋จ. ์ด์ /๋ค์ ๋ฌ ๋ฒํผ์ผ๋ก ์ ์ด๋ ๊ฐ๋ฅ.
# ๊ณต์ฐ ์ถ๊ฐ ํผ ๊ตฌํ (๊ด๋ฆฌ์์ฉ ๊ฐ๋จ ํ์ด์ง)
## ์ปจํธ๋กค๋ฌ ๋ฉ์๋ ์ถ๊ฐ
// CalendarController.php
public function create()
{
return view('performances.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'performance_date' => 'required|date',
'performance_time' => 'required',
'city' => 'required|string|max:100',
'venue' => 'required|string|max:200',
'ticket_price' => 'required|integer|min:0',
'remaining_seats' => 'required|integer|min:0',
'booking_url' => 'nullable|url',
]);
Performance::create($validated);
return redirect()->route('calendar')
->with('success', '๊ณต์ฐ์ด ์ถ๊ฐ๋์์ต๋๋ค!');
}
## ๋ผ์ฐํธ ์ถ๊ฐ
Route::get('/performances/create', [CalendarController::class, 'create'])->name('performances.create');
Route::post('/performances', [CalendarController::class, 'store'])->name('performances.store');
## resources/views/performances/create.blade.php ์์ฑ
- ํผ ํ๋: ๋ ์ง, ์๊ฐ, ๋์, ์ฅ์, ํฐ์ผ ๊ฐ๊ฒฉ, ์์ฌ์, ์๋งค ๋งํฌ
- Tailwind CSS๋ก ๊น๋ํ ํผ ๋์์ธ
- ์ ์ถ ๋ฒํผ: "๊ณต์ฐ ์ถ๊ฐํ๊ธฐ"
- ์ทจ์ ๋ฒํผ: ์บ๋ฆฐ๋๋ก ๋์๊ฐ๊ธฐ ๋งํฌ
## ์บ๋ฆฐ๋ ํ์ด์ง์ "+ ๊ณต์ฐ ์ถ๊ฐ" ๋ฒํผ ์ถ๊ฐ
์๋จ ๋ค๋น๊ฒ์ด์
์ ์์ ๋ฒํผ ๋ฐฐ์น (performances.create ๋ผ์ฐํธ ๋งํฌ)
## ์ฑ๊ณต ๋ฉ์์ง ํ์
calendar.blade.php ์๋จ์ ์ธ์
๋ฉ์์ง ํ์ ์์ญ ์ถ๊ฐ
@if(session('success'))
<div class="alert">{{ session('success') }}</div>
@endif
ํผ ๋์ ํ
์คํธ: ์ ๊ณต์ฐ ์ถ๊ฐ ํ ์บ๋ฆฐ๋์์ ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ณต์ฐ ์ถ๊ฐ ํผ ํ์ด์ง๊ฐ ์์ฑ๋๊ณ , ํผ์ ํตํด ์ ๊ณต์ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ ๊ฐ๋ฅ. ์ถ๊ฐ ํ ์บ๋ฆฐ๋๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋ฉฐ ์ฑ๊ณต ๋ฉ์์ง ํ์. ์บ๋ฆฐ๋์์ ์๋ก ์ถ๊ฐ๋ ๊ณต์ฐ์ด ํด๋น ๋ ์ง์ ํ์๋จ.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!