๐ฒ ๋๋ค ๋ฏธ์ ์์ฑ๊ธฐ - ์ค๋์ ์ฌ๋ฏธ์๋ ๋์ ๊ณผ์
random-mission-generator
๋ฌธ์ ์ค๋ช
๐ฏ ํ๋ก์ ํธ ๊ฐ์
๋งค์ผ ์ง๋ฃจํ ์ผ์์ ํ๋ ฅ์ ๋ถ์ด๋ฃ์ด์ค ๋๋ค ๋ฏธ์ ์์ฑ๊ธฐ๋ฅผ ๋ง๋ญ๋๋ค. ๋ฒํผ ํ๋๋ก ์ฌ๋ฏธ์๋ ๋์ ๊ณผ์ ๋ฅผ ๋ฐ๊ณ , ์๋ฃํ ๋ฏธ์ ์ ๊ธฐ๋กํ๋ฉฐ ์ฑ์ทจ๊ฐ์ ๋๋ ์ ์๋ ์ธํฐ๋ํฐ๋ธ ํ์ด์ง์ ๋๋ค.
์ฌ์ฉ์๋ "๋ฏธ์ ๋ฝ๊ธฐ" ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ฌด์์๋ก ๋ฏธ์ ์ด ์ ํ๋๊ณ , ์๋ฃ ํ ์ฒดํฌ ํ์๋ฅผ ๋จ๊ธธ ์ ์์ต๋๋ค. ๊ฐ๋จํ์ง๋ง ์ค๋ ์ฑ ์๋ gamification ์์๋ก ์ผ์์ ๋ ์ฆ๊ฒ๊ฒ ๋ง๋ค์ด๋ณด์ธ์!
โจ ํ์ ๊ธฐ๋ฅ
-
๋๋ค ๋ฏธ์ ์์ฑ
- ๋ฏธ๋ฆฌ ์ ์๋ ๋ฏธ์ ํ์์ ๋ฌด์์ ์ ํ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๋ฏธ์ (์ด๋, ํ์ต, ์ทจ๋ฏธ, ์น๊ตฌ/๊ฐ์กฑ, ์๊ธฐ๊ณ๋ฐ ๋ฑ)
- ์ ๋๋ฉ์ด์ ๊ณผ ํจ๊ป ๋ฏธ์ ์นด๋ ๋ฑ์ฅ
-
๋ฏธ์ ์๋ฃ ๊ธฐ๋ก
- ์๋ฃํ ๋ฏธ์ ์ ์ฒดํฌ ํ์
- SQLite DB์ ์๋ฃ ๋ ์ง/์๊ฐ ์ ์ฅ
- ์ค๋ ์๋ฃํ ๋ฏธ์ ๊ฐ์ ํ์
-
๋ฏธ์ ํ์คํ ๋ฆฌ
- ์ต๊ทผ ์๋ฃํ ๋ฏธ์ ๋ชฉ๋ก (์ต๋ 10๊ฐ)
- ๋ ์ง๋ณ ์๋ฃ ๋ฏธ์ ํ์ธ
- ์ญ์ ๊ธฐ๋ฅ (์ค์๋ก ์ฒดํฌํ ๊ฒฝ์ฐ)
-
UI/UX
- modern shell ์ ์ฉ (nav/footer/๋คํฌ๋ชจ๋)
- ๋์์ธ ํ ํฐ ์ฌ์ฉ (๋คํฌ๋ชจ๋ ์๋ ๋์)
- ๋ถ๋๋ฌ์ด ํ์ด๋์ธ/์ฌ๋ผ์ด๋ ์ ๋๋ฉ์ด์
- ๋ฏธ์ ์นด๋ ํ๋ฆฝ ํจ๊ณผ
๐ ๋ณด๋์ค ๊ธฐ๋ฅ
- ๋ฏธ์ ๋์ด๋ ํ์ (โญ ์ฌ์ / โญโญ ๋ณดํต / โญโญโญ ์ด๋ ค์)
- ์ฐ์ ๋ฌ์ฑ ์ผ์ ์นด์ดํฐ (streak ์์คํ )
- ๋ฏธ์ ์นดํ ๊ณ ๋ฆฌ ํํฐ (ํน์ ์นดํ ๊ณ ๋ฆฌ๋ง ๋ฝ๊ธฐ)
- ์ปค์คํ ๋ฏธ์ ์ถ๊ฐ ๊ธฐ๋ฅ
- ์๋ฃ ๋ฏธ์ ํต๊ณ ์ฐจํธ (์นดํ ๊ณ ๋ฆฌ๋ณ ์๋ฃ์จ)
- ๊ณต์ ํ๊ธฐ ๋ฒํผ (SNS๋ก ์ค๋์ ๋ฏธ์ ์๋ํ๊ธฐ)
๐ ๊ธฐ์ ์คํ
- ํ๋ ์์ํฌ: ๊ทธ๋๋ณด๋5 SE (g5se)
- ์ธ์ด: PHP 8.x
- ๋ฐ์ดํฐ๋ฒ ์ด์ค: SQLite (๋ชจ๋ ์ ์ฉ DB)
- ํ๋ก ํธ์๋: HTML5, CSS3 (๋์์ธ ํ ํฐ), Vanilla JavaScript
- ๋์์ธ: modern shell (nav/footer/๋คํฌ๋ชจ๋ ํ ๊ธ)
๐ ๊ตฌํ ์ธ๋ถ์ฌํญ
- ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ
:
app/modules/random-mission-generator/index.phpโ/random-mission-generator - SQLite DB:
G5_DATA_PATH.'/modules/random-mission-generator.sqlite'์์น์ ์๋ ์์ฑ - ํ
์ด๋ธ ์คํค๋ง:
missions: id, category, title, difficulty, created_atcompleted: id, mission_id, completed_at
- AJAX๋ก ๋ฏธ์ ๋ฝ๊ธฐ/์๋ฃ ์ฒ๋ฆฌ (ํ์ด์ง ์๋ก๊ณ ์นจ ์์ด)
- prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
htmlspecialchars()๋ก XSS ๋ฐฉ์ง
์ ์ฝ ์กฐ๊ฑด
- โข ๊ทธ๋๋ณด๋5 SE(g5se) ํ๋ ์์ํฌ ์ฌ์ฉ (Laravel/Composer/npm/Vite ์ ๋ ๊ธ์ง)
- โข ์์ PHP + g5se ์ปจ๋ฒค์ ๋ง ์ฌ์ฉ
- โข ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ (app/modules/{repo_name}/index.php)
- โข modern shell ํ์ ์ ์ฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php)
- โข ๋์์ธ ํ ํฐ ์ฌ์ฉ (var(--m-surface), var(--m-text) ๋ฑ)
- โข ๋ชจ๋ ์ ์ฉ SQLite DB (G5_DATA_PATH.'/modules/random-mission-generator.sqlite')
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
- โข htmlspecialchars()๋ก ์ถ๋ ฅ ์ด์ค์ผ์ดํ
- โข ์ธ๋ถ ๋น๋ ๋๊ตฌ ์ฌ์ฉ ๊ธ์ง (์์ HTML/CSS/JS)
- โข CDN ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ์ง์ (Vanilla JS๋ก ๊ตฌํ)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
## Step 1: ํ๋ก์ ํธ ๊ธฐ๋ณธ ๊ณจ๊ฒฉ ์์ฑ
`app/modules/random-mission-generator/index.php` ํ์ผ์ ์์ฑํ๊ณ modern shell ๊ณจ๊ฒฉ์ ์ ์ฉํด์ฃผ์ธ์.
### ์๊ตฌ์ฌํญ:
1. **ํ์ผ ๊ตฌ์กฐ**:
- `app/modules/random-mission-generator/index.php` ์์ฑ
- g5se ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ
์ผ๋ก `/random-mission-generator` URL์์ ์ ๊ทผ ๊ฐ๋ฅ
2. **modern shell ๊ณจ๊ฒฉ ์ ์ฉ**:
```php
<?php
include_once('./_common.php');
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 32px 20px 48px;">
<!-- ํ์ด์ง ๋ณธ๋ฌธ -->
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
3. **ํ์ด์ง ํค๋ ์น์
**:
- ์ ๋ชฉ: "๐ฒ ๋๋ค ๋ฏธ์
์์ฑ๊ธฐ"
- ๋ถ์ : "์ค๋์ ์ฌ๋ฏธ์๋ ๋์ ๊ณผ์ ๋ฅผ ๋ฐ์๋ณด์ธ์!"
- ๊ฐ๋จํ ์ค๋ช
๋ฌธ๊ตฌ
4. **๋ฏธ์
์นด๋ ์์ญ**:
- ์ด๊ธฐ ์ํ: "๋ฏธ์
๋ฝ๊ธฐ" ๋ฒํผ๋ง ํ์
- ์นด๋ ์คํ์ผ: `background: var(--m-surface); box-shadow: var(--m-shadow); border-radius: var(--m-radius-lg);`
- ์ค์ ์ ๋ ฌ ๋ ์ด์์
5. **์๋ฃ ๋ฏธ์
ํ์คํ ๋ฆฌ ์น์
**:
- ์ ๋ชฉ: "์ค๋ ์๋ฃํ ๋ฏธ์
"
- ๋น ์ํ ๋ฉ์์ง: "์์ง ์๋ฃํ ๋ฏธ์
์ด ์์ต๋๋ค."
### ๋์์ธ ๊ฐ์ด๋:
- ๋ชจ๋ ํ
์คํธ๋ `color: var(--m-text);` ์ฌ์ฉ
- ํ๋ฆฐ ํ
์คํธ๋ `color: var(--m-text-muted);`
- ๋ฒํผ์ `background: #6366f1; color: white;` (primary ์์)
- ํ์ด์ง ์ต๋ ๋๋น: 800px, ์ค์ ์ ๋ ฌ
- ์ฌ๋ฐฑ๊ณผ ๊ฐ๊ฒฉ์ 16px ๋จ์๋ก ํต์ผ
### ์ฐธ๊ณ :
- `_common.php`, `head.sub.php`, `tail.sub.php`๋ `app/` ๋ฃจํธ์ ์์ผ๋ฏ๋ก `./`๋ก ์ธํด๋ฃจ๋
- `G5_THEME_PATH`๋ `_common.php` ์ธํด๋ฃจ๋ ์์ ์ ์ ์๋จ
- ์์ง DB๋ JavaScript ๊ธฐ๋ฅ์ ๊ตฌํํ์ง ์์ (์ ์ HTML๋ง)
๊ธฐ๋ ๊ฒฐ๊ณผ: modern shell์ด ์ ์ฉ๋ ๊ธฐ๋ณธ ํ์ด์ง๊ฐ ์์ฑ๋๊ณ `/random-mission-generator` URL๋ก ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค. ํค๋, ๋ฏธ์ ์นด๋ ์์ญ, ํ์คํ ๋ฆฌ ์น์ ์ด ์๊ฐ์ ์ผ๋ก ๊ตฌ๋ถ๋์ด ํ์๋๋ฉฐ, ๋คํฌ๋ชจ๋ ํ ๊ธ ์ ์์์ด ์๋์ผ๋ก ์ ํ๋ฉ๋๋ค.
## Step 2: SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ๋ฐ ๋ฏธ์
๋ฐ์ดํฐ ์ด๊ธฐํ
๋ชจ๋ ์ ์ฉ SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ฑํ๊ณ ๋ฏธ์
๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ํ
์ด๋ธ์ ๋ง๋ค์ด์ฃผ์ธ์.
### ์๊ตฌ์ฌํญ:
1. **SQLite DB ์ฐ๊ฒฐ ํจ์**:
- `app/modules/random-mission-generator/index.php` ์๋จ์ DB ์ฐ๊ฒฐ ๋ก์ง ์ถ๊ฐ
- DB ํ์ผ ์์น: `G5_DATA_PATH.'/modules/random-mission-generator.sqlite'`
- `G5_DATA_PATH.'/modules'` ๋๋ ํฐ๋ฆฌ๊ฐ ์์ผ๋ฉด ์๋ ์์ฑ (`@mkdir(..., 0707, true)`)
- PDO๋ก ์ฐ๊ฒฐ: `new PDO('sqlite:'.G5_DATA_PATH.'/modules/random-mission-generator.sqlite')`
2. **ํ
์ด๋ธ ์คํค๋ง (์๋ ์์ฑ)**:
```sql
CREATE TABLE IF NOT EXISTS missions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
title TEXT NOT NULL,
difficulty INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS completed (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mission_id INTEGER NOT NULL,
completed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (mission_id) REFERENCES missions(id)
);
```
3. **์ด๊ธฐ ๋ฏธ์
๋ฐ์ดํฐ ์ฝ์
**:
- missions ํ
์ด๋ธ์ด ๋น์ด์์ผ๋ฉด ์ํ ๋ฏธ์
20๊ฐ ์ด์ ์ฝ์
- ์นดํ
๊ณ ๋ฆฌ: ์ด๋, ํ์ต, ์ทจ๋ฏธ, ์น๊ตฌ/๊ฐ์กฑ, ์๊ธฐ๊ณ๋ฐ
- difficulty: 1(์ฌ์), 2(๋ณดํต), 3(์ด๋ ค์)
- ์์ ๋ฏธ์
:
- (์ด๋, "10๋ถ ์คํธ๋ ์นญํ๊ธฐ", 1)
- (ํ์ต, "์๋ก์ด ์๋จ์ด 5๊ฐ ์ธ์ฐ๊ธฐ", 1)
- (์ทจ๋ฏธ, "์ข์ํ๋ ๋
ธ๋ ํ ๊ณก ๋ฃ๊ธฐ", 1)
- (์น๊ตฌ/๊ฐ์กฑ, "๊ฐ์กฑ์๊ฒ ๊ฐ์ฌ ๋ฉ์์ง ๋ณด๋ด๊ธฐ", 2)
- (์๊ธฐ๊ณ๋ฐ, "์ค๋ ํ๋ฃจ ๊ฐ์ฌํ ์ผ 3๊ฐ์ง ์ ๊ธฐ", 2)
- (์ด๋, "30๋ถ ์ฐ์ฑ
ํ๊ธฐ", 2)
- (ํ์ต, "์ฑ
ํ ์ฑํฐ ์ฝ๊ธฐ", 2)
- (์ทจ๋ฏธ, "์๋ก์ด ๋ ์ํผ ์๋ํด๋ณด๊ธฐ", 3)
- (์น๊ตฌ/๊ฐ์กฑ, "์ค๋๋ง์ ์น๊ตฌ์๊ฒ ์ ํํ๊ธฐ", 2)
- (์๊ธฐ๊ณ๋ฐ, "๋ช
์ 15๋ถ ํ๊ธฐ", 2)
4. **DB ์ฐ๊ฒฐ ํ์ธ**:
- ํ์ด์ง ๋ก๋ ์ DB ์ฐ๊ฒฐ ํ
์คํธ
- ์๋ฌ ๋ฐ์ ์ ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง ํ์
### ๊ตฌํ ํ:
- prepared statement ์ฌ์ฉ: `$stmt = $pdo->prepare("INSERT INTO ..."); $stmt->execute([...]);`
- ์ด๊ธฐ ๋ฐ์ดํฐ ์ฝ์
์ `SELECT COUNT(*) FROM missions`๋ก ์ฒดํฌ ํ 0์ด๋ฉด ์คํ
- DB ์ฐ๊ฒฐ ๊ฐ์ฒด๋ `$pdo` ๋ณ์์ ์ ์ฅํ์ฌ ์ฌ์ฌ์ฉ
### ๋ณด์:
- SQLite ํ์ผ์ ์น ๋ฃจํธ ๋ฐ (`G5_DATA_PATH`) ์์น
- ๋ชจ๋ ์ฟผ๋ฆฌ๋ prepared statement ์ฌ์ฉ
- ์๋ฌ ๋ฉ์์ง์ ๋ฏผ๊ฐํ ์ ๋ณด(๊ฒฝ๋ก ๋ฑ) ๋
ธ์ถ ๊ธ์ง
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ์ด์ง ๋ก๋ ์ SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์๋์ผ๋ก ์์ฑ๋๊ณ , missions ํ ์ด๋ธ์ 20๊ฐ ์ด์์ ์ํ ๋ฏธ์ ์ด ์ฝ์ ๋ฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ด ์ ์์ ์ผ๋ก ์๋ํ๋ฉฐ, ์๋ฌ ๋ฐ์ ์ ์ ์ ํ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
## Step 3: ๋๋ค ๋ฏธ์
๋ฝ๊ธฐ ๊ธฐ๋ฅ ๊ตฌํ (AJAX)
"๋ฏธ์
๋ฝ๊ธฐ" ๋ฒํผ ํด๋ฆญ ์ DB์์ ๋ฌด์์ ๋ฏธ์
์ ๊ฐ์ ธ์ ์นด๋ ํํ๋ก ํ์ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ฃผ์ธ์.
### ์๊ตฌ์ฌํญ:
1. **AJAX ์๋ํฌ์ธํธ**:
- `app/modules/random-mission-generator/index.php` ์๋จ์ AJAX ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
- `$_GET['action'] === 'get_mission'` ๋ถ๊ธฐ ์ฒ๋ฆฌ
- DB์์ ๋๋ค ๋ฏธ์
1๊ฐ ์กฐํ: `SELECT * FROM missions ORDER BY RANDOM() LIMIT 1`
- JSON ์๋ต: `{"id": 1, "category": "์ด๋", "title": "10๋ถ ์คํธ๋ ์นญํ๊ธฐ", "difficulty": 1}`
- ์๋ต ํ `exit;`๋ก HTML ๋ ๋๋ง ๋ฐฉ์ง
2. **JavaScript ๊ตฌํ**:
- "๋ฏธ์
๋ฝ๊ธฐ" ๋ฒํผ์ `onclick` ์ด๋ฒคํธ ์ถ๊ฐ
- `fetch('/random-mission-generator?action=get_mission')`๋ก AJAX ์์ฒญ
- ์๋ต ๋ฐ์ ๋ฏธ์
๋ฐ์ดํฐ๋ฅผ ์นด๋์ ํ์
- ๋ฏธ์
์นด๋ HTML ๊ตฌ์กฐ:
```html
<div class="mission-card">
<div class="mission-category">{์นดํ
๊ณ ๋ฆฌ}</div>
<h2 class="mission-title">{๋ฏธ์
์ ๋ชฉ}</h2>
<div class="mission-difficulty">{๋ณํ ๊ฐ์}</div>
<button class="complete-btn">์๋ฃ ์ฒดํฌ</button>
</div>
```
3. **์ ๋๋ฉ์ด์
ํจ๊ณผ**:
- ๋ฏธ์
์นด๋ ๋ฑ์ฅ ์ ํ์ด๋์ธ + ์ฌ๋ผ์ด๋์
ํจ๊ณผ
- CSS transition ์ฌ์ฉ:
```css
.mission-card {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.3s, transform 0.3s;
}
.mission-card.show {
opacity: 1;
transform: translateY(0);
}
```
- JavaScript๋ก `.show` ํด๋์ค ํ ๊ธ
4. **๋์ด๋ ํ์**:
- difficulty ๊ฐ์ ๋ฐ๋ผ ๋ณํ(โญ) ๊ฐ์ ํ์
- 1: โญ ์ฌ์
- 2: โญโญ ๋ณดํต
- 3: โญโญโญ ์ด๋ ค์
5. **์นดํ
๊ณ ๋ฆฌ ์์ ์ฝ๋ฉ**:
- ์ด๋: `#ef4444` (๋นจ๊ฐ)
- ํ์ต: `#3b82f6` (ํ๋)
- ์ทจ๋ฏธ: `#8b5cf6` (๋ณด๋ผ)
- ์น๊ตฌ/๊ฐ์กฑ: `#ec4899` (ํํฌ)
- ์๊ธฐ๊ณ๋ฐ: `#10b981` (์ด๋ก)
### ์คํ์ผ ๊ฐ์ด๋:
- ๋ฏธ์
์นด๋: `max-width: 500px; margin: 0 auto; padding: 32px;`
- ์นดํ
๊ณ ๋ฆฌ ๋ฐฐ์ง: `display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 14px;`
- ๋ฏธ์
์ ๋ชฉ: `font-size: 24px; font-weight: 600; margin: 16px 0;`
- ์๋ฃ ๋ฒํผ: `width: 100%; padding: 12px; background: #10b981; color: white; border-radius: 8px;`
### ์๋ฌ ์ฒ๋ฆฌ:
- DB ์กฐํ ์คํจ ์ `{"error": "๋ฏธ์
์ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค."}` ์๋ต
- JavaScript์์ ์๋ฌ ๋ฉ์์ง alert ํ์
๊ธฐ๋ ๊ฒฐ๊ณผ: "๋ฏธ์ ๋ฝ๊ธฐ" ๋ฒํผ ํด๋ฆญ ์ ํ์ด์ง ์๋ก๊ณ ์นจ ์์ด ๋ฌด์์ ๋ฏธ์ ์ด ์นด๋ ํํ๋ก ๋ถ๋๋ฝ๊ฒ ๋ฑ์ฅํฉ๋๋ค. ์นดํ ๊ณ ๋ฆฌ๋ณ ์์๊ณผ ๋์ด๋ ๋ณํ๊ฐ ํ์๋๋ฉฐ, "์๋ฃ ์ฒดํฌ" ๋ฒํผ์ด ํ์ฑํ๋ฉ๋๋ค. AJAX ํต์ ์ด ์ ์์ ์ผ๋ก ์๋ํ๊ณ , ์๋ฌ ๋ฐ์ ์ ์ ์ ํ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
## Step 4: ๋ฏธ์
์๋ฃ ์ฒดํฌ ๋ฐ ํ์คํ ๋ฆฌ ๊ธฐ๋ฅ ๊ตฌํ
๋ฏธ์
์๋ฃ ๋ฒํผ ํด๋ฆญ ์ DB์ ๊ธฐ๋กํ๊ณ , ์ค๋ ์๋ฃํ ๋ฏธ์
๋ชฉ๋ก์ ํ์ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ฃผ์ธ์.
### ์๊ตฌ์ฌํญ:
1. **์๋ฃ ์ฒดํฌ AJAX ์๋ํฌ์ธํธ**:
- `$_POST['action'] === 'complete_mission'` ๋ถ๊ธฐ ์ฒ๋ฆฌ
- `$_POST['mission_id']` ๋ฐ์์ completed ํ
์ด๋ธ์ ์ฝ์
- prepared statement ์ฌ์ฉ: `INSERT INTO completed (mission_id) VALUES (?)`
- JSON ์๋ต: `{"success": true, "count": 3}` (์ค๋ ์๋ฃํ ๋ฏธ์
๊ฐ์ ํฌํจ)
2. **์ค๋ ์๋ฃํ ๋ฏธ์
์กฐํ**:
- `$_GET['action'] === 'get_today_completed'` ์๋ํฌ์ธํธ ์ถ๊ฐ
- SQL ์ฟผ๋ฆฌ:
```sql
SELECT m.*, c.completed_at
FROM completed c
JOIN missions m ON c.mission_id = m.id
WHERE DATE(c.completed_at) = DATE('now')
ORDER BY c.completed_at DESC
```
- JSON ๋ฐฐ์ด ์๋ต: `[{"id": 1, "title": "...", "category": "...", "completed_at": "2024-01-15 14:30:00"}, ...]`
3. **JavaScript ์๋ฃ ์ฒ๋ฆฌ**:
- "์๋ฃ ์ฒดํฌ" ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ
- `fetch('/random-mission-generator', {method: 'POST', body: ...})`
- ์ฑ๊ณต ์:
- ๋ฏธ์
์นด๋์ ์๋ฃ ํ์ (์ฒดํฌ ์์ด์ฝ + "์๋ฃ!" ๋ฉ์์ง)
- 1์ด ํ ํ์คํ ๋ฆฌ ๋ชฉ๋ก ๊ฐฑ์
- 2์ด ํ ๋ฏธ์
์นด๋ ์จ๊ธฐ๊ณ "๋ค์ ๋ฏธ์
๋ฝ๊ธฐ" ๋ฒํผ ํ์
4. **ํ์คํ ๋ฆฌ ๋ชฉ๋ก ๋ ๋๋ง**:
- ํ์ด์ง ๋ก๋ ์ ์ค๋ ์๋ฃํ ๋ฏธ์
์๋ ๋ก๋
- ๊ฐ ๋ฏธ์
ํญ๋ชฉ:
```html
<div class="history-item">
<span class="category-badge">{์นดํ
๊ณ ๋ฆฌ}</span>
<span class="mission-text">{๋ฏธ์
์ ๋ชฉ}</span>
<span class="time-text">{์๋ฃ ์๊ฐ}</span>
<button class="delete-btn">โ</button>
</div>
```
- ์๋ฃ ์๊ฐ์ "์คํ 2:30" ํ์์ผ๋ก ํ์ (JavaScript `toLocaleTimeString()`)
5. **์ค๋ ์๋ฃ ๊ฐ์ ํ์**:
- ํ์คํ ๋ฆฌ ์น์
์๋จ์ "์ค๋ ์๋ฃํ ๋ฏธ์
: {count}๊ฐ" ํ์
- 0๊ฐ์ผ ๋: "์์ง ์๋ฃํ ๋ฏธ์
์ด ์์ต๋๋ค. ์ฒซ ๋ฏธ์
์ ์์ํด๋ณด์ธ์!"
6. **์๋ฃ ์ญ์ ๊ธฐ๋ฅ (๋ณด๋์ค)**:
- `$_POST['action'] === 'delete_completed'` ์๋ํฌ์ธํธ
- `$_POST['completed_id']` ๋ฐ์์ ํด๋น ๋ ์ฝ๋ ์ญ์
- ์ญ์ ๋ฒํผ ํด๋ฆญ ์ ํ์ธ ๋ค์ด์ผ๋ก๊ทธ ํ์
### ์คํ์ผ ๊ฐ์ด๋:
- ํ์คํ ๋ฆฌ ํญ๋ชฉ: `display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid var(--m-border);`
- ์๋ฃ ํ์: ์ด๋ก์ ์ฒดํฌ ์์ด์ฝ + "์๋ฃ!" ํ
์คํธ (์ ๋๋ฉ์ด์
)
- ์ญ์ ๋ฒํผ: `color: var(--m-text-muted); opacity: 0.5;` (hover ์ `opacity: 1`)
### ์๋ฌ ์ฒ๋ฆฌ:
- mission_id๊ฐ ์๊ฑฐ๋ ์ ํจํ์ง ์์ผ๋ฉด ์๋ฌ ์๋ต
- DB ์ฝ์
์คํจ ์ ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง
- completed_id ์ญ์ ๊ถํ ์ฒดํฌ (์ค๋ ์๋ฃํ ๊ฒ๋ง ์ญ์ ๊ฐ๋ฅ)
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ฏธ์ ์๋ฃ ๋ฒํผ ํด๋ฆญ ์ DB์ ๊ธฐ๋ก๋๊ณ , ์๋ฃ ์ ๋๋ฉ์ด์ ์ด ํ์๋ ํ ํ์คํ ๋ฆฌ ๋ชฉ๋ก์ด ์๋์ผ๋ก ๊ฐฑ์ ๋ฉ๋๋ค. ์ค๋ ์๋ฃํ ๋ฏธ์ ๊ฐ์๊ฐ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋๋ฉฐ, ๊ฐ ๋ฏธ์ ์ ์๊ฐ ์์ผ๋ก ์ ๋ ฌ๋์ด ํ์๋ฉ๋๋ค. ์ญ์ ๋ฒํผ์ผ๋ก ์ค์๋ก ์ฒดํฌํ ๋ฏธ์ ์ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!