๐ŸŽฒ ๋žœ๋ค ๋ฏธ์…˜ ์ƒ์„ฑ๊ธฐ - ์˜ค๋Š˜์˜ ์žฌ๋ฏธ์žˆ๋Š” ๋„์ „ ๊ณผ์ œ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ž…๋ฌธ PHP g5se AI: Claude

๐ŸŽฒ ๋žœ๋ค ๋ฏธ์…˜ ์ƒ์„ฑ๊ธฐ - ์˜ค๋Š˜์˜ ์žฌ๋ฏธ์žˆ๋Š” ๋„์ „ ๊ณผ์ œ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… random-mission-generator
์กฐํšŒ์ˆ˜ 171 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

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

๋งค์ผ ์ง€๋ฃจํ•œ ์ผ์ƒ์— ํ™œ๋ ฅ์„ ๋ถˆ์–ด๋„ฃ์–ด์ค„ ๋žœ๋ค ๋ฏธ์…˜ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ฒ„ํŠผ ํ•˜๋‚˜๋กœ ์žฌ๋ฏธ์žˆ๋Š” ๋„์ „ ๊ณผ์ œ๋ฅผ ๋ฐ›๊ณ , ์™„๋ฃŒํ•œ ๋ฏธ์…˜์„ ๊ธฐ๋กํ•˜๋ฉฐ ์„ฑ์ทจ๊ฐ์„ ๋А๋‚„ ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๋Š” "๋ฏธ์…˜ ๋ฝ‘๊ธฐ" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ฌด์ž‘์œ„๋กœ ๋ฏธ์…˜์ด ์„ ํƒ๋˜๊ณ , ์™„๋ฃŒ ํ›„ ์ฒดํฌ ํ‘œ์‹œ๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ค‘๋…์„ฑ ์žˆ๋Š” gamification ์š”์†Œ๋กœ ์ผ์ƒ์„ ๋” ์ฆ๊ฒ๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”!

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

  1. ๋žœ๋ค ๋ฏธ์…˜ ์ƒ์„ฑ

    • ๋ฏธ๋ฆฌ ์ •์˜๋œ ๋ฏธ์…˜ ํ’€์—์„œ ๋ฌด์ž‘์œ„ ์„ ํƒ
    • ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฏธ์…˜ (์šด๋™, ํ•™์Šต, ์ทจ๋ฏธ, ์นœ๊ตฌ/๊ฐ€์กฑ, ์ž๊ธฐ๊ณ„๋ฐœ ๋“ฑ)
    • ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๋ฏธ์…˜ ์นด๋“œ ๋“ฑ์žฅ
  2. ๋ฏธ์…˜ ์™„๋ฃŒ ๊ธฐ๋ก

    • ์™„๋ฃŒํ•œ ๋ฏธ์…˜์— ์ฒดํฌ ํ‘œ์‹œ
    • SQLite DB์— ์™„๋ฃŒ ๋‚ ์งœ/์‹œ๊ฐ„ ์ €์žฅ
    • ์˜ค๋Š˜ ์™„๋ฃŒํ•œ ๋ฏธ์…˜ ๊ฐœ์ˆ˜ ํ‘œ์‹œ
  3. ๋ฏธ์…˜ ํžˆ์Šคํ† ๋ฆฌ

    • ์ตœ๊ทผ ์™„๋ฃŒํ•œ ๋ฏธ์…˜ ๋ชฉ๋ก (์ตœ๋Œ€ 10๊ฐœ)
    • ๋‚ ์งœ๋ณ„ ์™„๋ฃŒ ๋ฏธ์…˜ ํ™•์ธ
    • ์‚ญ์ œ ๊ธฐ๋Šฅ (์‹ค์ˆ˜๋กœ ์ฒดํฌํ•œ ๊ฒฝ์šฐ)
  4. 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_at
    • completed: 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์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
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๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ—ค๋”, ๋ฏธ์…˜ ์นด๋“œ ์˜์—ญ, ํžˆ์Šคํ† ๋ฆฌ ์„น์…˜์ด ์‹œ๊ฐ์ ์œผ๋กœ ๊ตฌ๋ถ„๋˜์–ด ํ‘œ์‹œ๋˜๋ฉฐ, ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€ ์‹œ ์ƒ‰์ƒ์ด ์ž๋™์œผ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.

2
Step 2
## 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๊ฐœ ์ด์ƒ์˜ ์ƒ˜ํ”Œ ๋ฏธ์…˜์ด ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋ฉฐ, ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ ์ ˆํ•œ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

3
Step 3
## 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 ํ†ต์‹ ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๊ณ , ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ ์ ˆํ•œ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
## 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)

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

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

๋Œ“๊ธ€ (0)

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