๐จ g5se ํฌ๋ฆฌ์์ดํฐ ์ํ ์๋ขฐ ๊ฒฌ์ ์๋ ์ฐ์ถ๊ธฐ - ํ๋ก์ ํธ ์ค์ฝํ ๊ธฐ๋ฐ ๊ฒฌ์ ์ ์์ฑ & ๊ณ ๊ฐ ์น์ธ ์ํฌํ๋ก์ฐ
g5se-creator-quote-calculator
๋ฌธ์ ์ค๋ช
๐ฏ ํ๋ก์ ํธ ๊ฐ์
ํ๋ฆฌ๋์ ํฌ๋ฆฌ์์ดํฐ(์ผ๋ฌ์คํธ๋ ์ดํฐ, ๋์์ด๋, ๊ฐ๋ฐ์ ๋ฑ)๊ฐ ๊ณ ๊ฐ์ผ๋ก๋ถํฐ ํ๋ก์ ํธ ์๋ขฐ๋ฅผ ๋ฐ์ ๋, ์์ ๋ฒ์์ ๋ฐ๋ผ ์๋์ผ๋ก ๊ฒฌ์ ์ ์ฐ์ถํ๊ณ ๊ณ ๊ฐ์ด ์จ๋ผ์ธ์ผ๋ก ๊ฒํ ยท์น์ธํ ์ ์๋ ํ์คํ ๊ฒฌ์ ๊ด๋ฆฌ ์์คํ ์ ๋๋ค. g5se์ ํด๋ ์๋ ๋ผ์ฐํ ๊ณผ modern shell์ ํ์ฉํ์ฌ ํฌ๋ฆฌ์์ดํฐ์ ๊ณ ๊ฐ ์์ธก ๋ชจ๋ ์ฌ์ฉํ๊ธฐ ํธํ UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
๐ ํ์ ๊ธฐ๋ฅ
1. ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋ (/creator-quote/dashboard)
- ์๋ขฐ ๋ชฉ๋ก (๋๊ธฐ/์น์ธ/๊ฑฐ์ /์๋ฃ ์ํ๋ณ ํํฐ)
- ๊ฐ ์๋ขฐ์ ๊ฒฌ์ ๊ธ์ก, ๊ณ ๊ฐ๋ช , ์ ์ถ์ผ, ์ํ ํ์
- ์ ๊ท ์๋ขฐ ์๋ฆผ ๋ฐฐ์ง (๋ฏธํ์ธ ๊ฑด์)
- ์๋ขฐ ์์ธ ๋ณด๊ธฐ ๋ชจ๋ฌ (๊ณ ๊ฐ ์ ๋ ฅ ๋ด์ฉ ์ ์ฒด ํ์)
2. ๊ฒฌ์ ์ฐ์ถ ์ค์ ํ์ด์ง (/creator-quote/pricing)
- ์์ ์ ํ๋ณ ๊ธฐ๋ณธ ๋จ๊ฐ ์ค์ (์: ์บ๋ฆญํฐ ๋์์ธ, ๋ก๊ณ , ์น ํ์ด์ง, ์ผ๋ฌ์คํธ ๋ฑ)
- ์ถ๊ฐ ์ต์ ๋จ๊ฐ (์: ์์ ํ์, ๊ธํ๋ฃ, ์์ ์ ์ด์ฉ๊ถ, ์์ค ํ์ผ ์ ๊ณต ๋ฑ)
- ๋์ด๋๋ณ ๋ฐฐ์จ (๊ฐ๋จ/๋ณดํต/๋ณต์ก 1.0x/1.5x/2.0x)
- ์ค์ ์ ์ฅ ์ SQLite์ JSON ํํ๋ก ์ ์ฅ
3. ๊ณ ๊ฐ ์๋ขฐ ์ ์ ํผ (/creator-quote/request)
- ํ๋ก์ ํธ ์ ๋ชฉ, ์์ธ ์ค๋ช (textarea)
- ์์ ์ ํ ์ ํ (๋๋กญ๋ค์ด)
- ๋์ด๋ ์ ํ (๋ผ๋์ค ๋ฒํผ: ๊ฐ๋จ/๋ณดํต/๋ณต์ก)
- ์ถ๊ฐ ์ต์ ์ฒดํฌ๋ฐ์ค (์ฌ๋ฌ ๊ฐ ์ ํ ๊ฐ๋ฅ)
- ํฌ๋ง ๋ฉ๊ธฐ์ผ (date picker)
- ์ฐธ๊ณ ์ด๋ฏธ์ง ์
๋ก๋ (์ต๋ 3์ฅ, ํ์ผ์
G5_DATA_PATH/modules/creator-quote/uploads/ํ์์ ์ ์ฅ) - ๊ณ ๊ฐ ์ด๋ฉ์ผ (๊ฒฌ์ ์ ๋ฐ์ก์ฉ)
- ์ ์ถ ์ ์๋ ๊ฒฌ์ ๊ณ์ฐ ํ DB ์ ์ฅ
4. ์๋ ๊ฒฌ์ ์ฐ์ถ ๋ก์ง
- ๊ธฐ๋ณธ ๋จ๊ฐ ร ๋์ด๋ ๋ฐฐ์จ + ์ถ๊ฐ ์ต์ ํฉ๊ณ = ์ต์ข ๊ฒฌ์
- ์: ์บ๋ฆญํฐ ๋์์ธ(50,000์) ร ๋ณต์ก(2.0x) + ์์ 3ํ(10,000์) + ์์ ์ ์ด์ฉ๊ถ(30,000์) = 140,000์
- ๊ฒฌ์ ๋ด์ญ์ ํญ๋ชฉ๋ณ๋ก ํ์ (ํฌ๋ช ์ฑ)
5. ๊ณ ๊ฐ ๊ฒฌ์ ํ์ธ ํ์ด์ง (/creator-quote/view/[id])
- ๋์ segment๋ก ์๋ขฐ ID ์บก์ฒ
- ํ๋ก์ ํธ ์ ๋ณด, ๊ฒฌ์ ๋ด์ญ, ์ต์ข ๊ธ์ก ํ์
- ์น์ธ/๊ฑฐ์ ๋ฒํผ (ํด๋ฆญ ์ ์ํ ์ ๋ฐ์ดํธ)
- ๊ฑฐ์ ์ ์ฌ์ ์ ๋ ฅ (optional textarea)
- ์น์ธ ์ "๊ฒฌ์ ์๊ฐ ์น์ธ๋์์ต๋๋ค" ํ์ธ ๋ฉ์์ง
6. ๊ฒฌ์ ์ PDF ๋ค์ด๋ก๋ (๋ณด๋์ค)
- PHP์ TCPDF ๋๋ mPDF ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ (CDN ์์ด ์์ PHP)
- ๊ฒฌ์ ๋ด์ญ์ ํ ์ด๋ธ ํ์์ผ๋ก PDF ์์ฑ
- ๋ค์ด๋ก๋ ๋งํฌ๋ ๊ณ ๊ฐ ํ์ธ ํ์ด์ง์ ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋์ ํ์
7. ์ด๋ฉ์ผ ์๋ฆผ (์ ํ)
- ๊ณ ๊ฐ์ด ์๋ขฐ ์ ์ถ ์ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์๋ฆผ (PHP
mail()ํจ์) - ๊ณ ๊ฐ์ด ๊ฒฌ์ ์น์ธ/๊ฑฐ์ ์ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์๋ฆผ
๐จ ๋์์ธ ์๊ตฌ์ฌํญ
- modern shell ์ฌ์ฉ (nav, footer, ๋คํฌ๋ชจ๋ ํ ๊ธ ์๋ ํฌํจ)
- ๋์์ธ ํ ํฐ (
--m-surface,--m-text,--m-shadow๋ฑ) ํ์ฉ - ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋๋ ํ ์ด๋ธ ๋ทฐ (์ํ๋ณ ์์ ๋ฐฐ์ง)
- ๊ณ ๊ฐ ์ ์ ํผ์ ๋จ๊ณ๋ณ ์น์ (ํ๋ก์ ํธ ์ ๋ณด โ ์ต์ ์ ํ โ ์ฐ๋ฝ์ฒ)
- ๊ฒฌ์ ํ์ธ ํ์ด์ง๋ ์ธ๋ณด์ด์ค ์คํ์ผ ๋ ์ด์์ (ํญ๋ชฉ๋ณ ๊ธ์ก ์ฐ์ธก ์ ๋ ฌ)
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ (ํ ์ด๋ธ์ ์นด๋ ํํ๋ก ์ ํ)
๐ ๊ธฐ์ ์คํ
- g5se (PHP 8.x ๊ธฐ๋ฐ CMS)
- SQLite (๋ชจ๋ ์ ์ฉ DB:
G5_DATA_PATH/modules/creator-quote.sqlite) - ์์ HTML/CSS/JS (์ธ๋ถ ํ๋ ์์ํฌ ์์)
- modern shell (Pretendard ํฐํธ, ๋์์ธ ํ ํฐ, ๋คํฌ๋ชจ๋)
๐ ๋ณด๋์ค ๊ธฐ๋ฅ
- ๊ฒฌ์ ์ ํจ๊ธฐ๊ฐ ์ค์ (์: 7์ผ ํ ์๋ ๋ง๋ฃ)
- ๊ณ ๊ฐ๋ณ ์๋ขฐ ํ์คํ ๋ฆฌ (์ด๋ฉ์ผ ๊ธฐ์ค)
- ์๊ฐ ์์ต ํต๊ณ ์ฐจํธ (์น์ธ๋ ๊ฒฌ์ ํฉ๊ณ)
- ๊ฒฌ์ ํ ํ๋ฆฟ ์ ์ฅ (์์ฃผ ์ฐ๋ ์กฐํฉ ํ๋ฆฌ์ )
- ๊ณ ๊ฐ ํผ๋๋ฐฑ ์์ง (์น์ธ ํ ๋ง์กฑ๋ ๋ณ์ )
โ ๏ธ ์ ์ฝ ์กฐ๊ฑด
- g5se ๋ณธ์ฒด DB์ ํ ์ด๋ธ ์ถ๊ฐ ๊ธ์ง (๋ชจ๋ ์ ์ฉ SQLite ์ฌ์ฉ)
- ์ธ๋ถ npm/composer ํจํค์ง ๊ธ์ง (์์ PHP)
- WebSocket/์ค์๊ฐ ์ฑํ ๊ธ์ง (HTTP ๊ธฐ๋ฐ ํด๋ง ๋๋ ์๋ก๊ณ ์นจ)
- ํ์ผ ์
๋ก๋๋
move_uploaded_file()๋ก ์ฒ๋ฆฌ (๋ณด์ ๊ฒ์ฆ ํ์)
์ ์ฝ ์กฐ๊ฑด
- โข g5se ์ค์น๋ณธ ์์์ ์์ (๋ณ๋ ํด๋ ์์ฑ ๊ธ์ง)
- โข ํด๋ ์๋ ๋ผ์ฐํ ์ฌ์ฉ (`app/modules/creator-quote/` ํ์)
- โข modern shell ํ์ ์ ์ฉ (`_head.inc.php`, `m-shell`, `_nav.inc.php`, `_footer.inc.php`)
- โข ๋์์ธ ํ ํฐ ์ฌ์ฉ (`--m-surface`, `--m-text`, `--m-shadow` ๋ฑ)
- โข ๋ชจ๋ ์ ์ฉ SQLite DB (`G5_DATA_PATH/modules/creator-quote.sqlite`)
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
- โข `htmlspecialchars()`๋ก XSS ๋ฐฉ์ง
- โข ํ์ผ ์ ๋ก๋ ์ ํ์ฅ์ยทMIME ๊ฒ์ฆ (ํ์ดํธ๋ฆฌ์คํธ)
- โข ์ธ๋ถ CDN/npm/composer ์ฌ์ฉ ๊ธ์ง
- โข WebSocket/์ค์๊ฐ ๊ธฐ๋ฅ ๊ธ์ง
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
์ด์ ํฌ๋ฆฌ์์ดํฐ ๊ฒฌ์ ๊ด๋ฆฌ ์์คํ
์ ๊ณจ๊ฒฉ์ ๋ง๋ค๊ฒ ์ต๋๋ค.
1. ํด๋ ๊ตฌ์กฐ ์์ฑ:
```bash
mkdir -p app/modules/creator-quote/dashboard
mkdir -p app/modules/creator-quote/pricing
mkdir -p app/modules/creator-quote/request
mkdir -p app/modules/creator-quote/view
```
2. `app/modules/creator-quote/index.php` ์์ฑ (๋ฉ์ธ ๋๋ฉ ํ์ด์ง):
```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: 48px 20px;">
<div style="max-width: 1200px; margin: 0 auto; text-align: center;">
<h1 style="font-size: 2.5rem; font-weight: 700; margin-bottom: 16px; color: var(--m-text);">๐ผ ํฌ๋ฆฌ์์ดํฐ ๊ฒฌ์ ์๋ ์ฐ์ถ ์์คํ
</h1>
<p style="font-size: 1.125rem; color: var(--m-text-muted); margin-bottom: 48px;">ํ๋ก์ ํธ ๋ฒ์์ ๋ฐ๋ผ ์๋์ผ๋ก ๊ฒฌ์ ์ ๊ณ์ฐํ๊ณ ๊ณ ๊ฐ ์น์ธ๊น์ง ํ ๋ฒ์</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; margin-top: 48px;">
<a href="/creator-quote/dashboard" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 32px; border-radius: var(--m-radius-lg); text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='translateY(-4px)'" onmouseout="this.style.transform='translateY(0)'">
<div style="font-size: 2rem; margin-bottom: 12px;">๐</div>
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 8px;">ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋</h3>
<p style="opacity: 0.9; font-size: 0.875rem;">์๋ขฐ ๊ด๋ฆฌ & ๊ฒฌ์ ํํฉ</p>
</a>
<a href="/creator-quote/pricing" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 32px; border-radius: var(--m-radius-lg); text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='translateY(-4px)'" onmouseout="this.style.transform='translateY(0)'">
<div style="font-size: 2rem; margin-bottom: 12px;">๐ฐ</div>
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 8px;">๊ฐ๊ฒฉ ์ค์ </h3>
<p style="opacity: 0.9; font-size: 0.875rem;">์์
์ ํ๋ณ ๋จ๊ฐ ๊ด๋ฆฌ</p>
</a>
<a href="/creator-quote/request" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 32px; border-radius: var(--m-radius-lg); text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='translateY(-4px)'" onmouseout="this.style.transform='translateY(0)'">
<div style="font-size: 2rem; margin-bottom: 12px;">๐</div>
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 8px;">ํ๋ก์ ํธ ์๋ขฐํ๊ธฐ</h3>
<p style="opacity: 0.9; font-size: 0.875rem;">๊ณ ๊ฐ์ฉ ๊ฒฌ์ ์์ฒญ ํผ</p>
</a>
</div>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
3. SQLite DB ์ด๊ธฐํ ํจ์ (`app/modules/creator-quote/_db.php`):
```php
<?php
if (!defined('_GNUBOARD_')) exit;
function get_creator_quote_db() {
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$db_path = G5_DATA_PATH.'/modules/creator-quote.sqlite';
$pdo = new PDO('sqlite:'.$db_path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// ๊ฐ๊ฒฉ ์ค์ ํ
์ด๋ธ
$pdo->exec("
CREATE TABLE IF NOT EXISTS pricing (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_type TEXT NOT NULL,
base_price INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
// ์ถ๊ฐ ์ต์
ํ
์ด๋ธ
$pdo->exec("
CREATE TABLE IF NOT EXISTS options (
id INTEGER PRIMARY KEY AUTOINCREMENT,
option_name TEXT NOT NULL,
option_price INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
// ์๋ขฐ ํ
์ด๋ธ
$pdo->exec("
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_title TEXT NOT NULL,
description TEXT,
work_type TEXT NOT NULL,
difficulty TEXT NOT NULL,
deadline DATE,
customer_email TEXT NOT NULL,
selected_options TEXT,
reference_images TEXT,
total_quote INTEGER NOT NULL,
status TEXT DEFAULT 'pending',
rejection_reason TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
return $pdo;
}
?>
```
์ด ๋จ๊ณ๋ฅผ ์คํํ๋ฉด ๋ฉ์ธ ๋๋ฉ ํ์ด์ง์ DB ์ด๊ธฐํ ํจ์๊ฐ ์ค๋น๋ฉ๋๋ค. `/creator-quote`๋ก ์ ์ํ์ฌ 3๊ฐ์ ์นด๋๊ฐ ํ์๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ฉ์ธ ๋๋ฉ ํ์ด์ง๊ฐ modern shell ๋์์ธ์ผ๋ก ๋ ๋๋ง๋๊ณ , 3๊ฐ์ gradient ์นด๋(๋์๋ณด๋/๊ฐ๊ฒฉ ์ค์ /์๋ขฐํ๊ธฐ)๊ฐ ํ์๋ฉ๋๋ค. SQLite DB ํ์ผ์ด `data/modules/creator-quote.sqlite`์ ์์ฑ๋๋ฉฐ, 3๊ฐ ํ ์ด๋ธ(pricing, options, requests)์ด ์๋์ผ๋ก ๋ง๋ค์ด์ง๋๋ค.
๊ฐ๊ฒฉ ์ค์ ํ์ด์ง๋ฅผ ๋ง๋ค์ด ํฌ๋ฆฌ์์ดํฐ๊ฐ ์์
์ ํ๋ณ ๋จ๊ฐ์ ์ถ๊ฐ ์ต์
์ ๊ด๋ฆฌํ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค.
`app/modules/creator-quote/pricing/index.php` ์์ฑ:
```php
<?php
include_once('./_common.php');
require_once('../_db.php');
$pdo = get_creator_quote_db();
$message = '';
// POST ์ฒ๋ฆฌ: ์์
์ ํ ์ถ๊ฐ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_work_type'])) {
$work_type = trim($_POST['work_type']);
$base_price = intval($_POST['base_price']);
if ($work_type && $base_price > 0) {
$stmt = $pdo->prepare("INSERT INTO pricing (work_type, base_price) VALUES (?, ?)");
$stmt->execute([$work_type, $base_price]);
$message = '์์
์ ํ์ด ์ถ๊ฐ๋์์ต๋๋ค.';
}
}
// POST ์ฒ๋ฆฌ: ์ถ๊ฐ ์ต์
์ถ๊ฐ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_option'])) {
$option_name = trim($_POST['option_name']);
$option_price = intval($_POST['option_price']);
if ($option_name && $option_price > 0) {
$stmt = $pdo->prepare("INSERT INTO options (option_name, option_price) VALUES (?, ?)");
$stmt->execute([$option_name, $option_price]);
$message = '์ถ๊ฐ ์ต์
์ด ๋ฑ๋ก๋์์ต๋๋ค.';
}
}
// ์ญ์ ์ฒ๋ฆฌ
if (isset($_GET['delete_pricing'])) {
$id = intval($_GET['delete_pricing']);
$pdo->prepare("DELETE FROM pricing WHERE id = ?")->execute([$id]);
header('Location: /creator-quote/pricing');
exit;
}
if (isset($_GET['delete_option'])) {
$id = intval($_GET['delete_option']);
$pdo->prepare("DELETE FROM options WHERE id = ?")->execute([$id]);
header('Location: /creator-quote/pricing');
exit;
}
// ๋ฐ์ดํฐ ์กฐํ
$work_types = $pdo->query("SELECT * FROM pricing ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
$options = $pdo->query("SELECT * FROM options ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.pricing-section {
background: var(--m-surface);
padding: 24px;
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
margin-bottom: 32px;
}
.pricing-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.pricing-table th,
.pricing-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
.pricing-table th {
font-weight: 600;
color: var(--m-text);
}
.pricing-table td {
color: var(--m-text-muted);
}
.btn-delete {
background: #ef4444;
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: 8px;
color: var(--m-text);
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 8px;
font-size: 1rem;
background: var(--m-surface);
color: var(--m-text);
}
.btn-submit {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
}
</style>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 48px 20px; max-width: 1200px; margin: 0 auto;">
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 32px; color: var(--m-text);">๐ฐ ๊ฐ๊ฒฉ ์ค์ ๊ด๋ฆฌ</h1>
<?php if ($message): ?>
<div style="background: #10b981; color: white; padding: 16px; border-radius: 8px; margin-bottom: 24px;">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<!-- ์์
์ ํ ์ถ๊ฐ -->
<div class="pricing-section">
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 16px; color: var(--m-text);">์์
์ ํ ์ถ๊ฐ</h2>
<form method="POST">
<div style="display: grid; grid-template-columns: 2fr 1fr auto; gap: 12px; align-items: end;">
<div class="form-group" style="margin-bottom: 0;">
<label>์์
์ ํ</label>
<input type="text" name="work_type" placeholder="์: ์บ๋ฆญํฐ ๋์์ธ" required>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label>๊ธฐ๋ณธ ๋จ๊ฐ (์)</label>
<input type="number" name="base_price" placeholder="50000" required>
</div>
<button type="submit" name="add_work_type" class="btn-submit">์ถ๊ฐ</button>
</div>
</form>
<table class="pricing-table">
<thead>
<tr>
<th>์์
์ ํ</th>
<th>๊ธฐ๋ณธ ๋จ๊ฐ</th>
<th>๊ด๋ฆฌ</th>
</tr>
</thead>
<tbody>
<?php foreach ($work_types as $wt): ?>
<tr>
<td><?php echo htmlspecialchars($wt['work_type']); ?></td>
<td><?php echo number_format($wt['base_price']); ?>์</td>
<td><a href="?delete_pricing=<?php echo $wt['id']; ?>" class="btn-delete" onclick="return confirm('์ญ์ ํ์๊ฒ ์ต๋๊น?')">์ญ์ </a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- ์ถ๊ฐ ์ต์
์ถ๊ฐ -->
<div class="pricing-section">
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 16px; color: var(--m-text);">์ถ๊ฐ ์ต์
๋ฑ๋ก</h2>
<form method="POST">
<div style="display: grid; grid-template-columns: 2fr 1fr auto; gap: 12px; align-items: end;">
<div class="form-group" style="margin-bottom: 0;">
<label>์ต์
๋ช
</label>
<input type="text" name="option_name" placeholder="์: ์์ 3ํ" required>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label>์ถ๊ฐ ๊ธ์ก (์)</label>
<input type="number" name="option_price" placeholder="10000" required>
</div>
<button type="submit" name="add_option" class="btn-submit">์ถ๊ฐ</button>
</div>
</form>
<table class="pricing-table">
<thead>
<tr>
<th>์ต์
๋ช
</th>
<th>์ถ๊ฐ ๊ธ์ก</th>
<th>๊ด๋ฆฌ</th>
</tr>
</thead>
<tbody>
<?php foreach ($options as $opt): ?>
<tr>
<td><?php echo htmlspecialchars($opt['option_name']); ?></td>
<td>+<?php echo number_format($opt['option_price']); ?>์</td>
<td><a href="?delete_option=<?php echo $opt['id']; ?>" class="btn-delete" onclick="return confirm('์ญ์ ํ์๊ฒ ์ต๋๊น?')">์ญ์ </a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="text-align: center; margin-top: 32px;">
<a href="/creator-quote" style="color: var(--m-text-muted); text-decoration: none;">โ ๋ฉ์ธ์ผ๋ก ๋์๊ฐ๊ธฐ</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ํ์ด์ง์์ ์์
์ ํ(์: ๋ก๊ณ ๋์์ธ, ์ผ๋ฌ์คํธ)๊ณผ ์ถ๊ฐ ์ต์
(์: ๊ธํ๋ฃ, ์์ ํ์)์ CRUDํ ์ ์์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฐ๊ฒฉ ์ค์ ํ์ด์ง(`/creator-quote/pricing`)๊ฐ ๋ ๋๋ง๋๊ณ , ์์ ์ ํ๊ณผ ์ถ๊ฐ ์ต์ ์ ์ถ๊ฐ/์ญ์ ํ ์ ์๋ ํผ๊ณผ ํ ์ด๋ธ์ด ํ์๋ฉ๋๋ค. ๋ฐ์ดํฐ๋ SQLite์ ์ ์ฅ๋๋ฉฐ, ์ถ๊ฐ ํ ์ฆ์ ๋ชฉ๋ก์ ๋ฐ์๋ฉ๋๋ค.
๊ณ ๊ฐ์ฉ ํ๋ก์ ํธ ์๋ขฐ ์ ์ ํผ์ ๋ง๋ค์ด ์๋ ๊ฒฌ์ ์ฐ์ถ์ด ์ด๋ฃจ์ด์ง๋๋ก ํ๊ฒ ์ต๋๋ค.
`app/modules/creator-quote/request/index.php` ์์ฑ:
```php
<?php
include_once('./_common.php');
require_once('../_db.php');
$pdo = get_creator_quote_db();
$message = '';
$quote_id = null;
// POST ์ฒ๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$project_title = trim($_POST['project_title']);
$description = trim($_POST['description']);
$work_type = trim($_POST['work_type']);
$difficulty = $_POST['difficulty']; // simple, normal, complex
$deadline = $_POST['deadline'];
$customer_email = trim($_POST['customer_email']);
$selected_options = isset($_POST['options']) ? $_POST['options'] : [];
// ํ์ผ ์
๋ก๋ ์ฒ๋ฆฌ
$upload_dir = G5_DATA_PATH.'/modules/creator-quote/uploads/';
@mkdir($upload_dir, 0707, true);
$uploaded_files = [];
if (isset($_FILES['reference_images'])) {
foreach ($_FILES['reference_images']['tmp_name'] as $key => $tmp_name) {
if ($_FILES['reference_images']['error'][$key] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($_FILES['reference_images']['name'][$key], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (in_array($ext, $allowed)) {
$filename = uniqid().'.'.$ext;
move_uploaded_file($tmp_name, $upload_dir.$filename);
$uploaded_files[] = $filename;
}
}
}
}
// ๊ฒฌ์ ๊ณ์ฐ
$stmt = $pdo->prepare("SELECT base_price FROM pricing WHERE work_type = ?");
$stmt->execute([$work_type]);
$base_price = $stmt->fetchColumn();
$difficulty_multiplier = ['simple' => 1.0, 'normal' => 1.5, 'complex' => 2.0];
$total = $base_price * $difficulty_multiplier[$difficulty];
// ์ถ๊ฐ ์ต์
ํฉ์ฐ
$option_total = 0;
if (!empty($selected_options)) {
$placeholders = implode(',', array_fill(0, count($selected_options), '?'));
$stmt = $pdo->prepare("SELECT SUM(option_price) FROM options WHERE id IN ($placeholders)");
$stmt->execute($selected_options);
$option_total = $stmt->fetchColumn();
}
$total += $option_total;
// DB ์ ์ฅ
$stmt = $pdo->prepare("
INSERT INTO requests
(project_title, description, work_type, difficulty, deadline, customer_email, selected_options, reference_images, total_quote)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$project_title,
$description,
$work_type,
$difficulty,
$deadline,
$customer_email,
json_encode($selected_options),
json_encode($uploaded_files),
$total
]);
$quote_id = $pdo->lastInsertId();
$message = '๊ฒฌ์ ์์ฒญ์ด ์ ์๋์์ต๋๋ค!';
}
// ์์
์ ํ ๋ชฉ๋ก
$work_types = $pdo->query("SELECT * FROM pricing ORDER BY work_type")->fetchAll(PDO::FETCH_ASSOC);
$options = $pdo->query("SELECT * FROM options ORDER BY option_name")->fetchAll(PDO::FETCH_ASSOC);
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.request-form {
background: var(--m-surface);
padding: 32px;
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
max-width: 800px;
margin: 0 auto;
}
.form-section {
margin-bottom: 32px;
}
.form-section h3 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 16px;
color: var(--m-text);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: 8px;
color: var(--m-text);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 8px;
font-size: 1rem;
background: var(--m-surface);
color: var(--m-text);
}
.form-group textarea {
min-height: 120px;
resize: vertical;
}
.radio-group {
display: flex;
gap: 16px;
}
.radio-group label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.checkbox-group {
display: grid;
gap: 12px;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.btn-submit {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 8px;
cursor: pointer;
font-size: 1.125rem;
font-weight: 600;
width: 100%;
}
</style>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 48px 20px;">
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 32px; text-align: center; color: var(--m-text);">๐ ํ๋ก์ ํธ ์๋ขฐํ๊ธฐ</h1>
<?php if ($message): ?>
<div style="background: #10b981; color: white; padding: 20px; border-radius: 8px; margin-bottom: 24px; text-align: center; max-width: 800px; margin-left: auto; margin-right: auto;">
<?php echo htmlspecialchars($message); ?><br>
<a href="/creator-quote/view/<?php echo $quote_id; ?>" style="color: white; text-decoration: underline; font-weight: 600; margin-top: 8px; display: inline-block;">๊ฒฌ์ ์ ํ์ธํ๊ธฐ โ</a>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data" class="request-form">
<!-- ํ๋ก์ ํธ ์ ๋ณด -->
<div class="form-section">
<h3>ํ๋ก์ ํธ ์ ๋ณด</h3>
<div class="form-group">
<label>ํ๋ก์ ํธ ์ ๋ชฉ *</label>
<input type="text" name="project_title" placeholder="์: ๋ธ๋๋ ๋ก๊ณ ๋์์ธ" required>
</div>
<div class="form-group">
<label>์์ธ ์ค๋ช
*</label>
<textarea name="description" placeholder="ํ๋ก์ ํธ ๋ด์ฉ์ ์์ธํ ์ค๋ช
ํด์ฃผ์ธ์" required></textarea>
</div>
</div>
<!-- ์์
์ ํ & ๋์ด๋ -->
<div class="form-section">
<h3>์์
์ ํ & ๋์ด๋</h3>
<div class="form-group">
<label>์์
์ ํ *</label>
<select name="work_type" required>
<option value="">์ ํํ์ธ์</option>
<?php foreach ($work_types as $wt): ?>
<option value="<?php echo htmlspecialchars($wt['work_type']); ?>">
<?php echo htmlspecialchars($wt['work_type']); ?> (๊ธฐ๋ณธ <?php echo number_format($wt['base_price']); ?>์)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>๋์ด๋ *</label>
<div class="radio-group">
<label><input type="radio" name="difficulty" value="simple" required> ๊ฐ๋จ (ร1.0)</label>
<label><input type="radio" name="difficulty" value="normal" checked> ๋ณดํต (ร1.5)</label>
<label><input type="radio" name="difficulty" value="complex"> ๋ณต์ก (ร2.0)</label>
</div>
</div>
</div>
<!-- ์ถ๊ฐ ์ต์
-->
<?php if (!empty($options)): ?>
<div class="form-section">
<h3>์ถ๊ฐ ์ต์
</h3>
<div class="checkbox-group">
<?php foreach ($options as $opt): ?>
<label>
<input type="checkbox" name="options[]" value="<?php echo $opt['id']; ?>">
<?php echo htmlspecialchars($opt['option_name']); ?> (+<?php echo number_format($opt['option_price']); ?>์)
</label>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- ๋ฉ๊ธฐ์ผ & ์ฐ๋ฝ์ฒ -->
<div class="form-section">
<h3>๋ฉ๊ธฐ์ผ & ์ฐ๋ฝ์ฒ</h3>
<div class="form-group">
<label>ํฌ๋ง ๋ฉ๊ธฐ์ผ</label>
<input type="date" name="deadline">
</div>
<div class="form-group">
<label>์ด๋ฉ์ผ *</label>
<input type="email" name="customer_email" placeholder="your@email.com" required>
</div>
</div>
<!-- ์ฐธ๊ณ ์ด๋ฏธ์ง -->
<div class="form-section">
<h3>์ฐธ๊ณ ์ด๋ฏธ์ง (์ต๋ 3์ฅ)</h3>
<div class="form-group">
<input type="file" name="reference_images[]" accept="image/*" multiple>
</div>
</div>
<button type="submit" class="btn-submit">๊ฒฌ์ ์์ฒญํ๊ธฐ</button>
</form>
<div style="text-align: center; margin-top: 32px;">
<a href="/creator-quote" style="color: var(--m-text-muted); text-decoration: none;">โ ๋ฉ์ธ์ผ๋ก ๋์๊ฐ๊ธฐ</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ํผ์ ๊ณ ๊ฐ์ด ํ๋ก์ ํธ ์ ๋ณด๋ฅผ ์
๋ ฅํ๋ฉด ์๋์ผ๋ก ๊ฒฌ์ ์ ๊ณ์ฐํ์ฌ DB์ ์ ์ฅํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ณ ๊ฐ์ฉ ์๋ขฐ ํผ(`/creator-quote/request`)์ด ๋ ๋๋ง๋๊ณ , ํ๋ก์ ํธ ์ ๋ชฉ/์ค๋ช /์์ ์ ํ/๋์ด๋/์ถ๊ฐ ์ต์ /๋ฉ๊ธฐ์ผ/์ด๋ฉ์ผ/์ฐธ๊ณ ์ด๋ฏธ์ง๋ฅผ ์ ๋ ฅํ ์ ์์ต๋๋ค. ์ ์ถ ์ ๊ฒฌ์ ์ด ์๋ ๊ณ์ฐ๋์ด DB์ ์ ์ฅ๋๋ฉฐ, ๊ฒฌ์ ์ ํ์ธ ๋งํฌ๊ฐ ํ์๋ฉ๋๋ค.
๊ณ ๊ฐ์ด ๊ฒฌ์ ์ ํ์ธํ๊ณ ์น์ธ/๊ฑฐ์ ํ ์ ์๋ ํ์ด์ง๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค. ๋์ segment๋ฅผ ํ์ฉํฉ๋๋ค.
`app/modules/creator-quote/view/[id]/index.php` ์์ฑ:
```php
<?php
include_once('./_common.php');
require_once('../../_db.php');
$pdo = get_creator_quote_db();
$request_id = intval($_GET['id']);
$message = '';
// ์น์ธ/๊ฑฐ์ ์ฒ๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['approve'])) {
$stmt = $pdo->prepare("UPDATE requests SET status = 'approved', updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$request_id]);
$message = '๊ฒฌ์ ์ด ์น์ธ๋์์ต๋๋ค! ํฌ๋ฆฌ์์ดํฐ๊ฐ ๊ณง ์ฐ๋ฝ๋๋ฆด ์์ ์
๋๋ค.';
} elseif (isset($_POST['reject'])) {
$reason = trim($_POST['rejection_reason']);
$stmt = $pdo->prepare("UPDATE requests SET status = 'rejected', rejection_reason = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$reason, $request_id]);
$message = '๊ฒฌ์ ์ด ๊ฑฐ์ ๋์์ต๋๋ค. ์ฌ์ ๊ฐ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์ ๋ฌ๋์์ต๋๋ค.';
}
}
// ์๋ขฐ ์ ๋ณด ์กฐํ
$stmt = $pdo->prepare("SELECT * FROM requests WHERE id = ?");
$stmt->execute([$request_id]);
$request = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$request) {
die('์กด์ฌํ์ง ์๋ ๊ฒฌ์ ์์ฒญ์
๋๋ค.');
}
// ์์
์ ํ ์ ๋ณด
$stmt = $pdo->prepare("SELECT base_price FROM pricing WHERE work_type = ?");
$stmt->execute([$request['work_type']]);
$base_price = $stmt->fetchColumn();
// ๋์ด๋ ๋ฐฐ์จ
$difficulty_map = ['simple' => '๊ฐ๋จ (ร1.0)', 'normal' => '๋ณดํต (ร1.5)', 'complex' => '๋ณต์ก (ร2.0)'];
$difficulty_multiplier = ['simple' => 1.0, 'normal' => 1.5, 'complex' => 2.0];
// ์ถ๊ฐ ์ต์
์ ๋ณด
$selected_options = json_decode($request['selected_options'], true) ?: [];
$option_details = [];
if (!empty($selected_options)) {
$placeholders = implode(',', array_fill(0, count($selected_options), '?'));
$stmt = $pdo->prepare("SELECT * FROM options WHERE id IN ($placeholders)");
$stmt->execute($selected_options);
$option_details = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.quote-card {
background: var(--m-surface);
padding: 32px;
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
max-width: 900px;
margin: 0 auto;
}
.quote-header {
border-bottom: 2px solid rgba(0,0,0,0.1);
padding-bottom: 16px;
margin-bottom: 24px;
}
.quote-section {
margin-bottom: 24px;
}
.quote-section h3 {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 12px;
color: var(--m-text);
}
.quote-table {
width: 100%;
border-collapse: collapse;
}
.quote-table td {
padding: 12px 0;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
.quote-table td:last-child {
text-align: right;
font-weight: 600;
}
.total-row {
font-size: 1.25rem;
font-weight: 700;
color: #667eea;
}
.btn-group {
display: flex;
gap: 12px;
margin-top: 32px;
}
.btn {
flex: 1;
padding: 16px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
.btn-approve {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.btn-reject {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.status-badge {
display: inline-block;
padding: 6px 12px;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
}
.status-pending { background: #fbbf24; color: #78350f; }
.status-approved { background: #10b981; color: white; }
.status-rejected { background: #ef4444; color: white; }
</style>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 48px 20px;">
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 32px; text-align: center; color: var(--m-text);">๐ ๊ฒฌ์ ์</h1>
<?php if ($message): ?>
<div style="background: #10b981; color: white; padding: 20px; border-radius: 8px; margin-bottom: 24px; text-align: center; max-width: 900px; margin-left: auto; margin-right: auto;">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<div class="quote-card">
<div class="quote-header">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 style="font-size: 1.5rem; font-weight: 700; color: var(--m-text);"><?php echo htmlspecialchars($request['project_title']); ?></h2>
<span class="status-badge status-<?php echo $request['status']; ?>">
<?php echo ['pending' => '๋๊ธฐ์ค', 'approved' => '์น์ธ๋จ', 'rejected' => '๊ฑฐ์ ๋จ'][$request['status']]; ?>
</span>
</div>
<p style="color: var(--m-text-muted); margin-top: 8px;">์ ์์ผ: <?php echo date('Y-m-d H:i', strtotime($request['created_at'])); ?></p>
</div>
<div class="quote-section">
<h3>ํ๋ก์ ํธ ์์ธ</h3>
<p style="color: var(--m-text-muted); line-height: 1.6;"><?php echo nl2br(htmlspecialchars($request['description'])); ?></p>
</div>
<div class="quote-section">
<h3>๊ฒฌ์ ๋ด์ญ</h3>
<table class="quote-table">
<tr>
<td style="color: var(--m-text);">์์
์ ํ</td>
<td><?php echo htmlspecialchars($request['work_type']); ?></td>
</tr>
<tr>
<td style="color: var(--m-text);">๊ธฐ๋ณธ ๋จ๊ฐ</td>
<td><?php echo number_format($base_price); ?>์</td>
</tr>
<tr>
<td style="color: var(--m-text);">๋์ด๋</td>
<td><?php echo $difficulty_map[$request['difficulty']]; ?></td>
</tr>
<tr>
<td style="color: var(--m-text);">์์
๋น (๋จ๊ฐ ร ๋์ด๋)</td>
<td><?php echo number_format($base_price * $difficulty_multiplier[$request['difficulty']]); ?>์</td>
</tr>
<?php foreach ($option_details as $opt): ?>
<tr>
<td style="color: var(--m-text);"><?php echo htmlspecialchars($opt['option_name']); ?></td>
<td>+<?php echo number_format($opt['option_price']); ?>์</td>
</tr>
<?php endforeach; ?>
<tr class="total-row">
<td>์ต์ข
๊ฒฌ์ </td>
<td><?php echo number_format($request['total_quote']); ?>์</td>
</tr>
</table>
</div>
<?php if ($request['deadline']): ?>
<div class="quote-section">
<h3>ํฌ๋ง ๋ฉ๊ธฐ์ผ</h3>
<p style="color: var(--m-text-muted);"><?php echo date('Y๋
m์ d์ผ', strtotime($request['deadline'])); ?></p>
</div>
<?php endif; ?>
<?php if ($request['status'] === 'pending'): ?>
<form method="POST">
<div class="btn-group">
<button type="submit" name="approve" class="btn btn-approve">โ ๊ฒฌ์ ์น์ธ</button>
<button type="button" class="btn btn-reject" onclick="showRejectForm()">โ ๊ฒฌ์ ๊ฑฐ์ </button>
</div>
<div id="reject-form" style="display: none; margin-top: 16px;">
<textarea name="rejection_reason" placeholder="๊ฑฐ์ ์ฌ์ ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์ (์ ํ)" style="width: 100%; padding: 12px; border: 1px solid rgba(0,0,0,0.1); border-radius: 8px; min-height: 80px;"></textarea>
<button type="submit" name="reject" class="btn btn-reject" style="width: 100%; margin-top: 12px;">๊ฑฐ์ ํ์ </button>
</div>
</form>
<?php elseif ($request['status'] === 'rejected' && $request['rejection_reason']): ?>
<div style="background: #fef2f2; padding: 16px; border-radius: 8px; margin-top: 16px;">
<h4 style="font-weight: 600; color: #ef4444; margin-bottom: 8px;">๊ฑฐ์ ์ฌ์ </h4>
<p style="color: #7f1d1d;"><?php echo nl2br(htmlspecialchars($request['rejection_reason'])); ?></p>
</div>
<?php endif; ?>
</div>
<div style="text-align: center; margin-top: 32px;">
<a href="/creator-quote" style="color: var(--m-text-muted); text-decoration: none;">โ ๋ฉ์ธ์ผ๋ก ๋์๊ฐ๊ธฐ</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<script>
function showRejectForm() {
document.getElementById('reject-form').style.display = 'block';
}
</script>
<?php include_once('./tail.sub.php'); ?>
```
์ด ํ์ด์ง๋ `/creator-quote/view/{id}` ํ์์ผ๋ก ์ ๊ทผํ๋ฉฐ, ๊ฒฌ์ ๋ด์ญ๊ณผ ์น์ธ/๊ฑฐ์ ๋ฒํผ์ ์ ๊ณตํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ณ ๊ฐ ๊ฒฌ์ ํ์ธ ํ์ด์ง(`/creator-quote/view/1`)๊ฐ ๋ ๋๋ง๋๊ณ , ํ๋ก์ ํธ ์ ๋ณด, ๊ฒฌ์ ๋ด์ญ(์์ ๋น, ์ถ๊ฐ ์ต์ , ์ต์ข ๊ธ์ก), ์น์ธ/๊ฑฐ์ ๋ฒํผ์ด ํ์๋ฉ๋๋ค. ์น์ธ ์ ์ํ๊ฐ 'approved'๋ก, ๊ฑฐ์ ์ 'rejected'๋ก ๋ณ๊ฒฝ๋๋ฉฐ ์ฌ์ ๊ฐ ์ ์ฅ๋ฉ๋๋ค.
ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋๋ฅผ ๋ง๋ค์ด ์ ์๋ ์๋ขฐ๋ฅผ ๊ด๋ฆฌํ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค.
`app/modules/creator-quote/dashboard/index.php` ์์ฑ:
```php
<?php
include_once('./_common.php');
require_once('../_db.php');
$pdo = get_creator_quote_db();
// ํํฐ๋ง
$filter = isset($_GET['filter']) ? $_GET['filter'] : 'all';
$where = $filter === 'all' ? '' : "WHERE status = '".$pdo->quote($filter)."'";
$requests = $pdo->query("SELECT * FROM requests $where ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC);
// ํต๊ณ
$stats = $pdo->query("
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved,
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected,
SUM(CASE WHEN status = 'approved' THEN total_quote ELSE 0 END) as total_revenue
FROM requests
")->fetch(PDO::FETCH_ASSOC);
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 24px;
border-radius: var(--m-radius-lg);
text-align: center;
}
.stat-card h3 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
}
.stat-card p {
opacity: 0.9;
font-size: 0.875rem;
}
.filter-tabs {
display: flex;
gap: 8px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.filter-tabs a {
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 500;
transition: all 0.2s;
}
.filter-tabs a.active {
background: #667eea;
color: white;
}
.filter-tabs a:not(.active) {
background: var(--m-surface);
color: var(--m-text-muted);
}
.request-table {
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
overflow: hidden;
}
.request-table table {
width: 100%;
border-collapse: collapse;
}
.request-table th,
.request-table td {
padding: 16px;
text-align: left;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
.request-table th {
background: rgba(0,0,0,0.02);
font-weight: 600;
color: var(--m-text);
}
.request-table td {
color: var(--m-text-muted);
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
}
.status-pending { background: #fef3c7; color: #92400e; }
.status-approved { background: #d1fae5; color: #065f46; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.btn-view {
background: #667eea;
color: white;
padding: 6px 16px;
border-radius: 6px;
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
}
@media (max-width: 768px) {
.request-table {
overflow-x: auto;
}
}
</style>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 48px 20px; max-width: 1400px; margin: 0 auto;">
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 32px; color: var(--m-text);">๐ ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋</h1>
<!-- ํต๊ณ ์นด๋ -->
<div class="stats-grid">
<div class="stat-card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<h3><?php echo $stats['total']; ?></h3>
<p>์ ์ฒด ์๋ขฐ</p>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);">
<h3><?php echo $stats['pending']; ?></h3>
<p>๋๊ธฐ์ค</p>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
<h3><?php echo $stats['approved']; ?></h3>
<p>์น์ธ๋จ</p>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);">
<h3><?php echo $stats['rejected']; ?></h3>
<p>๊ฑฐ์ ๋จ</p>
</div>
<div class="stat-card" style="background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);">
<h3><?php echo number_format($stats['total_revenue']); ?>์</h3>
<p>์ด ์์ต (์น์ธ)</p>
</div>
</div>
<!-- ํํฐ ํญ -->
<div class="filter-tabs">
<a href="?filter=all" class="<?php echo $filter === 'all' ? 'active' : ''; ?>">์ ์ฒด</a>
<a href="?filter=pending" class="<?php echo $filter === 'pending' ? 'active' : ''; ?>">๋๊ธฐ์ค</a>
<a href="?filter=approved" class="<?php echo $filter === 'approved' ? 'active' : ''; ?>">์น์ธ๋จ</a>
<a href="?filter=rejected" class="<?php echo $filter === 'rejected' ? 'active' : ''; ?>">๊ฑฐ์ ๋จ</a>
</div>
<!-- ์๋ขฐ ๋ชฉ๋ก -->
<div class="request-table">
<table>
<thead>
<tr>
<th>ID</th>
<th>ํ๋ก์ ํธ๋ช
</th>
<th>์์
์ ํ</th>
<th>๊ณ ๊ฐ ์ด๋ฉ์ผ</th>
<th>๊ฒฌ์ ๊ธ์ก</th>
<th>์ํ</th>
<th>์ ์์ผ</th>
<th>๊ด๋ฆฌ</th>
</tr>
</thead>
<tbody>
<?php if (empty($requests)): ?>
<tr>
<td colspan="8" style="text-align: center; padding: 48px; color: var(--m-text-muted);">์๋ขฐ๊ฐ ์์ต๋๋ค.</td>
</tr>
<?php else: ?>
<?php foreach ($requests as $req): ?>
<tr>
<td style="font-weight: 600; color: var(--m-text);">#<?php echo $req['id']; ?></td>
<td style="color: var(--m-text);"><?php echo htmlspecialchars(mb_substr($req['project_title'], 0, 30)); ?></td>
<td><?php echo htmlspecialchars($req['work_type']); ?></td>
<td><?php echo htmlspecialchars($req['customer_email']); ?></td>
<td style="font-weight: 600; color: #667eea;"><?php echo number_format($req['total_quote']); ?>์</td>
<td>
<span class="status-badge status-<?php echo $req['status']; ?>">
<?php echo ['pending' => '๋๊ธฐ', 'approved' => '์น์ธ', 'rejected' => '๊ฑฐ์ '][$req['status']]; ?>
</span>
</td>
<td><?php echo date('Y-m-d', strtotime($req['created_at'])); ?></td>
<td>
<a href="/creator-quote/view/<?php echo $req['id']; ?>" class="btn-view">์์ธ</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div style="text-align: center; margin-top: 32px;">
<a href="/creator-quote" style="color: var(--m-text-muted); text-decoration: none;">โ ๋ฉ์ธ์ผ๋ก ๋์๊ฐ๊ธฐ</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ๋์๋ณด๋๋ ์๋ขฐ ํต๊ณ(์ ์ฒด/๋๊ธฐ/์น์ธ/๊ฑฐ์ /์ด ์์ต)์ ํํฐ๋ง๋ ์๋ขฐ ๋ชฉ๋ก์ ํ
์ด๋ธ๋ก ํ์ํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋(`/creator-quote/dashboard`)๊ฐ ๋ ๋๋ง๋๊ณ , ํต๊ณ ์นด๋(์ ์ฒด/๋๊ธฐ/์น์ธ/๊ฑฐ์ /์ด ์์ต)์ ์๋ขฐ ๋ชฉ๋ก ํ ์ด๋ธ์ด ํ์๋ฉ๋๋ค. ํํฐ ํญ์ผ๋ก ์ํ๋ณ ํํฐ๋ง์ด ๊ฐ๋ฅํ๋ฉฐ, ๊ฐ ์๋ขฐ์ ์์ธ ํ์ด์ง๋ก ์ด๋ํ ์ ์์ต๋๋ค.
๊ฒฌ์ ์ PDF ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ฌ ๊ณ ๊ฐ๊ณผ ํฌ๋ฆฌ์์ดํฐ๊ฐ ๊ฒฌ์ ์๋ฅผ ์ ์ฅํ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค. PHP์ TCPDF ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๋จผ์ TCPDF๋ฅผ ์๋์ผ๋ก ๋ค์ด๋ก๋ํ์ฌ ํ๋ก์ ํธ์ ํฌํจ์ํต๋๋ค (composer ์ฌ์ฉ ๊ธ์ง):
1. https://github.com/tecnickcom/TCPDF/releases ์์ ์ต์ ๋ฒ์ ๋ค์ด๋ก๋
2. `app/modules/creator-quote/vendor/tcpdf/` ํด๋์ ์์ถ ํด์
๊ทธ ๋ค์ PDF ์์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ญ๋๋ค.
`app/modules/creator-quote/download-pdf.php` ์์ฑ:
```php
<?php
include_once('./_common.php');
require_once('./_db.php');
$request_id = intval($_GET['id']);
$pdo = get_creator_quote_db();
// ์๋ขฐ ์ ๋ณด ์กฐํ
$stmt = $pdo->prepare("SELECT * FROM requests WHERE id = ?");
$stmt->execute([$request_id]);
$request = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$request) {
die('์กด์ฌํ์ง ์๋ ๊ฒฌ์ ์
๋๋ค.');
}
// ์์
์ ํ ์ ๋ณด
$stmt = $pdo->prepare("SELECT base_price FROM pricing WHERE work_type = ?");
$stmt->execute([$request['work_type']]);
$base_price = $stmt->fetchColumn();
// ๋์ด๋ ๋ฐฐ์จ
$difficulty_map = ['simple' => '๊ฐ๋จ (ร1.0)', 'normal' => '๋ณดํต (ร1.5)', 'complex' => '๋ณต์ก (ร2.0)'];
$difficulty_multiplier = ['simple' => 1.0, 'normal' => 1.5, 'complex' => 2.0];
// ์ถ๊ฐ ์ต์
์ ๋ณด
$selected_options = json_decode($request['selected_options'], true) ?: [];
$option_details = [];
if (!empty($selected_options)) {
$placeholders = implode(',', array_fill(0, count($selected_options), '?'));
$stmt = $pdo->prepare("SELECT * FROM options WHERE id IN ($placeholders)");
$stmt->execute($selected_options);
$option_details = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// TCPDF ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก๋
require_once(__DIR__.'/vendor/tcpdf/tcpdf.php');
// PDF ์์ฑ
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->SetCreator('Creator Quote System');
$pdf->SetAuthor('Creator');
$pdf->SetTitle('๊ฒฌ์ ์ - '.$request['project_title']);
$pdf->SetSubject('ํ๋ก์ ํธ ๊ฒฌ์ ์');
// ํค๋/ํธํฐ ์ ๊ฑฐ
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->SetMargins(15, 15, 15);
$pdf->SetAutoPageBreak(true, 15);
$pdf->AddPage();
// ํ๊ธ ํฐํธ ์ค์ (๊ธฐ๋ณธ ๋ด์ฅ ํฐํธ ์ฌ์ฉ)
$pdf->SetFont('helvetica', '', 10);
// HTML ์ฝํ
์ธ
$html = '
<h1 style="text-align: center; color: #667eea; margin-bottom: 20px;">ํ๋ก์ ํธ ๊ฒฌ์ ์</h1>
<hr style="border: 1px solid #667eea;">
<h2 style="margin-top: 20px;">ํ๋ก์ ํธ ์ ๋ณด</h2>
<table cellpadding="5" style="width: 100%; border: 1px solid #ddd;">
<tr style="background-color: #f9f9f9;">
<td style="width: 30%; font-weight: bold;">ํ๋ก์ ํธ๋ช
</td>
<td>'.htmlspecialchars($request['project_title']).'</td>
</tr>
<tr>
<td style="font-weight: bold;">๊ณ ๊ฐ ์ด๋ฉ์ผ</td>
<td>'.htmlspecialchars($request['customer_email']).'</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="font-weight: bold;">์ ์์ผ</td>
<td>'.date('Y-m-d H:i', strtotime($request['created_at'])).'</td>
</tr>
</table>
<h2 style="margin-top: 20px;">์์ธ ์ค๋ช
</h2>
<p style="line-height: 1.6;">'.nl2br(htmlspecialchars($request['description'])).'</p>
<h2 style="margin-top: 20px;">๊ฒฌ์ ๋ด์ญ</h2>
<table cellpadding="5" style="width: 100%; border: 1px solid #ddd;">
<tr style="background-color: #f9f9f9;">
<td style="width: 70%; font-weight: bold;">ํญ๋ชฉ</td>
<td style="text-align: right; font-weight: bold;">๊ธ์ก</td>
</tr>
<tr>
<td>์์
์ ํ: '.htmlspecialchars($request['work_type']).'</td>
<td style="text-align: right;">'.number_format($base_price).'์</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td>๋์ด๋: '.$difficulty_map[$request['difficulty']].'</td>
<td style="text-align: right;">'.number_format($base_price * $difficulty_multiplier[$request['difficulty']]).'์</td>
</tr>
';
foreach ($option_details as $opt) {
$html .= '
<tr>
<td>'.htmlspecialchars($opt['option_name']).'</td>
<td style="text-align: right;">+'.number_format($opt['option_price']).'์</td>
</tr>
';
}
$html .= '
<tr style="background-color: #667eea; color: white; font-size: 14px; font-weight: bold;">
<td>์ต์ข
๊ฒฌ์ </td>
<td style="text-align: right;">'.number_format($request['total_quote']).'์</td>
</tr>
</table>
';
if ($request['deadline']) {
$html .= '
<h2 style="margin-top: 20px;">ํฌ๋ง ๋ฉ๊ธฐ์ผ</h2>
<p>'.date('Y๋
m์ d์ผ', strtotime($request['deadline'])).'</p>
';
}
$html .= '
<p style="margin-top: 30px; text-align: center; color: #999; font-size: 9px;">๋ณธ ๊ฒฌ์ ์๋ ํฌ๋ฆฌ์์ดํฐ ๊ฒฌ์ ์๋ ์ฐ์ถ ์์คํ
์์ ์์ฑ๋์์ต๋๋ค.</p>
';
$pdf->writeHTML($html, true, false, true, false, '');
// PDF ์ถ๋ ฅ
$pdf->Output('quote_'.$request_id.'.pdf', 'D');
?>
```
์ด์ ๊ฒฌ์ ํ์ธ ํ์ด์ง์ ๋์๋ณด๋์ PDF ๋ค์ด๋ก๋ ๋ฒํผ์ ์ถ๊ฐํฉ๋๋ค.
`app/modules/creator-quote/view/[id]/index.php`์ `.quote-card` ์์ ๋ค์ ๋ฒํผ ์ถ๊ฐ (์ํ ๋ฐฐ์ง ์):
```html
<a href="/creator-quote/download-pdf?id=<?php echo $request_id; ?>" style="background: #ef4444; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 0.875rem; font-weight: 600;">๐ PDF ๋ค์ด๋ก๋</a>
```
`app/modules/creator-quote/dashboard/index.php`์ ํ
์ด๋ธ "๊ด๋ฆฌ" ์ด์ ๋ค์ ๋งํฌ ์ถ๊ฐ:
```html
<a href="/creator-quote/download-pdf?id=<?php echo $req['id']; ?>" style="color: #ef4444; text-decoration: none; margin-left: 8px;">๐ PDF</a>
```
์ด์ ๊ฒฌ์ ์๋ฅผ PDF๋ก ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: PDF ๋ค์ด๋ก๋ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด `/creator-quote/download-pdf?id=1` ์ ์ ์ ๊ฒฌ์ ์๊ฐ PDF ํ์ผ๋ก ๋ค์ด๋ก๋๋ฉ๋๋ค. ๊ฒฌ์ ํ์ธ ํ์ด์ง์ ๋์๋ณด๋์์ PDF ๋ค์ด๋ก๋ ๋ฒํผ์ด ํ์๋๋ฉฐ, ํด๋ฆญ ์ ๊น๋ํ ๋ ์ด์์์ PDF ๊ฒฌ์ ์๊ฐ ์์ฑ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ์ด๋ฉ์ผ ์๋ฆผ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ฌ ๊ณ ๊ฐ์ด ์๋ขฐ๋ฅผ ์ ์ถํ๊ฑฐ๋ ์น์ธ/๊ฑฐ์ ํ์ ๋ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์๋ฆผ์ ๋ณด๋ด๋๋ก ํ๊ฒ ์ต๋๋ค.
๋จผ์ ํฌ๋ฆฌ์์ดํฐ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ค์ ํ์ผ์ ์ ์ฅํฉ๋๋ค.
`app/modules/creator-quote/_config.php` ์์ฑ:
```php
<?php
if (!defined('_GNUBOARD_')) exit;
// ํฌ๋ฆฌ์์ดํฐ ์ด๋ฉ์ผ (์๋ฆผ ์์ ์ฉ)
define('CREATOR_EMAIL', 'your-email@example.com'); // ์ค์ ์ด๋ฉ์ผ๋ก ๋ณ๊ฒฝํ์ธ์
// ์ฌ์ดํธ๋ช
define('SITE_NAME', 'ํฌ๋ฆฌ์์ดํฐ ๊ฒฌ์ ์์คํ
');
?>
```
์ด์ `app/modules/creator-quote/request/index.php`์ POST ์ฒ๋ฆฌ ๋ถ๋ถ์ ์ด๋ฉ์ผ ์ ์ก ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค (DB ์ ์ฅ ํ):
```php
// DB ์ ์ฅ ํ ์ด๋ฉ์ผ ์๋ฆผ
require_once('../_config.php');
$subject = '['.SITE_NAME.'] ์๋ก์ด ํ๋ก์ ํธ ์๋ขฐ๊ฐ ์ ์๋์์ต๋๋ค';
$message = '
์๋
ํ์ธ์,\n\n'
.htmlspecialchars($customer_email).'๋์ผ๋ก๋ถํฐ ์๋ก์ด ํ๋ก์ ํธ ์๋ขฐ๊ฐ ์ ์๋์์ต๋๋ค.\n\n'
.'ํ๋ก์ ํธ๋ช
: '.htmlspecialchars($project_title).'\n'
.'์์
์ ํ: '.htmlspecialchars($work_type).'\n'
.'๊ฒฌ์ ๊ธ์ก: '.number_format($total).'์\n\n'
.'๋์๋ณด๋์์ ํ์ธํ์ธ์: '.G5_URL.'/creator-quote/dashboard\n\n'
.'๊ฐ์ฌํฉ๋๋ค.';
@mail(CREATOR_EMAIL, $subject, $message, 'From: noreply@'.str_replace('www.', '', $_SERVER['HTTP_HOST']));
```
๊ทธ๋ฆฌ๊ณ `app/modules/creator-quote/view/[id]/index.php`์ ์น์ธ/๊ฑฐ์ POST ์ฒ๋ฆฌ ๋ถ๋ถ์๋ ์ด๋ฉ์ผ ์ ์ก์ ์ถ๊ฐํฉ๋๋ค:
```php
require_once('../../_config.php');
if (isset($_POST['approve'])) {
$stmt = $pdo->prepare("UPDATE requests SET status = 'approved', updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$request_id]);
$message = '๊ฒฌ์ ์ด ์น์ธ๋์์ต๋๋ค! ํฌ๋ฆฌ์์ดํฐ๊ฐ ๊ณง ์ฐ๋ฝ๋๋ฆด ์์ ์
๋๋ค.';
// ์ด๋ฉ์ผ ์๋ฆผ
$subject = '['.SITE_NAME.'] ๊ฒฌ์ ์ด ์น์ธ๋์์ต๋๋ค';
$email_message = '
์๋
ํ์ธ์,\n\n'
.htmlspecialchars($request['customer_email']).'๋์ด ํ๋ก์ ํธ "'.htmlspecialchars($request['project_title']).'"์ ๊ฒฌ์ ์ ์น์ธํ์
จ์ต๋๋ค.\n\n'
.'๊ฒฌ์ ๊ธ์ก: '.number_format($request['total_quote']).'์\n\n'
.'๊ณ ๊ฐ์๊ฒ ์ฐ๋ฝํ์ฌ ํ๋ก์ ํธ๋ฅผ ์งํํ์ธ์.\n\n'
.'๊ฐ์ฌํฉ๋๋ค.';
@mail(CREATOR_EMAIL, $subject, $email_message, 'From: noreply@'.str_replace('www.', '', $_SERVER['HTTP_HOST']));
} elseif (isset($_POST['reject'])) {
$reason = trim($_POST['rejection_reason']);
$stmt = $pdo->prepare("UPDATE requests SET status = 'rejected', rejection_reason = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$reason, $request_id]);
$message = '๊ฒฌ์ ์ด ๊ฑฐ์ ๋์์ต๋๋ค. ์ฌ์ ๊ฐ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์ ๋ฌ๋์์ต๋๋ค.';
// ์ด๋ฉ์ผ ์๋ฆผ
$subject = '['.SITE_NAME.'] ๊ฒฌ์ ์ด ๊ฑฐ์ ๋์์ต๋๋ค';
$email_message = '
์๋
ํ์ธ์,\n\n'
.htmlspecialchars($request['customer_email']).'๋์ด ํ๋ก์ ํธ "'.htmlspecialchars($request['project_title']).'"์ ๊ฒฌ์ ์ ๊ฑฐ์ ํ์
จ์ต๋๋ค.\n\n'
.($reason ? '๊ฑฐ์ ์ฌ์ : '.htmlspecialchars($reason).'\n\n' : '')
.'๊ฐ์ฌํฉ๋๋ค.';
@mail(CREATOR_EMAIL, $subject, $email_message, 'From: noreply@'.str_replace('www.', '', $_SERVER['HTTP_HOST']));
}
```
๋ง์ง๋ง์ผ๋ก ๋ฉ์ธ ๋๋ฉ ํ์ด์ง(`app/modules/creator-quote/index.php`)์ ์ฌ์ฉ ์๋ด๋ฅผ ์ถ๊ฐํฉ๋๋ค:
```html
<div style="max-width: 800px; margin: 48px auto; padding: 24px; background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 16px; color: var(--m-text);">๐ ์์คํ
์ฌ์ฉ ๊ฐ์ด๋</h2>
<ol style="line-height: 2; color: var(--m-text-muted);">
<li><strong>ํฌ๋ฆฌ์์ดํฐ:</strong> "๊ฐ๊ฒฉ ์ค์ " ๋ฉ๋ด์์ ์์
์ ํ๊ณผ ์ถ๊ฐ ์ต์
์ ๋จ๊ฐ๋ฅผ ๋จผ์ ๋ฑ๋กํ์ธ์.</li>
<li><strong>๊ณ ๊ฐ:</strong> "ํ๋ก์ ํธ ์๋ขฐํ๊ธฐ" ๋ฉ๋ด์์ ํ๋ก์ ํธ ์ ๋ณด๋ฅผ ์
๋ ฅํ๋ฉด ์๋์ผ๋ก ๊ฒฌ์ ์ด ๊ณ์ฐ๋ฉ๋๋ค.</li>
<li><strong>๊ณ ๊ฐ:</strong> ๊ฒฌ์ ์ ํ์ธ ํ์ด์ง์์ ๊ฒฌ์ ์ ๊ฒํ ํ๊ณ ์น์ธ ๋๋ ๊ฑฐ์ ํ์ธ์.</li>
<li><strong>ํฌ๋ฆฌ์์ดํฐ:</strong> "ํฌ๋ฆฌ์์ดํฐ ๋์๋ณด๋"์์ ์ ์๋ ์๋ขฐ๋ฅผ ๊ด๋ฆฌํ๊ณ PDF ๊ฒฌ์ ์๋ฅผ ๋ค์ด๋ก๋ํ์ธ์.</li>
<li><strong>์๋ ์๋ฆผ:</strong> ์๋ขฐ ์ ์/์น์ธ/๊ฑฐ์ ์ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์ด๋ฉ์ผ์ด ์๋ ๋ฐ์ก๋ฉ๋๋ค.</li>
</ol>
</div>
```
์ด์ ์ ์ฒด ์์คํ
์ด ์์ฑ๋์์ต๋๋ค! ํ
์คํธ๋ฅผ ์ํด ๋ค์์ ํ์ธํ์ธ์:
1. `/creator-quote/pricing`์์ ์์
์ ํ๊ณผ ์ต์
์ถ๊ฐ
2. `/creator-quote/request`์์ ๊ณ ๊ฐ ์๋ขฐ ์ ์ถ
3. `/creator-quote/view/1`์์ ๊ฒฌ์ ํ์ธ ๋ฐ ์น์ธ/๊ฑฐ์
4. `/creator-quote/dashboard`์์ ๋์๋ณด๋ ํ์ธ
5. PDF ๋ค์ด๋ก๋ ๋ฐ ์ด๋ฉ์ผ ์๋ฆผ ์์ ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ด๋ฉ์ผ ์๋ฆผ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด ๊ณ ๊ฐ์ด ์๋ขฐ๋ฅผ ์ ์ถํ๊ฑฐ๋ ๊ฒฌ์ ์ ์น์ธ/๊ฑฐ์ ํ ๋ ํฌ๋ฆฌ์์ดํฐ์๊ฒ ์๋์ผ๋ก ์ด๋ฉ์ผ์ด ๋ฐ์ก๋ฉ๋๋ค. ๋ฉ์ธ ํ์ด์ง์ ์ฌ์ฉ ๊ฐ์ด๋๊ฐ ์ถ๊ฐ๋์ด ์์คํ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ํ๋์ ํ์ ํ ์ ์์ต๋๋ค. ์ ์ฒด ์ํฌํ๋ก์ฐ๊ฐ ์์ฑ๋์ด ์ค๋ฌด์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ ์์ค์ ๊ฒฌ์ ๊ด๋ฆฌ ์์คํ ์ด ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!