๐ฏ ์ค๋์ ์ด๋ ์ฑ๋ฆฐ์ง ์ถ์ฒ๊ธฐ - AI๊ฐ ๊ณจ๋ผ์ฃผ๋ ํํธ ๋ฏธ์
daily-workout-challenge-picker
๋ฌธ์ ์ค๋ช
๐ ํ๋ก์ ํธ ๊ฐ์
๋งค์ผ ์์นจ ์ ์ํ๋ฉด AI๊ฐ ๋๋ค์ผ๋ก ์ด๋ ์ฑ๋ฆฐ์ง๋ฅผ ์ถ์ฒํด์ฃผ๋ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. "์ค๋์ ๋ญ ํ์ง?" ๊ณ ๋ฏผ ์์ด ๋ฐ๋ก ํํธ๋ฅผ ์์ํ ์ ์๋๋ก ๋๋ ๋๊ธฐ๋ถ์ฌ ์๋น์ค์ ๋๋ค.
โจ ํต์ฌ ๊ธฐ๋ฅ
ํ์ ๊ธฐ๋ฅ
- ๋๋ค ์ฑ๋ฆฐ์ง ์์ฑ: ํ์ด์ง ๋ก๋ ์ ์ด๋ ์ข ๋ฅ, ํ์, ์ธํธ ์๋ฅผ ๋๋ค ์กฐํฉ์ผ๋ก ํ์
- ์นดํ ๊ณ ๋ฆฌ๋ณ ํํฐ: ์์ฒด/ํ์ฒด/์ฝ์ด/์ ์ฐ์ ๋ฑ ๋ถ์๋ณ๋ก ์ฑ๋ฆฐ์ง ์ ํ ๊ฐ๋ฅ
- ๋์ด๋ ์ ํ: ์ด๊ธ/์ค๊ธ/๊ณ ๊ธ ๋์ด๋์ ๋ฐ๋ผ ๋ค๋ฅธ ์ด๋๋ ์ ์
- ํ์ด๋จธ ๊ธฐ๋ฅ: ์ด๋ ์์ ๋ฒํผ ๋๋ฅด๋ฉด ์นด์ดํธ๋ค์ด ํ์ด๋จธ ์๋ (์: 30์ด ํ๋ญํฌ)
- ์ฑ๋ฆฐ์ง ์๋ฃ ๊ธฐ๋ก: SQLite DB์ ๋ ์ง๋ณ ์๋ฃ ์ฌ๋ถ ์ ์ฅ (๋ก๊ทธ์ธ ์์ด ๋ธ๋ผ์ฐ์ ์ธ์ ๊ธฐ์ค)
- ์๋ฃ ์คํธ๋ฆญ ํ์: ์ฐ์ ๋ฌ์ฑ ์ผ์ ๋ฐฐ์ง ("๐ฅ 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์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
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๊ฐ ํ์ - ์๋ฃ ์ฒดํฌ ๋ฒํผ๊ณผ ์๋ก๊ณ ์นจ ๋ฒํผ ํ์ - ๋คํฌ๋ชจ๋ ์ ํ ์ ์์ ์๋ ๋ณ๊ฒฝ
# 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 ํ ์ด๋ธ ์์ฑ (์์ง ๋น์ด์์) - ํ์ด์ง๋ ์ด์ ๊ณผ ๋์ผํ๊ฒ ์ ์ ์นด๋ ํ์
# 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๊ฐ ์ ํ - ์นด๋์ ์ด๋๋ช , ํ์/์๊ฐ, ์นผ๋ก๋ฆฌ, ๋์ด๋ ๋์ ํ์ - ๋์ด๋๋ณ ๋ฐฐ์ง ์์ ์๋ ๋ณ๊ฒฝ - "๋ค๋ฅธ ์ฑ๋ฆฐ์ง ๋ณด๊ธฐ" ๋ฒํผ ํด๋ฆญ ์ ํ์ด์ง ์๋ก๊ณ ์นจ๋์ด ์ ์ฑ๋ฆฐ์ง ํ์
# 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 ํ ์ด๋ธ์ ๊ธฐ๋ก - ์ค๋ ์๋ฃ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ฒํผ ์ํ ๋ณ๊ฒฝ - ์ฐ์ ๋ฌ์ฑ ์ผ์ ๊ณ์ฐ ๋ฐ ์คํธ๋ฆญ ๋ฐฐ๋ ํ์ - ์๋ฃ ํ ์์ ๋ฉ์์ง ํ์ - ์ธ์ ๊ธฐ๋ฐ ๊ฐ์ด ์ฌ์ฉ์ ์๋ณ
# 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)
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>