๐ ๋ฌ๋ ํฌ๋ฃจ ์ฃผ๊ฐ ๋ฌ๋ ๊ธฐ๋ก ํ์ด์ง - ํ์๋ณ ๊ฑฐ๋ฆฌ & ํ์ด์ค ํต๊ณ
running-crew-weekly-log
๋ฌธ์ ์ค๋ช
๐ฏ ํ๋ก์ ํธ ๊ฐ์
๋ฌ๋ ํฌ๋ฃจ(๋ํธํ)๋ฅผ ์ํ ์ฃผ๊ฐ ๋ฌ๋ ๊ธฐ๋ก ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ํฌ๋ฃจ ๋ฉค๋ฒ๋ค์ด ์ด๋ฒ ์ฃผ์ ๋ด ๊ฑฐ๋ฆฌ, ํ์ด์ค, ๋ ์ง๋ฅผ ๊ธฐ๋กํ๊ณ , ์ ์ฒด ํ์์ ํต๊ณ๋ฅผ ํ๋์ ๋ณผ ์ ์๋ ์ฌํํ ์น ์๋น์ค์ ๋๋ค.
โจ ํ์ ๊ธฐ๋ฅ
-
๋ฌ๋ ๊ธฐ๋ก ๋ฑ๋ก ํผ
- ์ด๋ฆ, ๋ ์ง, ๊ฑฐ๋ฆฌ(km), ํ์ด์ค(๋ถ:์ด/km), ๋ฉ๋ชจ ์ ๋ ฅ
- SQLite DB์ ์ ์ฅ
-
์ฃผ๊ฐ ๋ฌ๋ ๊ธฐ๋ก ๋ชฉ๋ก
- ์ต๊ทผ 1์ฃผ์ผ ๊ธฐ๋ก๋ง ํ์ (์ค๋ ๊ธฐ์ค 7์ผ ์ด๋ด)
- ๋ ์ง ์ต์ ์ ์ ๋ ฌ
- ๊ฐ ๊ธฐ๋ก๋ง๋ค ์ด๋ฆ, ๋ ์ง, ๊ฑฐ๋ฆฌ, ํ์ด์ค ํ์
-
์ฃผ๊ฐ ํต๊ณ ์์ฝ
- ์ ์ฒด ํฌ๋ฃจ์ ์ด ๊ฑฐ๋ฆฌ ํฉ๊ณ
- ํ๊ท ํ์ด์ค ๊ณ์ฐ
- ๊ฐ์ฅ ๋ง์ด ๋ด ํฌ๋ฃจ์ TOP 3 ๋ฐฐ์ง
-
๋ฐ์ํ UI (Tailwind CSS)
- ๋ชจ๋ฐ์ผ์์๋ ํธํ๊ฒ ๊ธฐ๋ก ์ ๋ ฅ
- ์นด๋ํ ๋ ์ด์์์ผ๋ก ๊น๋ํ๊ฒ ์ ๋ฆฌ
- ๋ฌ๋ฆฌ๊ธฐ ๊ด๋ จ ์์ด์ฝ/์์ ํ์ฉ (๐โโ๏ธ ์ด๋ก/ํ๋ ๊ณ์ด)
๐ ์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ํฌ๋ฃจ์๋ณ ํํฐ๋ง (๋๋กญ๋ค์ด์ผ๋ก ํน์ ๋ฉค๋ฒ๋ง ๋ณด๊ธฐ)
- ํ์ด์ค๋ฅผ ๋ง๋ ๊ทธ๋ํ๋ก ์๊ฐํ (๋น ๋ฆ/๋ณดํต/๋๋ฆผ ์์ ๊ตฌ๋ถ)
- ๊ธฐ๋ก ์ญ์ ๋ฒํผ (๋ณธ์ธ ์ด๋ฆ ํ์ธ ํ ์ญ์ )
- "์ด๋ฒ ์ฃผ MVP ๋ฌ๋" ์๋ ์ ์ (๊ฐ์ฅ ๋ง์ด ๋ด ์ฌ๋)
- ๋คํฌ ๋ชจ๋ ํ ๊ธ
๐ ๏ธ ๊ธฐ์ ์คํ
- Backend: Laravel 13.x + Blade
- Database: SQLite (๊ฐ๋จํ ๋ก์ปฌ DB)
- Frontend: Tailwind CSS (CDN ๋๋ Vite ํตํฉ)
- ๊ธฐํ: Carbon (๋ ์ง ๊ณ์ฐ์ฉ)
๐ ํ์ต ํฌ์ธํธ
- Laravel ๋ง์ด๊ทธ๋ ์ด์ ์ผ๋ก ํ ์ด๋ธ ์์ฑ
- Blade ํ ํ๋ฆฟ์ผ๋ก ํผ๊ณผ ๋ชฉ๋ก ๋ ๋๋ง
- Eloquent ORM์ผ๋ก ๋ฐ์ดํฐ ์กฐํ/์ ์ฅ
- Carbon์ผ๋ก "์ต๊ทผ 7์ผ" ํํฐ๋ง
- Tailwind CSS๋ก ๋น ๋ฅธ UI ๊ตฌ์ฑ
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x + Blade ํ ํ๋ฆฟ ์ฌ์ฉ (React/Vue ๋ฑ SPA ํ๋ ์์ํฌ ์ฌ์ฉ ๊ธ์ง)
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ (MySQL ์ค์ ๋ถํ์)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง (CDN ๋๋ Laravel Mix/Vite ํตํฉ)
- โข ํ์ด์ง๋ ๋จ์ผ ๋ผ์ฐํธ (/) ๋๋ /running-log๋ก ๊ตฌ์ฑ
- โข ๋ฌ๋ ๊ธฐ๋ก ํ ์ด๋ธ: runs (id, name, date, distance_km, pace_seconds, memo, created_at)
- โข ํ์ด์ค๋ '๋ถ:์ด' ํ์์ผ๋ก ์ ๋ ฅ๋ฐ์ ์ด(int)๋ก ์ ์ฅ ํ ํ์ ์ ๋ค์ '๋ถ:์ด'๋ก ๋ณํ
- โข ์ต๊ทผ 7์ผ ๊ธฐ๋ก๋ง ํ์ (Carbon::now()->subDays(7) ํ์ฉ)
- โข ํต๊ณ๋ Eloquent์ sum(), avg() ๋ฑ ์ง๊ณ ํจ์ ์ฌ์ฉ
- โข ํผ ๊ฒ์ฆ: ์ด๋ฆ(ํ์), ๋ ์ง(ํ์, date), ๊ฑฐ๋ฆฌ(ํ์, numeric, min:0.1), ํ์ด์ค(ํ์, ํ์: MM:SS)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir running-crew-weekly-log && cd running-crew-weekly-log
์ด ํด๋์์ Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
# Laravel ํ๋ก์ ํธ ์ด๊ธฐํ
composer create-project laravel/laravel:^13.0 .
ํ๋ก์ ํธ๊ฐ ์์ฑ๋๋ฉด ๋ค์์ ํ์ธํด์ฃผ์ธ์:
- Laravel ๋ฒ์ ์ด 13.x์ธ์ง ํ์ธ (`php artisan --version`)
- `.env` ํ์ผ์์ DB_CONNECTION=sqlite๋ก ์ค์
- `database/database.sqlite` ํ์ผ ์์ฑ (`touch database/database.sqlite`)
๊ทธ ๋ค์, ๋ฌ๋ ๊ธฐ๋ก ํ
์ด๋ธ์ ์ํ ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ์ ์์ฑํฉ๋๋ค.
```bash
php artisan make:migration create_runs_table
```
๋ง์ด๊ทธ๋ ์ด์
ํ์ผ(`database/migrations/xxxx_create_runs_table.php`)์ ๋ค์ ์คํค๋ง๋ฅผ ์์ฑํ์ธ์:
```php
Schema::create('runs', function (Blueprint $table) {
$table->id();
$table->string('name'); // ํฌ๋ฃจ์ ์ด๋ฆ
$table->date('date'); // ๋ฌ๋ ๋ ์ง
$table->decimal('distance_km', 5, 2); // ๊ฑฐ๋ฆฌ (์: 5.25km)
$table->integer('pace_seconds'); // ํ์ด์ค๋ฅผ ์ด ๋จ์๋ก ์ ์ฅ (์: 5๋ถ 30์ด = 330์ด)
$table->text('memo')->nullable(); // ๋ฉ๋ชจ
$table->timestamps();
});
```
๋ง์ด๊ทธ๋ ์ด์
์คํ:
```bash
php artisan migrate
```
๋ง์ง๋ง์ผ๋ก Run ๋ชจ๋ธ์ ์์ฑํฉ๋๋ค:
```bash
php artisan make:model Run
```
`app/Models/Run.php`์์ fillable ์์ฑ์ ์ถ๊ฐํ์ธ์:
```php
protected $fillable = ['name', 'date', 'distance_km', 'pace_seconds', 'memo'];
```
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์ด๊ธฐํ๋๊ณ , SQLite DB๊ฐ ์ค์ ๋๋ฉฐ, runs ํ ์ด๋ธ๊ณผ Run ๋ชจ๋ธ์ด ์์ฑ๋ฉ๋๋ค. `php artisan migrate` ์คํ ์ runs ํ ์ด๋ธ์ด database.sqlite์ ์์ฑ๋ฉ๋๋ค.
# Tailwind CSS ์ค์ ๋ฐ ๋ฉ์ธ ํ์ด์ง ๋ ์ด์์ ์์ฑ
Tailwind CSS๋ฅผ Laravel์ ํตํฉํฉ๋๋ค. Vite๋ฅผ ์ฌ์ฉํ์ฌ ๋น ๋ฅด๊ฒ ์ค์ ํ๊ฒ ์ต๋๋ค.
1. **Tailwind CSS ์ค์น**
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
2. **`tailwind.config.js` ์์ **
```js
export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
],
theme: {
extend: {
colors: {
'runner-green': '#10b981',
'runner-blue': '#3b82f6',
},
},
},
plugins: [],
}
```
3. **`resources/css/app.css`์ Tailwind ์ง์์ด ์ถ๊ฐ**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
4. **Vite ๋น๋**
```bash
npm install
npm run dev
```
(๊ฐ๋ฐ ์๋ฒ๋ฅผ ์คํํ ์ํ๋ก ๋์ธ์)
5. **๋ฉ์ธ ๋ ์ด์์ Blade ํ์ผ ์์ฑ**
`resources/views/layout.blade.php` ํ์ผ์ ๋ง๋ค๊ณ ๋ค์ ๋ด์ฉ์ ์์ฑํ์ธ์:
```blade
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>๐ ๋ฌ๋ ํฌ๋ฃจ ์ฃผ๊ฐ ๊ธฐ๋ก</title>
@vite('resources/css/app.css')
</head>
<body class="bg-gradient-to-br from-green-50 to-blue-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-runner-green mb-2">๐โโ๏ธ ๋ฌ๋ ํฌ๋ฃจ ์ฃผ๊ฐ ๊ธฐ๋ก</h1>
<p class="text-gray-600">์ด๋ฒ ์ฃผ ์ฐ๋ฆฌ ํฌ๋ฃจ์ ๋ฌ๋ ๊ธฐ๋ก์ ๋จ๊ฒจ๋ณด์ธ์!</p>
</header>
<main>
@yield('content')
</main>
<footer class="text-center mt-12 text-gray-500 text-sm">
<p>๐ช ํจ๊ป ๋ฌ๋ฆฌ๋ฉด ๋ ๋ฉ๋ฆฌ ๊ฐ๋๋ค</p>
</footer>
</div>
</body>
</html>
```
์ด์ ๊ธฐ๋ณธ ๋ ์ด์์์ด ์์ฑ๋์์ต๋๋ค. ๋ค์ ๋จ๊ณ์์ ๋ฌ๋ ๊ธฐ๋ก ํผ๊ณผ ๋ชฉ๋ก์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: Tailwind CSS๊ฐ Laravel ํ๋ก์ ํธ์ ํตํฉ๋๊ณ , Vite๋ก ๋น๋๋ฉ๋๋ค. `resources/views/layout.blade.php` ๋ ์ด์์ ํ์ผ์ด ์์ฑ๋์ด ๋ฌ๋ ํฌ๋ฃจ ํ ๋ง์ ํค๋์ ํธํฐ๊ฐ ํ์๋ฉ๋๋ค.
# ๋ฌ๋ ๊ธฐ๋ก ์
๋ ฅ ํผ ๋ฐ ๋ชฉ๋ก ํ์ด์ง ๊ตฌํ
์ด์ ๋ฉ์ธ ํ์ด์ง์ ๋ฌ๋ ๊ธฐ๋ก ํผ๊ณผ ์ต๊ทผ 7์ผ ๊ธฐ๋ก ๋ชฉ๋ก์ ๋ง๋ค๊ฒ ์ต๋๋ค.
1. **์ปจํธ๋กค๋ฌ ์์ฑ**
```bash
php artisan make:controller RunController
```
2. **`app/Http/Controllers/RunController.php` ์์ฑ**
```php
namespace App\Http\Controllers;
use App\Models\Run;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class RunController extends Controller
{
public function index()
{
// ์ต๊ทผ 7์ผ ๊ธฐ๋ก๋ง ๊ฐ์ ธ์ค๊ธฐ
$runs = Run::where('date', '>=', Carbon::now()->subDays(7))
->orderBy('date', 'desc')
->orderBy('created_at', 'desc')
->get();
// ์ฃผ๊ฐ ํต๊ณ
$totalDistance = $runs->sum('distance_km');
$avgPaceSeconds = $runs->avg('pace_seconds');
// TOP 3 ๋ฌ๋ (๊ฑฐ๋ฆฌ ํฉ์ฐ)
$topRunners = Run::where('date', '>=', Carbon::now()->subDays(7))
->selectRaw('name, SUM(distance_km) as total_distance')
->groupBy('name')
->orderBy('total_distance', 'desc')
->limit(3)
->get();
return view('runs.index', compact('runs', 'totalDistance', 'avgPaceSeconds', 'topRunners'));
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:50',
'date' => 'required|date',
'distance_km' => 'required|numeric|min:0.1',
'pace' => 'required|regex:/^\d{1,2}:\d{2}$/', // MM:SS ํ์
'memo' => 'nullable|string|max:200',
]);
// ํ์ด์ค๋ฅผ ์ด๋ก ๋ณํ (์: "5:30" -> 330์ด)
[$minutes, $seconds] = explode(':', $validated['pace']);
$paceSeconds = ($minutes * 60) + $seconds;
Run::create([
'name' => $validated['name'],
'date' => $validated['date'],
'distance_km' => $validated['distance_km'],
'pace_seconds' => $paceSeconds,
'memo' => $validated['memo'],
]);
return redirect()->route('runs.index')->with('success', '๋ฌ๋ ๊ธฐ๋ก์ด ์ถ๊ฐ๋์์ต๋๋ค! ๐');
}
}
```
3. **๋ผ์ฐํธ ์ค์ (`routes/web.php`)**
```php
use App\Http\Controllers\RunController;
Route::get('/', [RunController::class, 'index'])->name('runs.index');
Route::post('/runs', [RunController::class, 'store'])->name('runs.store');
```
4. **๋ทฐ ํ์ผ ์์ฑ (`resources/views/runs/index.blade.php`)**
```blade
@extends('layout')
@section('content')
{{-- ์ฑ๊ณต ๋ฉ์์ง --}}
@if(session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-6">
{{ session('success') }}
</div>
@endif
{{-- ๋ฌ๋ ๊ธฐ๋ก ์
๋ ฅ ํผ --}}
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-2xl font-bold text-runner-blue mb-4">๐ ์ค๋ ๋ฌ๋ ๊ธฐ๋กํ๊ธฐ</h2>
<form action="{{ route('runs.store') }}" method="POST" class="space-y-4">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">์ด๋ฆ</label>
<input type="text" name="name" required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-runner-green focus:border-runner-green"
placeholder="ํ๊ธธ๋">
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">๋ ์ง</label>
<input type="date" name="date" required value="{{ date('Y-m-d') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-runner-green focus:border-runner-green">
@error('date') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">๊ฑฐ๋ฆฌ (km)</label>
<input type="number" step="0.01" name="distance_km" required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-runner-green focus:border-runner-green"
placeholder="5.5">
@error('distance_km') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">ํ์ด์ค (๋ถ:์ด/km)</label>
<input type="text" name="pace" required pattern="\d{1,2}:\d{2}"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-runner-green focus:border-runner-green"
placeholder="5:30">
@error('pace') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">๋ฉ๋ชจ (์ ํ)</label>
<textarea name="memo" rows="2"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-runner-green focus:border-runner-green"
placeholder="์ค๋ ์ปจ๋์
์ด ์ข์์ด์!"></textarea>
</div>
<button type="submit"
class="w-full bg-runner-green hover:bg-green-600 text-white font-bold py-3 rounded-md transition">
๐ ๊ธฐ๋ก ์ถ๊ฐํ๊ธฐ
</button>
</form>
</div>
{{-- ์ฃผ๊ฐ ํต๊ณ --}}
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-2xl font-bold text-runner-blue mb-4">๐ ์ด๋ฒ ์ฃผ ํต๊ณ</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="text-center">
<p class="text-gray-600 text-sm">์ด ๊ฑฐ๋ฆฌ</p>
<p class="text-3xl font-bold text-runner-green">{{ number_format($totalDistance, 1) }} km</p>
</div>
<div class="text-center">
<p class="text-gray-600 text-sm">ํ๊ท ํ์ด์ค</p>
<p class="text-3xl font-bold text-runner-blue">
@if($avgPaceSeconds)
{{ floor($avgPaceSeconds / 60) }}:{{ str_pad($avgPaceSeconds % 60, 2, '0', STR_PAD_LEFT) }}
@else
-:--
@endif
</p>
</div>
<div class="text-center">
<p class="text-gray-600 text-sm">์ ์ฒด ๊ธฐ๋ก ์</p>
<p class="text-3xl font-bold text-gray-700">{{ $runs->count() }}๊ฐ</p>
</div>
</div>
{{-- TOP 3 ๋ฌ๋ --}}
@if($topRunners->count() > 0)
<div class="mt-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3">๐ ์ด๋ฒ ์ฃผ TOP ๋ฌ๋</h3>
<div class="space-y-2">
@foreach($topRunners as $index => $runner)
<div class="flex items-center justify-between bg-gradient-to-r from-yellow-50 to-orange-50 px-4 py-2 rounded">
<span class="font-medium">
@if($index === 0) ๐ฅ
@elseif($index === 1) ๐ฅ
@else ๐ฅ
@endif
{{ $runner->name }}
</span>
<span class="text-runner-green font-bold">{{ number_format($runner->total_distance, 1) }} km</span>
</div>
@endforeach
</div>
</div>
@endif
</div>
{{-- ๋ฌ๋ ๊ธฐ๋ก ๋ชฉ๋ก --}}
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-runner-blue mb-4">๐
์ต๊ทผ 7์ผ ๊ธฐ๋ก</h2>
@if($runs->count() > 0)
<div class="space-y-3">
@foreach($runs as $run)
<div class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition">
<div class="flex justify-between items-start">
<div>
<p class="font-bold text-lg text-gray-800">{{ $run->name }}</p>
<p class="text-sm text-gray-500">{{ Carbon\Carbon::parse($run->date)->format('Y๋
m์ d์ผ (D)') }}</p>
</div>
<div class="text-right">
<p class="text-2xl font-bold text-runner-green">{{ number_format($run->distance_km, 1) }} km</p>
<p class="text-sm text-gray-600">
โฑ๏ธ {{ floor($run->pace_seconds / 60) }}:{{ str_pad($run->pace_seconds % 60, 2, '0', STR_PAD_LEFT) }}/km
</p>
</div>
</div>
@if($run->memo)
<p class="mt-2 text-gray-600 text-sm">๐ฌ {{ $run->memo }}</p>
@endif
</div>
@endforeach
</div>
@else
<p class="text-gray-500 text-center py-8">์์ง ์ด๋ฒ ์ฃผ ๋ฌ๋ ๊ธฐ๋ก์ด ์์ต๋๋ค. ์ฒซ ๊ธฐ๋ก์ ๋จ๊ฒจ๋ณด์ธ์! ๐โโ๏ธ</p>
@endif
</div>
@endsection
```
์ด์ `php artisan serve`๋ก ์๋ฒ๋ฅผ ์คํํ๊ณ ๋ธ๋ผ์ฐ์ ์์ ํ์ธํด๋ณด์ธ์!
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ฌ๋ ๊ธฐ๋ก ์ ๋ ฅ ํผ์ด ํ์๋๊ณ , ํผ์ ์ ์ถํ๋ฉด ๋ฐ์ดํฐ๊ฐ SQLite DB์ ์ ์ฅ๋ฉ๋๋ค. ์ต๊ทผ 7์ผ ๊ธฐ๋ก์ด ์นด๋ ํํ๋ก ํ์๋๋ฉฐ, ์ฃผ๊ฐ ํต๊ณ(์ด ๊ฑฐ๋ฆฌ, ํ๊ท ํ์ด์ค, TOP 3 ๋ฌ๋)๊ฐ ๊ณ์ฐ๋์ด ๋ณด์ฌ์ง๋๋ค. Tailwind CSS๋ก ์คํ์ผ๋ง๋ ๋ฐ์ํ UI๊ฐ ์๋ํฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!