๐ŸŽฏ ์˜ค๋Š˜์˜ ์šด๋™ ์ฑŒ๋ฆฐ์ง€ ์ถ”์ฒœ๊ธฐ - AI๊ฐ€ ๊ณจ๋ผ์ฃผ๋Š” ํ™ˆํŠธ ๋ฏธ์…˜ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ž…๋ฌธ PHP g5se AI: Claude

๐ŸŽฏ ์˜ค๋Š˜์˜ ์šด๋™ ์ฑŒ๋ฆฐ์ง€ ์ถ”์ฒœ๊ธฐ - AI๊ฐ€ ๊ณจ๋ผ์ฃผ๋Š” ํ™ˆํŠธ ๋ฏธ์…˜

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… daily-workout-challenge-picker
์กฐํšŒ์ˆ˜ 92 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

๋งค์ผ ์•„์นจ ์ ‘์†ํ•˜๋ฉด AI๊ฐ€ ๋žœ๋ค์œผ๋กœ ์šด๋™ ์ฑŒ๋ฆฐ์ง€๋ฅผ ์ถ”์ฒœํ•ด์ฃผ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. "์˜ค๋Š˜์€ ๋ญ ํ•˜์ง€?" ๊ณ ๋ฏผ ์—†์ด ๋ฐ”๋กœ ํ™ˆํŠธ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋™๊ธฐ๋ถ€์—ฌ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

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

  1. ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ ์ƒ์„ฑ: ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์šด๋™ ์ข…๋ฅ˜, ํšŸ์ˆ˜, ์„ธํŠธ ์ˆ˜๋ฅผ ๋žœ๋ค ์กฐํ•ฉ์œผ๋กœ ํ‘œ์‹œ
  2. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ: ์ƒ์ฒด/ํ•˜์ฒด/์ฝ”์–ด/์œ ์‚ฐ์†Œ ๋“ฑ ๋ถ€์œ„๋ณ„๋กœ ์ฑŒ๋ฆฐ์ง€ ์„ ํƒ ๊ฐ€๋Šฅ
  3. ๋‚œ์ด๋„ ์„ ํƒ: ์ดˆ๊ธ‰/์ค‘๊ธ‰/๊ณ ๊ธ‰ ๋‚œ์ด๋„์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์šด๋™๋Ÿ‰ ์ œ์‹œ
  4. ํƒ€์ด๋จธ ๊ธฐ๋Šฅ: ์šด๋™ ์‹œ์ž‘ ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ ์ž‘๋™ (์˜ˆ: 30์ดˆ ํ”Œ๋žญํฌ)
  5. ์ฑŒ๋ฆฐ์ง€ ์™„๋ฃŒ ๊ธฐ๋ก: SQLite DB์— ๋‚ ์งœ๋ณ„ ์™„๋ฃŒ ์—ฌ๋ถ€ ์ €์žฅ (๋กœ๊ทธ์ธ ์—†์ด ๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜ ๊ธฐ์ค€)
  6. ์™„๋ฃŒ ์ŠคํŠธ๋ฆญ ํ‘œ์‹œ: ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜ ๋ฐฐ์ง€ ("๐Ÿ”ฅ 3์ผ ์—ฐ์† ๋‹ฌ์„ฑ!")

๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

  • ์šด๋™๋ณ„ ์นผ๋กœ๋ฆฌ ์†Œ๋ชจ๋Ÿ‰ ์˜ˆ์ธก ํ‘œ์‹œ
  • ์™„๋ฃŒ ํ›„ ์‘์› ๋ฉ”์‹œ์ง€ ๋žœ๋ค ์ถœ๋ ฅ ("๋Œ€๋‹จํ•ด์š”! ๐Ÿ’ช", "์˜ค๋Š˜๋„ ์„ฑ๊ณต! ๐ŸŽ‰")
  • ์ฃผ๊ฐ„ ์™„๋ฃŒ์œจ ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ("์ด๋ฒˆ ์ฃผ 5/7์ผ ๋‹ฌ์„ฑ")
  • ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋Œ€์‘ (modern shell ํ† ํฐ ํ™œ์šฉ)
  • ์šด๋™ GIF/์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์„ ํƒ์ )

๐ŸŽจ UI/UX ์š”๊ตฌ์‚ฌํ•ญ

  • ์นด๋“œ ํ˜•ํƒœ๋กœ ์˜ค๋Š˜์˜ ์ฑŒ๋ฆฐ์ง€ ๊ฐ•์กฐ ํ‘œ์‹œ (ํฐ ํฐํŠธ, ์ค‘์•™ ๋ฐฐ์น˜)
  • ์นดํ…Œ๊ณ ๋ฆฌ ๋ฒ„ํŠผ์€ ์นฉ ์Šคํƒ€์ผ๋กœ ๊ฐ€๋กœ ์Šคํฌ๋กค
  • ํƒ€์ด๋จธ๋Š” ์›ํ˜• ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ๋˜๋Š” ํฐ ์ˆซ์ž ์นด์šดํŠธ๋‹ค์šด
  • ์™„๋ฃŒ ๋ฒ„ํŠผ์€ ๋ˆˆ์— ๋„๋Š” ์ƒ‰์ƒ (์˜ˆ: ์ดˆ๋ก์ƒ‰ ์ฒดํฌ ๋ฒ„ํŠผ)
  • ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ ์šฐ์„ )

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

  • ํ”„๋ ˆ์ž„์›Œํฌ: ๊ทธ๋ˆ„๋ณด๋“œ5 SE (g5se) โ€” PHP ๊ธฐ๋ฐ˜ ๊ฒฝ๋Ÿ‰ CMS
  • ํ…œํ”Œ๋ฆฟ: modern shell (๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋Œ€์‘, ์‚ฌ์ดํŠธ ํ†ต์ผ๊ฐ)
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: SQLite (๋ชจ๋“ˆ ์ „์šฉ DB ํŒŒ์ผ)
  • ํ”„๋ก ํŠธ์—”๋“œ: ๋ฐ”๋‹๋ผ JavaScript (์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์Œ)
  • ์Šคํƒ€์ผ: CSS ๋ณ€์ˆ˜ ํ† ํฐ + ์ธ๋ผ์ธ ์Šคํƒ€์ผ

๐Ÿ“Š ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ (SQLite)

CREATE TABLE challenges (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  category TEXT NOT NULL,
  exercise_name TEXT NOT NULL,
  reps INTEGER,
  duration_sec INTEGER,
  difficulty TEXT,
  calories_est INTEGER
);

CREATE TABLE completions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  challenge_id INTEGER,
  completed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  session_id TEXT
);

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • g5se ํด๋” ๊ธฐ๋ฐ˜ ์ž๋™ ๋ผ์šฐํŒ… ์ดํ•ด
  • modern shell๋กœ ์‚ฌ์ดํŠธ ํ†ต์ผ๊ฐ ์œ ์ง€
  • SQLite๋กœ ๊ฐ„๋‹จํ•œ CRUD ๊ตฌํ˜„
  • ๋ฐ”๋‹๋ผ JS๋กœ ํƒ€์ด๋จธยท๋žœ๋ค ๋กœ์ง ์ž‘์„ฑ
  • CSS ๋ณ€์ˆ˜ ํ† ํฐ์œผ๋กœ ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข ๊ทธ๋ˆ„๋ณด๋“œ5 SE(g5se) ๊ธฐ์กด ์„ค์น˜๋ณธ ์œ„์— ์ž‘์—… (์ƒˆ ํด๋” ์ƒ์„ฑ ๊ธˆ์ง€)
  • โ€ข app/modules/daily-workout-challenge-picker/index.php์— ํŽ˜์ด์ง€ ์ƒ์„ฑ โ†’ URL์€ /daily-workout-challenge-picker๋กœ ์ž๋™ ๋งคํ•‘
  • โ€ข modern shell ํ•„์ˆ˜ ์ ์šฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php + tail.sub.php)
  • โ€ข ๋ชจ๋“ˆ ์ „์šฉ SQLite DB ์‚ฌ์šฉ (G5_DATA_PATH.'/modules/daily-workout-challenge-picker.sqlite')
  • โ€ข g5se ๋ฉ”์ธ DB์— ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ ˆ๋Œ€ ๊ธˆ์ง€
  • โ€ข ์™ธ๋ถ€ ๋นŒ๋“œ ๋„๊ตฌ(npm/vite/webpack) ์‚ฌ์šฉ ๊ธˆ์ง€
  • โ€ข CDN ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ์ตœ์†Œํ™”, ๋ฐ”๋‹๋ผ JS๋กœ ๊ตฌํ˜„
  • โ€ข CSS๋Š” modern shell ํ† ํฐ ์šฐ์„  ์‚ฌ์šฉ (--m-surface, --m-text, --m-shadow ๋“ฑ)
  • โ€ข ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ด์Šค์ผ€์ดํ”„ ํ•„์ˆ˜ (htmlspecialchars)
  • โ€ข prepared statement๋กœ SQL ์ธ์ ์…˜ ๋ฐฉ์ง€
  • โ€ข ๋ผ์šฐํŠธ ์ˆ˜๋™ ๋“ฑ๋ก ๋ถˆํ•„์š” (ํด๋” ์ž๋™ ๋งคํ•‘)

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
g5se๊ฐ€ ์„ค์น˜๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ(๋ฃจํŠธ์— `index.php`ยท`app/`์ด ์žˆ๋Š” ๊ณณ)์—์„œ ์ž‘์—…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. (g5se ์„ค์น˜๋Š” https://github.com/gnuboard/g5se ์ฐธ๊ณ )

# Step 1: ํ”„๋กœ์ ํŠธ ๋ผˆ๋Œ€ ์ƒ์„ฑ + modern shell ์ ์šฉ

`app/modules/daily-workout-challenge-picker/index.php` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

// ํŽ˜์ด์ง€ ์ œ๋ชฉ
$g5['title'] = '์˜ค๋Š˜์˜ ์šด๋™ ์ฑŒ๋ฆฐ์ง€';

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;">
        <div style="max-width: 600px; margin: 0 auto;">
            <h1 style="font-size: 28px; font-weight: 700; margin-bottom: 8px; color: var(--m-text);">
                ๐ŸŽฏ ์˜ค๋Š˜์˜ ์šด๋™ ์ฑŒ๋ฆฐ์ง€
            </h1>
            <p style="color: var(--m-text-muted); margin-bottom: 32px;">
                AI๊ฐ€ ์ถ”์ฒœํ•˜๋Š” ํ™ˆํŠธ ๋ฏธ์…˜์„ ๋ฐ›์•„๋ณด์„ธ์š”!
            </p>

            <!-- ์ฑŒ๋ฆฐ์ง€ ์นด๋“œ ์˜์—ญ -->
            <div style="background: var(--m-surface); padding: 24px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); margin-bottom: 24px;">
                <div style="text-align: center;">
                    <div style="font-size: 48px; margin-bottom: 16px;">๐Ÿ’ช</div>
                    <h2 style="font-size: 24px; font-weight: 700; color: var(--m-text); margin-bottom: 8px;">
                        ํ‘ธ์‹œ์—…
                    </h2>
                    <p style="font-size: 18px; color: var(--m-text-muted); margin-bottom: 24px;">
                        15ํšŒ ร— 3์„ธํŠธ
                    </p>
                    <div style="display: inline-block; padding: 6px 12px; background: #3b82f6; color: white; border-radius: 16px; font-size: 14px; font-weight: 600;">
                        ์ดˆ๊ธ‰
                    </div>
                </div>
            </div>

            <!-- ์•ก์…˜ ๋ฒ„ํŠผ -->
            <button style="width: 100%; padding: 16px; background: #10b981; color: white; border: none; border-radius: var(--m-radius-lg); font-size: 18px; font-weight: 700; cursor: pointer; margin-bottom: 16px;">
                โœ… ์™„๋ฃŒ ์ฒดํฌ
            </button>
            <button style="width: 100%; padding: 16px; background: var(--m-surface); color: var(--m-text); border: 2px solid var(--m-text-muted); border-radius: var(--m-radius-lg); font-size: 16px; font-weight: 600; cursor: pointer;">
                ๐Ÿ”„ ๋‹ค๋ฅธ ์ฑŒ๋ฆฐ์ง€ ๋ณด๊ธฐ
            </button>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

์ด์ œ ๋ธŒ๋ผ์šฐ์ €์—์„œ `http://your-site.com/daily-workout-challenge-picker`์— ์ ‘์†ํ•˜๋ฉด ํŽ˜์ด์ง€๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค.

**ํ™•์ธ์‚ฌํ•ญ:**
- modern shell์ด ์ ์šฉ๋˜์–ด ์ƒ๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜, ํ‘ธํ„ฐ, ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€์ด ์ž๋™์œผ๋กœ ๋‚˜ํƒ€๋‚จ
- ์ค‘์•™์— ์ฑŒ๋ฆฐ์ง€ ์นด๋“œ๊ฐ€ ํ‘œ์‹œ๋จ
- ๋ฒ„ํŠผ์ด ๋ฐ˜์‘ํ˜•์œผ๋กœ ํ‘œ์‹œ๋จ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - /daily-workout-challenge-picker URL๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ - modern shell ๋ ˆ์ด์•„์›ƒ ์ ์šฉ (nav + footer) - ์ค‘์•™์— ์ •์  ์ฑŒ๋ฆฐ์ง€ ์นด๋“œ 1๊ฐœ ํ‘œ์‹œ - ์™„๋ฃŒ ์ฒดํฌ ๋ฒ„ํŠผ๊ณผ ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ ํ‘œ์‹œ - ๋‹คํฌ๋ชจ๋“œ ์ „ํ™˜ ์‹œ ์ƒ‰์ƒ ์ž๋™ ๋ณ€๊ฒฝ

2
Step 2
# Step 2: SQLite DB ์ดˆ๊ธฐํ™” + ์ƒ˜ํ”Œ ์ฑŒ๋ฆฐ์ง€ ๋ฐ์ดํ„ฐ ์‚ฝ์ž…

`app/modules/daily-workout-challenge-picker/index.php` ํŒŒ์ผ ์ƒ๋‹จ (modern shell ์ธํด๋ฃจ๋“œ ์ „)์— ๋‹ค์Œ DB ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

// SQLite DB ์ดˆ๊ธฐํ™”
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/daily-workout-challenge-picker.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ…Œ์ด๋ธ” ์ƒ์„ฑ
$db->exec("
    CREATE TABLE IF NOT EXISTS challenges (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        category TEXT NOT NULL,
        exercise_name TEXT NOT NULL,
        reps INTEGER,
        duration_sec INTEGER,
        difficulty TEXT,
        calories_est INTEGER
    )
");

$db->exec("
    CREATE TABLE IF NOT EXISTS completions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        challenge_id INTEGER,
        completed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        session_id TEXT
    )
");

// ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ)
$count = $db->query("SELECT COUNT(*) FROM challenges")->fetchColumn();
if ($count == 0) {
    $samples = [
        ['์ƒ์ฒด', 'ํ‘ธ์‹œ์—…', 15, null, '์ดˆ๊ธ‰', 50],
        ['์ƒ์ฒด', 'ํ”Œ๋žญํฌ', null, 30, '์ดˆ๊ธ‰', 30],
        ['ํ•˜์ฒด', '์Šค์ฟผํŠธ', 20, null, '์ดˆ๊ธ‰', 60],
        ['ํ•˜์ฒด', '๋Ÿฐ์ง€', 12, null, '์ค‘๊ธ‰', 55],
        ['์ฝ”์–ด', 'ํฌ๋Ÿฐ์น˜', 20, null, '์ดˆ๊ธ‰', 40],
        ['์ฝ”์–ด', '๋งˆ์šดํ‹ด ํด๋ผ์ด๋จธ', 15, null, '์ค‘๊ธ‰', 70],
        ['์œ ์‚ฐ์†Œ', '๋ฒ„ํ”ผ', 10, null, '์ค‘๊ธ‰', 100],
        ['์œ ์‚ฐ์†Œ', '์ ํ•‘์žญ', 30, null, '์ดˆ๊ธ‰', 50],
        ['์ƒ์ฒด', '๋‹ค์ด์•„๋ชฌ๋“œ ํ‘ธ์‹œ์—…', 10, null, '๊ณ ๊ธ‰', 80],
        ['ํ•˜์ฒด', '์ ํ”„ ์Šค์ฟผํŠธ', 15, null, '๊ณ ๊ธ‰', 90]
    ];
    
    $stmt = $db->prepare("INSERT INTO challenges (category, exercise_name, reps, duration_sec, difficulty, calories_est) VALUES (?, ?, ?, ?, ?, ?)");
    foreach ($samples as $s) {
        $stmt->execute($s);
    }
}

// ํŽ˜์ด์ง€ ์ œ๋ชฉ
$g5['title'] = '์˜ค๋Š˜์˜ ์šด๋™ ์ฑŒ๋ฆฐ์ง€';

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<!-- ์ดํ•˜ HTML์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ -->
```

ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉด SQLite DB๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋˜๊ณ  10๊ฐœ์˜ ์ƒ˜ํ”Œ ์ฑŒ๋ฆฐ์ง€๊ฐ€ ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค.

**ํ™•์ธ์‚ฌํ•ญ:**
- `data/modules/daily-workout-challenge-picker.sqlite` ํŒŒ์ผ ์ƒ์„ฑ๋จ
- DB์— challenges ํ…Œ์ด๋ธ”์— 10๊ฐœ ๋ ˆ์ฝ”๋“œ ์‚ฝ์ž…๋จ
- ํŽ˜์ด์ง€๋Š” ์ด์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ํ‘œ์‹œ๋จ (์•„์ง DB ์—ฐ๋™ ์•ˆ ํ•จ)

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - SQLite DB ํŒŒ์ผ ์ƒ์„ฑ (data/modules/ ํด๋”์—) - challenges ํ…Œ์ด๋ธ”์— 10๊ฐœ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… - completions ํ…Œ์ด๋ธ” ์ƒ์„ฑ (์•„์ง ๋น„์–ด์žˆ์Œ) - ํŽ˜์ด์ง€๋Š” ์ด์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ์ •์  ์นด๋“œ ํ‘œ์‹œ

3
Step 3
# Step 3: ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ ์„ ํƒ + ๋™์  ํ‘œ์‹œ

`app/modules/daily-workout-challenge-picker/index.php`์—์„œ DB์—์„œ ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€๋ฅผ ๊ฐ€์ ธ์™€ ์นด๋“œ์— ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์„ธ์š”.

DB ์ดˆ๊ธฐํ™” ๋ธ”๋ก ์ดํ›„, HTML ์ถœ๋ ฅ ์ „์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€:

```php
// ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ ์„ ํƒ
$challenge = $db->query("SELECT * FROM challenges ORDER BY RANDOM() LIMIT 1")->fetch(PDO::FETCH_ASSOC);

// ๋‚œ์ด๋„๋ณ„ ์ƒ‰์ƒ
$difficulty_colors = [
    '์ดˆ๊ธ‰' => '#10b981',
    '์ค‘๊ธ‰' => '#f59e0b',
    '๊ณ ๊ธ‰' => '#ef4444'
];
$badge_color = $difficulty_colors[$challenge['difficulty']] ?? '#6b7280';

// ์šด๋™๋Ÿ‰ ํ‘œ์‹œ ๋ฌธ์ž์—ด
if ($challenge['reps']) {
    $workout_text = $challenge['reps'] . 'ํšŒ ร— 3์„ธํŠธ';
} else {
    $workout_text = $challenge['duration_sec'] . '์ดˆ ์œ ์ง€';
}
```

๊ทธ๋ฆฌ๊ณ  HTML์˜ ์ฑŒ๋ฆฐ์ง€ ์นด๋“œ ๋ถ€๋ถ„์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •:

```php
<div style="background: var(--m-surface); padding: 24px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); margin-bottom: 24px;">
    <div style="text-align: center;">
        <div style="font-size: 48px; margin-bottom: 16px;">๐Ÿ’ช</div>
        <h2 style="font-size: 24px; font-weight: 700; color: var(--m-text); margin-bottom: 8px;">
            <?php echo htmlspecialchars($challenge['exercise_name']); ?>
        </h2>
        <p style="font-size: 18px; color: var(--m-text-muted); margin-bottom: 8px;">
            <?php echo htmlspecialchars($workout_text); ?>
        </p>
        <p style="font-size: 14px; color: var(--m-text-muted); margin-bottom: 16px;">
            ์˜ˆ์ƒ ์นผ๋กœ๋ฆฌ: <?php echo $challenge['calories_est']; ?>kcal ๐Ÿ”ฅ
        </p>
        <div style="display: inline-block; padding: 6px 12px; background: <?php echo $badge_color; ?>; color: white; border-radius: 16px; font-size: 14px; font-weight: 600;">
            <?php echo htmlspecialchars($challenge['difficulty']); ?>
        </div>
    </div>
</div>
```

"๋‹ค๋ฅธ ์ฑŒ๋ฆฐ์ง€ ๋ณด๊ธฐ" ๋ฒ„ํŠผ์„ ์‹ค์ œ ์ƒˆ๋กœ๊ณ ์นจ ๋งํฌ๋กœ ๋ณ€๊ฒฝ:

```php
<a href="/daily-workout-challenge-picker" style="display: block; width: 100%; padding: 16px; background: var(--m-surface); color: var(--m-text); border: 2px solid var(--m-text-muted); border-radius: var(--m-radius-lg); font-size: 16px; font-weight: 600; text-align: center; text-decoration: none; box-sizing: border-box;">
    ๐Ÿ”„ ๋‹ค๋ฅธ ์ฑŒ๋ฆฐ์ง€ ๋ณด๊ธฐ
</a>
```

ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์šด๋™์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

**ํ™•์ธ์‚ฌํ•ญ:**
- ์ƒˆ๋กœ๊ณ ์นจ๋งˆ๋‹ค ๋žœ๋ค ์šด๋™์ด ๋‚˜ํƒ€๋‚จ
- ์šด๋™๋ช…, ํšŸ์ˆ˜/์‹œ๊ฐ„, ์นผ๋กœ๋ฆฌ, ๋‚œ์ด๋„๊ฐ€ DB์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์œผ๋กœ ํ‘œ์‹œ๋จ
- ๋‚œ์ด๋„ ๋ฐฐ์ง€ ์ƒ‰์ƒ์ด ์ดˆ๊ธ‰(์ดˆ๋ก), ์ค‘๊ธ‰(์ฃผํ™ฉ), ๊ณ ๊ธ‰(๋นจ๊ฐ•)์œผ๋กœ ์ž๋™ ๋ณ€๊ฒฝ๋จ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ DB์—์„œ ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ 1๊ฐœ ์„ ํƒ - ์นด๋“œ์— ์šด๋™๋ช…, ํšŸ์ˆ˜/์‹œ๊ฐ„, ์นผ๋กœ๋ฆฌ, ๋‚œ์ด๋„ ๋™์  ํ‘œ์‹œ - ๋‚œ์ด๋„๋ณ„ ๋ฐฐ์ง€ ์ƒ‰์ƒ ์ž๋™ ๋ณ€๊ฒฝ - "๋‹ค๋ฅธ ์ฑŒ๋ฆฐ์ง€ ๋ณด๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์–ด ์ƒˆ ์ฑŒ๋ฆฐ์ง€ ํ‘œ์‹œ

4
Step 4
# Step 4: ์™„๋ฃŒ ์ฒดํฌ ๊ธฐ๋Šฅ + ์ŠคํŠธ๋ฆญ ํ‘œ์‹œ

์‚ฌ์šฉ์ž๊ฐ€ "์™„๋ฃŒ ์ฒดํฌ" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด completions ํ…Œ์ด๋ธ”์— ๊ธฐ๋กํ•˜๊ณ , ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

## 4-1. ์™„๋ฃŒ ์ฒ˜๋ฆฌ ๋กœ์ง

DB ์ดˆ๊ธฐํ™” ๋ธ”๋ก ์ดํ›„, ๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ ์„ ํƒ ์ „์— ๋‹ค์Œ ์ฝ”๋“œ ์ถ”๊ฐ€:

```php
// ์„ธ์…˜ ID (๊ฐ„์ด ์‚ฌ์šฉ์ž ์‹๋ณ„)
session_start();
if (!isset($_SESSION['workout_session_id'])) {
    $_SESSION['workout_session_id'] = uniqid('user_', true);
}
$session_id = $_SESSION['workout_session_id'];

// ์™„๋ฃŒ ์ฒดํฌ ์ฒ˜๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['complete'])) {
    $challenge_id = (int)$_POST['challenge_id'];
    $stmt = $db->prepare("INSERT INTO completions (challenge_id, session_id) VALUES (?, ?)");
    $stmt->execute([$challenge_id, $session_id]);
    
    // ์™„๋ฃŒ ํ›„ ์ƒˆ๋กœ๊ณ ์นจ
    header('Location: /daily-workout-challenge-picker?completed=1');
    exit;
}

// ์˜ค๋Š˜ ์™„๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
$today = date('Y-m-d');
$completed_today = $db->prepare("SELECT COUNT(*) FROM completions WHERE session_id = ? AND DATE(completed_at) = ?");
$completed_today->execute([$session_id, $today]);
$is_completed_today = $completed_today->fetchColumn() > 0;

// ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜ ๊ณ„์‚ฐ (๊ฐ„๋‹จ ๋ฒ„์ „: ์ตœ๊ทผ 7์ผ ์ค‘ ์™„๋ฃŒํ•œ ๋‚  ์ˆ˜)
$streak_query = $db->prepare("
    SELECT COUNT(DISTINCT DATE(completed_at)) as streak
    FROM completions
    WHERE session_id = ?
    AND DATE(completed_at) >= DATE('now', '-7 days')
");
$streak_query->execute([$session_id]);
$streak = $streak_query->fetchColumn();
```

## 4-2. HTML ์ˆ˜์ •

์™„๋ฃŒ ๋ฒ„ํŠผ์„ ํผ์œผ๋กœ ๋ณ€๊ฒฝ:

```php
<?php if ($is_completed_today): ?>
    <div style="width: 100%; padding: 16px; background: #10b981; color: white; border-radius: var(--m-radius-lg); font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 16px;">
        โœ… ์˜ค๋Š˜ ์ด๋ฏธ ์™„๋ฃŒํ–ˆ์–ด์š”!
    </div>
<?php else: ?>
    <form method="POST" style="margin-bottom: 16px;">
        <input type="hidden" name="challenge_id" value="<?php echo $challenge['id']; ?>">
        <button type="submit" name="complete" style="width: 100%; padding: 16px; background: #10b981; color: white; border: none; border-radius: var(--m-radius-lg); font-size: 18px; font-weight: 700; cursor: pointer;">
            โœ… ์™„๋ฃŒ ์ฒดํฌ
        </button>
    </form>
<?php endif; ?>
```

์ŠคํŠธ๋ฆญ ํ‘œ์‹œ๋ฅผ ์นด๋“œ ์œ„์— ์ถ”๊ฐ€:

```php
<?php if ($streak > 0): ?>
    <div style="background: linear-gradient(135deg, #f59e0b, #ef4444); padding: 16px; border-radius: var(--m-radius-lg); margin-bottom: 16px; text-align: center; color: white;">
        <div style="font-size: 32px; margin-bottom: 8px;">๐Ÿ”ฅ</div>
        <p style="font-size: 18px; font-weight: 700; margin: 0;">
            <?php echo $streak; ?>์ผ ์—ฐ์† ๋‹ฌ์„ฑ ์ค‘!
        </p>
    </div>
<?php endif; ?>
```

์™„๋ฃŒ ํ›„ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ (URL ํŒŒ๋ผ๋ฏธํ„ฐ ํ™œ์šฉ):

```php
<?php if (isset($_GET['completed'])): ?>
    <div style="background: #10b981; color: white; padding: 12px 16px; border-radius: var(--m-radius-lg); margin-bottom: 16px; text-align: center; font-weight: 600;">
        ๐ŸŽ‰ ๋Œ€๋‹จํ•ด์š”! ์˜ค๋Š˜๋„ ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!
    </div>
<?php endif; ?>
```

**ํ™•์ธ์‚ฌํ•ญ:**
- ์™„๋ฃŒ ์ฒดํฌ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ DB์— ๊ธฐ๋ก๋˜๊ณ  ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
- ์˜ค๋Š˜ ์ด๋ฏธ ์™„๋ฃŒํ–ˆ์œผ๋ฉด ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™” ๋ฉ”์‹œ์ง€๋กœ ๋ณ€๊ฒฝ
- ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜๊ฐ€ 1์ผ ์ด์ƒ์ด๋ฉด ์ƒ๋‹จ์— ์ŠคํŠธ๋ฆญ ๋ฐฐ๋„ˆ ํ‘œ์‹œ
- ์™„๋ฃŒ ํ›„ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - ์™„๋ฃŒ ์ฒดํฌ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ completions ํ…Œ์ด๋ธ”์— ๊ธฐ๋ก - ์˜ค๋Š˜ ์™„๋ฃŒ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋ฒ„ํŠผ ์ƒํƒœ ๋ณ€๊ฒฝ - ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜ ๊ณ„์‚ฐ ๋ฐ ์ŠคํŠธ๋ฆญ ๋ฐฐ๋„ˆ ํ‘œ์‹œ - ์™„๋ฃŒ ํ›„ ์‘์› ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ - ์„ธ์…˜ ๊ธฐ๋ฐ˜ ๊ฐ„์ด ์‚ฌ์šฉ์ž ์‹๋ณ„

5
Step 5
# Step 5: ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ + ๋‚œ์ด๋„ ์„ ํƒ ๊ธฐ๋Šฅ

์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์ฒด/ํ•˜์ฒด/์ฝ”์–ด/์œ ์‚ฐ์†Œ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ๋‚œ์ด๋„๋ฅผ ์„ ํƒํ•˜์—ฌ ์ฑŒ๋ฆฐ์ง€๋ฅผ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ํ•˜์„ธ์š”.

## 5-1. ํ•„ํ„ฐ ๋กœ์ง ์ถ”๊ฐ€

๋žœ๋ค ์ฑŒ๋ฆฐ์ง€ ์„ ํƒ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •:

```php
// ํ•„ํ„ฐ ํŒŒ๋ผ๋ฏธํ„ฐ
$category_filter = isset($_GET['category']) ? $_GET['category'] : null;
$difficulty_filter = isset($_GET['difficulty']) ? $_GET['difficulty'] : null;

// ์ฟผ๋ฆฌ ๋นŒ๋“œ
$where = [];
$params = [];

if ($category_filter) {
    $where[] = "category = ?";
    $params[] = $category_filter;
}

if ($difficulty_filter) {
    $where[] = "difficulty = ?";
    $params[] = $difficulty_filter;
}

$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
$stmt = $db->prepare("SELECT * FROM challenges {$where_sql} ORDER BY RANDOM() LIMIT 1");
$stmt->execute($params);
$challenge = $stmt->fetch(PDO::FETCH_ASSOC);

// ์ฑŒ๋ฆฐ์ง€๊ฐ€ ์—†์œผ๋ฉด ์ „์ฒด์—์„œ ๋žœ๋ค ์„ ํƒ
if (!$challenge) {
    $challenge = $db->query("SELECT * FROM challenges ORDER BY RANDOM() LIMIT 1")->fetch(PDO::FETCH_ASSOC);
}
```

## 5-2. ํ•„ํ„ฐ UI ์ถ”๊ฐ€

์ฑŒ๋ฆฐ์ง€ ์นด๋“œ ์œ„์— ํ•„ํ„ฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€:

```php
<!-- ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ -->
<div style="margin-bottom: 16px; overflow-x: auto; white-space: nowrap; padding-bottom: 8px;">
    <?php
    $categories = ['์ „์ฒด' => null, '์ƒ์ฒด' => '์ƒ์ฒด', 'ํ•˜์ฒด' => 'ํ•˜์ฒด', '์ฝ”์–ด' => '์ฝ”์–ด', '์œ ์‚ฐ์†Œ' => '์œ ์‚ฐ์†Œ'];
    foreach ($categories as $label => $value) {
        $is_active = ($category_filter === $value) || ($value === null && !$category_filter);
        $bg = $is_active ? '#3b82f6' : 'var(--m-surface)';
        $color = $is_active ? 'white' : 'var(--m-text)';
        $url = '/daily-workout-challenge-picker?category=' . urlencode($value ?: '') . ($difficulty_filter ? '&difficulty=' . urlencode($difficulty_filter) : '');
        echo "<a href='{$url}' style='display: inline-block; padding: 8px 16px; margin-right: 8px; background: {$bg}; color: {$color}; border-radius: 20px; font-size: 14px; font-weight: 600; text-decoration: none;'>{$label}</a>";
    }
    ?>
</div>

<!-- ๋‚œ์ด๋„ ํ•„ํ„ฐ -->
<div style="margin-bottom: 24px; overflow-x: auto; white-space: nowrap; padding-bottom: 8px;">
    <?php
    $difficulties = ['์ „์ฒด' => null, '์ดˆ๊ธ‰' => '์ดˆ๊ธ‰', '์ค‘๊ธ‰' => '์ค‘๊ธ‰', '๊ณ ๊ธ‰' => '๊ณ ๊ธ‰'];
    foreach ($difficulties as $label => $value) {
        $is_active = ($difficulty_filter === $value) || ($value === null && !$difficulty_filter);
        $bg = $is_active ? '#10b981' : 'var(--m-surface)';
        $color = $is_active ? 'white' : 'var(--m-text)';
        $url = '/daily-workout-challenge-picker?difficulty=' . urlencode($value ?: '') . ($category_filter ? '&category=' . urlencode($category_filter) : '');
        echo "<a href='{$url}' style='display: inline-block; padding: 8px 16px; margin-right: 8px; background: {$bg}; color: {$color}; border-radius: 20px; font-size: 14px; font-weight: 600; text-decoration: none;'>{$label}</a>";
    }
    ?>
</div>
```

**ํ™•์ธ์‚ฌํ•ญ:**
- ์นดํ…Œ๊ณ ๋ฆฌ ๋ฒ„ํŠผ(์ „์ฒด/์ƒ์ฒด/ํ•˜์ฒด/์ฝ”์–ด/์œ ์‚ฐ์†Œ) ํ‘œ์‹œ
- ๋‚œ์ด๋„ ๋ฒ„ํŠผ(์ „์ฒด/์ดˆ๊ธ‰/์ค‘๊ธ‰/๊ณ ๊ธ‰) ํ‘œ์‹œ
- ์„ ํƒ๋œ ํ•„ํ„ฐ๋Š” ํŒŒ๋ž€์ƒ‰/์ดˆ๋ก์ƒ‰์œผ๋กœ ๊ฐ•์กฐ
- ํ•„ํ„ฐ ์„ ํƒ ์‹œ ํ•ด๋‹น ์กฐ๊ฑด์— ๋งž๋Š” ์ฑŒ๋ฆฐ์ง€๋งŒ ๋žœ๋ค ์„ ํƒ
- ์กฐ๊ฑด์— ๋งž๋Š” ์ฑŒ๋ฆฐ์ง€๊ฐ€ ์—†์œผ๋ฉด ์ „์ฒด์—์„œ ์„ ํƒ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์นฉ ๋ฒ„ํŠผ ํ‘œ์‹œ (๊ฐ€๋กœ ์Šคํฌ๋กค ๊ฐ€๋Šฅ) - ๋‚œ์ด๋„ ํ•„ํ„ฐ ์นฉ ๋ฒ„ํŠผ ํ‘œ์‹œ - ์„ ํƒ๋œ ํ•„ํ„ฐ๋Š” ์ƒ‰์ƒ์œผ๋กœ ๊ฐ•์กฐ - ํ•„ํ„ฐ ์„ ํƒ ์‹œ URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋˜์–ด ์กฐ๊ฑด์— ๋งž๋Š” ์ฑŒ๋ฆฐ์ง€ ํ‘œ์‹œ - ์กฐ๊ฑด ์—†์œผ๋ฉด ์ „์ฒด ์ค‘ ๋žœ๋ค ์„ ํƒ

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

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

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

๋Œ“๊ธ€ (1)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
AmandaRechetti

Nous sommes lร  pour vous aider ร  obtenir votre permis de conduire en toute lรฉgalitรฉ, sans que vous ayez ร  vous soucier des dรฉmarches <a href="https://acheter-un-permis-deconduire.com">acheter permis de conduire</a>