๐ฏ ์ํ๋น ์ ์ฝ ์ฑ๋ฆฐ์ง ํธ๋์ปค - 30์ผ ์ง์ถ ์ค์ด๊ธฐ ๋ฏธ์
expense-saving-challenge-tracker
๋ฌธ์ ์ค๋ช
์ํ๋น ์ ์ฝ ์ฑ๋ฆฐ์ง ํธ๋์ปค
๋งค๋ฌ ์ง์ถ์ ์ค์ด๊ณ ์ถ์ง๋ง ์์ฌ์ผ์ผ๋ก ๋๋๋ ๋ถ๋ค์ ์ํ 30์ผ ์ ์ฝ ์ฑ๋ฆฐ์ง ํ๋ซํผ์ ๋๋ค. ์ฌ์ฉ์๋ ๋งค์ผ ์ ์ฝํ ํญ๋ชฉ๊ณผ ๊ธ์ก์ ๊ธฐ๋กํ๊ณ , ๋์ ์ ์ฝ์ก์ ์๊ฐ์ ์ผ๋ก ํ์ธํ๋ฉฐ ๋๊ธฐ๋ถ์ฌ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
ํ์ ๊ธฐ๋ฅ
- ์ฑ๋ฆฐ์ง ์์ ํ์ด์ง: ๋ชฉํ ์ ์ฝ์ก ์ค์ ๋ฐ ์ฑ๋ฆฐ์ง ์์์ผ ์ ๋ ฅ
- ์ผ์ผ ์ ์ฝ ๊ธฐ๋ก ํผ: ๋ ์ง, ์ ์ฝ ํญ๋ชฉ(์นดํ ๊ณ ๋ฆฌ), ์ ์ฝ ๊ธ์ก, ๊ฐ๋จ ๋ฉ๋ชจ ์ ๋ ฅ
- ์ ์ฝ ๋ด์ญ ๋ชฉ๋ก: ๊ธฐ๋กํ ์ ์ฝ ๋ด์ญ์ ์ต์ ์์ผ๋ก ํ์ (๋ ์ง, ์นดํ ๊ณ ๋ฆฌ, ๊ธ์ก, ๋ฉ๋ชจ)
- ๋์ ํต๊ณ ๋์๋ณด๋: ์ด ์ ์ฝ์ก, ๋ชฉํ ๋ฌ์ฑ๋ฅ (%), ๋จ์ ์ผ์ ํ์
- ์นดํ ๊ณ ๋ฆฌ๋ณ ์ง๊ณ: ์๋น/๊ตํต๋น/์ผํ/๊ธฐํ ๋ฑ ์นดํ ๊ณ ๋ฆฌ๋ณ ์ ์ฝ ๊ธ์ก ํฉ๊ณ
- ์งํ ๋ฐ ์๊ฐํ: ๋ชฉํ ๊ธ์ก ๋๋น ํ์ฌ ์ ์ฝ์ก์ ์งํ ๋ฐ๋ก ํ์
- ๋ฐ์ดํฐ ์ ์ฅ: SQLite DB์ ์ฑ๋ฆฐ์ง ์ค์ ๋ฐ ์ผ์ผ ๊ธฐ๋ก ์ ์ฅ (PDO ์ฌ์ฉ)
- ์ ๋ ฅ ๊ฒ์ฆ: ๊ธ์ก์ ์์๋ง ํ์ฉ, ๋ ์ง ํ์ ๊ฒ์ฆ
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ์ฃผ๊ฐ/์๊ฐ ํต๊ณ: ์ฃผ๋ณ/์๋ณ ์ ์ฝ ์ถ์ด๋ฅผ ๊ฐ๋จํ ์ฐจํธ(Chart.js)๋ก ์๊ฐํ
- ์ ์ฝ ๋ฑ์ง ์์คํ : ๋์ ๊ธ์ก ๋ฌ์ฑ ์ ๋ฑ์ง ํ๋ (์: 10๋ง์, 30๋ง์, 50๋ง์)
- CSV ๋ด๋ณด๋ด๊ธฐ: ์ ์ฝ ๋ด์ญ์ CSV ํ์ผ๋ก ๋ค์ด๋ก๋
- ๋คํฌ ๋ชจ๋: ์ฌ์ฉ์ ์ ํธ์ ๋ฐ๋ผ ํ ๋ง ์ ํ
- ๋ชฉํ ์ฌ์ค์ : ์ฑ๋ฆฐ์ง ๋์ค ๋ชฉํ ๊ธ์ก ์์ ๊ธฐ๋ฅ
๊ธฐ์ ์คํ
- PHP 8.3+ (Vanilla PHP)
- PDO + SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค
- HTML5 + CSS3 (Flexbox/Grid ๋ ์ด์์)
- Vanilla JavaScript (์ฐจํธ๋ Chart.js CDN ํ์ฉ)
- PHP ๋ด์ฅ ์๋ฒ (
php -S localhost:8000)
๋ณด์ ์๊ตฌ์ฌํญ
- ๋ชจ๋ ์ฌ์ฉ์ ์ ๋ ฅ ๊ฒ์ฆ (๊ธ์ก, ๋ ์ง, ํ ์คํธ ๊ธธ์ด)
- SQL Injection ๋ฐฉ์ง (Prepared Statements)
- XSS ๋ฐฉ์ง (์ถ๋ ฅ ์
htmlspecialchars()์ฌ์ฉ) - CSRF ํ ํฐ (ํผ ์ ์ถ ์ ์ธ์ ๊ธฐ๋ฐ ํ ํฐ ๊ฒ์ฆ)
๋์์ธ ๊ฐ์ด๋
- ๊น๋ํ ์นด๋ํ ๋ ์ด์์ (์ ์ฝ ๋ด์ญ ๊ฐ๊ฐ ์นด๋๋ก ํ์)
- ๋ชฉํ ๋ฌ์ฑ๋ฅ ์ ๋ฐ๋ผ ์งํ ๋ฐ ์์ ๋ณํ (0-50%: ๋นจ๊ฐ, 51-80%: ๋ ธ๋, 81-100%: ์ด๋ก)
- ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ/ํ๋ธ๋ฆฟ/๋ฐ์คํฌํฑ ๋์)
- ์์ด์ฝ ์ฌ์ฉ (Font Awesome CDN ๋๋ ์ด๋ชจ์ง)
- ํต๊ณ ์ซ์๋ ๊ฐ์กฐ ํ์ (ํฐ ํฐํธ, ์์ ๊ตฌ๋ถ)
์ ์ฝ ์กฐ๊ฑด
- โข PHP 8.3+ Vanilla PHP๋ง ์ฌ์ฉ (Laravel, Symfony ๋ฑ ํ๋ ์์ํฌ ๊ธ์ง)
- โข Composer ํจํค์ง ๋ฐ ๋น๋ ๋๊ตฌ ์ฌ์ฉ ๊ธ์ง
- โข PDO + SQLite๋ก ๋ฐ์ดํฐ ์ ์ฅ (ํ์ผ ๊ธฐ๋ฐ DB)
- โข PHP ๋ด์ฅ ์๋ฒ๋ก ์คํ ๊ฐ๋ฅํด์ผ ํจ (`php -S localhost:8000`)
- โข ๋ชจ๋ ์ฌ์ฉ์ ์ ๋ ฅ ๊ฒ์ฆ ๋ฐ ์ถ๋ ฅ ์ด์ค์ผ์ดํ ํ์
- โข Prepared Statements๋ก SQL Injection ๋ฐฉ์ง
- โข CSRF ํ ํฐ์ผ๋ก ํผ ๋ณดํธ
- โข ๋ฐ์ํ ์น ๋์์ธ (๋ชจ๋ฐ์ผ ์ฐ์ )
- โข Chart.js๋ CDN์ผ๋ก๋ง ์ฌ์ฉ (npm ์ค์น ๊ธ์ง)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir expense-saving-challenge-tracker && cd expense-saving-challenge-tracker ์ด ํด๋์์ ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค. # Step 1: ํ๋ก์ ํธ ์ด๊ธฐ ๊ตฌ์กฐ ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ๋ค์ ํ์ผ๋ค์ ์์ฑํด์ฃผ์ธ์: ## 1. `index.php` (๋ฉ์ธ ํ์ด์ง) - ์ฑ๋ฆฐ์ง ์ค์ ํ์ธ: DB์์ ํ์ฌ ํ์ฑ ์ฑ๋ฆฐ์ง ์กฐํ - ์ฑ๋ฆฐ์ง๊ฐ ์์ผ๋ฉด โ ์ค์ ํผ ํ์ (๋ชฉํ ๊ธ์ก, ์์์ผ ์ ๋ ฅ) - ์ฑ๋ฆฐ์ง๊ฐ ์์ผ๋ฉด โ ๋์๋ณด๋ ํ์ (๋์ ์ ์ฝ์ก, ๋ชฉํ ๋ฌ์ฑ๋ฅ , ๋จ์ ์ผ์) - ์ผ์ผ ์ ์ฝ ๊ธฐ๋ก ํผ ํ์ (๋ ์ง, ์นดํ ๊ณ ๋ฆฌ, ๊ธ์ก, ๋ฉ๋ชจ) - ์ต๊ทผ ์ ์ฝ ๋ด์ญ 5๊ฐ ํ์ (ํ ์ด๋ธ ํํ) - ์ธ์ ์์ (`session_start()`) - CSRF ํ ํฐ ์์ฑ ๋ฐ ์ธ์ ์ ์ฅ ## 2. `db.php` (๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ) - SQLite DB ํ์ผ๋ช : `challenge.db` - PDO ์ฐ๊ฒฐ ์ค์ (์๋ฌ ๋ชจ๋: EXCEPTION) - ํ ์ด๋ธ 2๊ฐ ์๋ ์์ฑ (์์ ๊ฒฝ์ฐ): - `challenges` ํ ์ด๋ธ: id, target_amount, start_date, created_at - `savings` ํ ์ด๋ธ: id, challenge_id, date, category, amount, memo, created_at ## 3. `config.php` (์ค์ ํ์ผ) - ์นดํ ๊ณ ๋ฆฌ ๋ชฉ๋ก ์์ ์ ์: ['์๋น', '๊ตํต๋น', '์ผํ', '๊ตฌ๋ ๋ฃ', '๊ธฐํ'] - DB ํ์ผ ๊ฒฝ๋ก ์์ - ์๋ฌ ํ์ ์ค์ (๊ฐ๋ฐ ํ๊ฒฝ) ## 4. `style.css` (๊ธฐ๋ณธ ์คํ์ผ) - CSS Reset - ์นด๋ํ ๋ ์ด์์ (์ ์ฝ ๋ด์ญ) - ์งํ ๋ฐ ์คํ์ผ (๋์ด 20px, ๋ฅ๊ทผ ๋ชจ์๋ฆฌ, ๊ทธ๋ผ๋ฐ์ด์ ) - ๋ฐ์ํ ๊ทธ๋ฆฌ๋ (๋ชจ๋ฐ์ผ: 1์ด, ํ๋ธ๋ฆฟ: 2์ด, ๋ฐ์คํฌํฑ: 3์ด) - ์์ ํ๋ ํธ: ์ฃผ์ ์์(์ด๋ก), ๋ณด์กฐ ์์(๋ ธ๋, ๋นจ๊ฐ) ์๊ตฌ์ฌํญ: - PHP 8.3+ ๋ฌธ๋ฒ ์ฌ์ฉ - PDO Prepared Statements ์ฌ์ฉ - `htmlspecialchars()`๋ก ๋ชจ๋ ์ถ๋ ฅ ์ด์ค์ผ์ดํ - ๊ฐ๋จํ ์๋ฌ ํธ๋ค๋ง (try-catch) - ์ฃผ์์ผ๋ก ์ฝ๋ ์ค๋ช ์ถ๊ฐ
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ๋ก์ ํธ ํด๋ ์์ฑ ๋ฐ ๊ธฐ๋ณธ ํ์ผ 4๊ฐ ์์ฑ. SQLite DB ์ฐ๊ฒฐ ๋ฐ ํ ์ด๋ธ ์๋ ์์ฑ. ๋ฉ์ธ ํ์ด์ง์์ ์ฑ๋ฆฐ์ง ์ค์ ํผ ๋๋ ๋์๋ณด๋ ์กฐ๊ฑด๋ถ ํ์. CSS๋ก ๊ธฐ๋ณธ ๋ ์ด์์ ๊ตฌ์ฑ.
# Step 2: ์ฑ๋ฆฐ์ง ์ค์ ๋ฐ ์ผ์ผ ์ ์ฝ ๊ธฐ๋ก ๊ธฐ๋ฅ ๊ตฌํ ๋ค์ ํ์ผ๋ค์ ์์ฑ/์์ ํด์ฃผ์ธ์: ## 1. `save_challenge.php` (์ฑ๋ฆฐ์ง ์ค์ ์ฒ๋ฆฌ) - POST ์์ฒญ ์ฒ๋ฆฌ - CSRF ํ ํฐ ๊ฒ์ฆ (์ธ์ ํ ํฐ๊ณผ ๋น๊ต) - ์ ๋ ฅ ๊ฒ์ฆ: - ๋ชฉํ ๊ธ์ก: ์์, ์ซ์๋ง ํ์ฉ - ์์์ผ: ์ ํจํ ๋ ์ง ํ์ (YYYY-MM-DD) - DB์ ์ฑ๋ฆฐ์ง ์ ์ฅ (challenges ํ ์ด๋ธ) - ์ฑ๊ณต ์ `index.php`๋ก ๋ฆฌ๋ค์ด๋ ํธ - ์คํจ ์ ์๋ฌ ๋ฉ์์ง ํ์ ## 2. `save_saving.php` (์ผ์ผ ์ ์ฝ ๊ธฐ๋ก ์ฒ๋ฆฌ) - POST ์์ฒญ ์ฒ๋ฆฌ - CSRF ํ ํฐ ๊ฒ์ฆ - ์ ๋ ฅ ๊ฒ์ฆ: - ๋ ์ง: ์ ํจํ ํ์, ์ฑ๋ฆฐ์ง ์์์ผ ์ดํ๋ง ํ์ฉ - ์นดํ ๊ณ ๋ฆฌ: ํ์ฉ๋ ์นดํ ๊ณ ๋ฆฌ ๋ชฉ๋ก์ ํฌํจ๋๋์ง ํ์ธ - ๊ธ์ก: ์์, ์ซ์๋ง ํ์ฉ - ๋ฉ๋ชจ: ์ต๋ 200์ ์ ํ - DB์ ์ ์ฝ ๊ธฐ๋ก ์ ์ฅ (savings ํ ์ด๋ธ, challenge_id ์ธ๋ํค ์ฐ๊ฒฐ) - ์ฑ๊ณต ์ `index.php`๋ก ๋ฆฌ๋ค์ด๋ ํธ (์ฑ๊ณต ๋ฉ์์ง ์ธ์ ํ๋์) - ์คํจ ์ ์๋ฌ ๋ฉ์์ง ํ์ ## 3. `index.php` ์์ - ์ฑ๋ฆฐ์ง๊ฐ ์์ ๋: - ์ด ์ ์ฝ์ก ๊ณ์ฐ (savings ํ ์ด๋ธ SUM) - ๋ชฉํ ๋ฌ์ฑ๋ฅ ๊ณ์ฐ (์ด ์ ์ฝ์ก / ๋ชฉํ ๊ธ์ก * 100) - ๋จ์ ์ผ์ ๊ณ์ฐ (์์์ผ + 30์ผ - ์ค๋) - ์งํ ๋ฐ ํ์ (๋ฌ์ฑ๋ฅ ์ ๋ฐ๋ผ ์์ ๋ณ๊ฒฝ: 0-50% ๋นจ๊ฐ, 51-80% ๋ ธ๋, 81-100% ์ด๋ก) - ์ต๊ทผ ์ ์ฝ ๋ด์ญ 5๊ฐ ํ์ (๋ ์ง, ์นดํ ๊ณ ๋ฆฌ, ๊ธ์ก, ๋ฉ๋ชจ) - ์ธ์ ํ๋์ ๋ฉ์์ง ํ์ ํ ์ญ์ ## 4. `helpers.php` (ํฌํผ ํจ์) - `validate_csrf()`: CSRF ํ ํฐ ๊ฒ์ฆ ํจ์ - `validate_positive_number()`: ์์ ๊ฒ์ฆ ํจ์ - `validate_date()`: ๋ ์ง ํ์ ๊ฒ์ฆ ํจ์ - `format_currency()`: ๊ธ์ก ํฌ๋งท ํจ์ (์: 10000 โ 10,000์) - `calculate_days_left()`: ๋จ์ ์ผ์ ๊ณ์ฐ ํจ์ ์๊ตฌ์ฌํญ: - ๋ชจ๋ ํผ์ CSRF ํ ํฐ hidden ํ๋ ์ถ๊ฐ - ์๋ฒ ์ฌ์ด๋ ์ ๋ ฅ ๊ฒ์ฆ ์ฒ ์ ํ - ์๋ฌ ๋ฉ์์ง๋ ์ฌ์ฉ์ ์นํ์ ์ผ๋ก - ์ฑ๊ณต ๋ฉ์์ง๋ ์ธ์ ํ๋์๋ก ๊ตฌํ - PDO ์์ธ ์ฒ๋ฆฌ
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ฑ๋ฆฐ์ง ์ค์ ๋ฐ ์ผ์ผ ์ ์ฝ ๊ธฐ๋ก ๊ธฐ๋ฅ ์์ฑ. CSRF ๋ณดํธ ๋ฐ ์ ๋ ฅ ๊ฒ์ฆ ๊ตฌํ. ๋์๋ณด๋์์ ์ด ์ ์ฝ์ก, ๋ฌ์ฑ๋ฅ , ๋จ์ ์ผ์ ํ์. ์งํ ๋ฐ ์์ ๋์ ๋ณ๊ฒฝ.
# Step 3: ์ ์ฝ ๋ด์ญ ๋ชฉ๋ก ๋ฐ ์นดํ ๊ณ ๋ฆฌ๋ณ ํต๊ณ ๊ตฌํ ๋ค์ ํ์ผ๋ค์ ์์ฑ/์์ ํด์ฃผ์ธ์: ## 1. `history.php` (์ ์ฝ ๋ด์ญ ์ ์ฒด ๋ชฉ๋ก) - ํ์ฌ ์ฑ๋ฆฐ์ง์ ๋ชจ๋ ์ ์ฝ ๊ธฐ๋ก ์กฐํ (์ต์ ์ ์ ๋ ฌ) - ํ์ด์ง๋ค์ด์ ๊ตฌํ (ํ ํ์ด์ง๋น 10๊ฐ) - ๊ฐ ๊ธฐ๋ก๋ง๋ค ์์ /์ญ์ ๋ฒํผ ํ์ - ์ด ์ ์ฝ์ก ๋ฐ ์ด ๊ธฐ๋ก ์ ํ์ - ์นดํ ๊ณ ๋ฆฌ ํํฐ๋ง ๊ธฐ๋ฅ (GET ํ๋ผ๋ฏธํฐ๋ก ์นดํ ๊ณ ๋ฆฌ ์ ํ) ## 2. `stats.php` (์นดํ ๊ณ ๋ฆฌ๋ณ ํต๊ณ ํ์ด์ง) - ์นดํ ๊ณ ๋ฆฌ๋ณ ์ ์ฝ ๊ธ์ก ํฉ๊ณ ์กฐํ (SQL GROUP BY) - ๊ฐ ์นดํ ๊ณ ๋ฆฌ๋ณ ๋น์จ ๊ณ์ฐ (์นดํ ๊ณ ๋ฆฌ ๊ธ์ก / ์ด ์ ์ฝ์ก * 100) - ํ ์ด๋ธ ํํ๋ก ํ์: - ์นดํ ๊ณ ๋ฆฌ๋ช , ์ ์ฝ ๊ธ์ก, ๋น์จ(%), ๊ธฐ๋ก ์ - ๊ฐ์ฅ ๋ง์ด ์ ์ฝํ ์นดํ ๊ณ ๋ฆฌ ๊ฐ์กฐ ํ์ - Chart.js๋ก ํ์ด ์ฐจํธ ์๊ฐํ (CDN ์ฌ์ฉ) ## 3. `delete_saving.php` (์ ์ฝ ๊ธฐ๋ก ์ญ์ ) - POST ์์ฒญ ์ฒ๋ฆฌ - CSRF ํ ํฐ ๊ฒ์ฆ - ์ญ์ ํ ๊ธฐ๋ก ID ํ์ธ - ํ์ฌ ์ฑ๋ฆฐ์ง์ ๊ธฐ๋ก์ธ์ง ๊ฒ์ฆ (challenge_id ํ์ธ) - DB์์ ์ญ์ ํ `history.php`๋ก ๋ฆฌ๋ค์ด๋ ํธ ## 4. `index.php` ๋ฐ `style.css` ์์ - ๋ฉ์ธ ํ์ด์ง์ '์ ์ฒด ๋ด์ญ ๋ณด๊ธฐ', 'ํต๊ณ ๋ณด๊ธฐ' ๋งํฌ ์ถ๊ฐ - ์นด๋ํ ๋ ์ด์์ ๊ฐ์ (ํธ๋ฒ ํจ๊ณผ, ๊ทธ๋ฆผ์) - ์นดํ ๊ณ ๋ฆฌ๋ณ ์์ ๊ตฌ๋ถ (์๋น: ์ฃผํฉ, ๊ตํต๋น: ํ๋, ์ผํ: ๋ถํ, ๊ตฌ๋ ๋ฃ: ๋ณด๋ผ, ๊ธฐํ: ํ์) - ์ญ์ ๋ฒํผ ์คํ์ผ (๋นจ๊ฐ์, ํ์ธ ๋ค์ด์ผ๋ก๊ทธ) ## 5. `script.js` (JavaScript ํ์ผ ์์ฑ) - ์ญ์ ๋ฒํผ ํด๋ฆญ ์ ํ์ธ ๋ค์ด์ผ๋ก๊ทธ ํ์ - Chart.js ํ์ด ์ฐจํธ ์ด๊ธฐํ (stats.php์ฉ) - ์งํ ๋ฐ ์ ๋๋ฉ์ด์ ํจ๊ณผ ์๊ตฌ์ฌํญ: - ํ์ด์ง๋ค์ด์ ์ LIMIT/OFFSET ์ฌ์ฉ - Chart.js๋ CDN์ผ๋ก๋ง ์ฌ์ฉ (`<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>`) - ์นดํ ๊ณ ๋ฆฌ ์์์ CSS ๋ณ์๋ก ์ ์ - ์ญ์ ์ JavaScript confirm() ์ฌ์ฉ - ๋ฐ์ํ ํ ์ด๋ธ (๋ชจ๋ฐ์ผ์์ ์ธ๋ก ์คํฌ๋กค)
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ ์ฝ ๋ด์ญ ์ ์ฒด ๋ชฉ๋ก ํ์ด์ง ์์ฑ (ํ์ด์ง๋ค์ด์ , ํํฐ๋ง). ์นดํ ๊ณ ๋ฆฌ๋ณ ํต๊ณ ํ์ด์ง ์์ฑ (ํ์ด ์ฐจํธ ํฌํจ). ์ญ์ ๊ธฐ๋ฅ ๊ตฌํ. ์๊ฐ์ ์ผ๋ก ๊ฐ์ ๋ UI (์นด๋ ํธ๋ฒ, ์นดํ ๊ณ ๋ฆฌ ์์ ๊ตฌ๋ถ).
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!