mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2026-06-13 04:19:11 +00:00
초기 프로젝트 설정 및 설계 문서 추가
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>로그인 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page flex flex-col items-center justify-center" style="padding-bottom: 0;">
|
||||
<div class="container" style="max-width: 400px;">
|
||||
<!-- Logo & Title -->
|
||||
<div class="text-center mb-2xl">
|
||||
<div class="mb-lg">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-kt-red);">celebration</span>
|
||||
</div>
|
||||
<h1 class="text-display text-kt-red mb-sm">KT AI 이벤트</h1>
|
||||
<p class="text-body text-secondary">소상공인을 위한 스마트 마케팅</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" class="mb-lg">
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label form-label-required">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-input"
|
||||
placeholder="example@email.com"
|
||||
required
|
||||
autocomplete="email"
|
||||
>
|
||||
<span id="emailError" class="form-error hidden"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label form-label-required">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-input"
|
||||
placeholder="8자 이상 입력하세요"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
>
|
||||
<span id="passwordError" class="form-error hidden"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-lg">
|
||||
<input type="checkbox" id="remember" name="remember" class="form-check-input">
|
||||
<label for="remember" class="form-check-label">로그인 상태 유지</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-large btn-full mb-md">
|
||||
로그인
|
||||
</button>
|
||||
|
||||
<div class="flex justify-between text-body-small">
|
||||
<a href="#" class="text-kt-red" id="findPassword">비밀번호 찾기</a>
|
||||
<a href="02-회원가입.html" class="text-kt-red">회원가입</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="flex items-center gap-md mb-lg">
|
||||
<div class="flex-1 border-t"></div>
|
||||
<span class="text-caption text-tertiary">또는</span>
|
||||
<div class="flex-1 border-t"></div>
|
||||
</div>
|
||||
|
||||
<!-- SNS Login -->
|
||||
<div class="flex flex-col gap-sm">
|
||||
<button class="btn btn-secondary btn-large btn-full" id="kakaoLogin">
|
||||
<span class="material-icons">chat_bubble</span>
|
||||
카카오톡으로 시작하기
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large btn-full" id="naverLogin">
|
||||
<span style="color: #03C75A; font-weight: bold;">N</span>
|
||||
네이버로 시작하기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="text-center mt-2xl">
|
||||
<p class="text-caption text-tertiary">
|
||||
회원가입 시 <a href="#" class="text-kt-red">이용약관</a> 및 <a href="#" class="text-kt-red">개인정보처리방침</a>에 동의하게 됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 폼 제출 처리
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const remember = document.getElementById('remember').checked;
|
||||
|
||||
// 에러 메시지 초기화
|
||||
document.getElementById('emailError').classList.add('hidden');
|
||||
document.getElementById('passwordError').classList.add('hidden');
|
||||
|
||||
// 유효성 검사
|
||||
let hasError = false;
|
||||
|
||||
if (!KTEventApp.Utils.validateEmail(email)) {
|
||||
document.getElementById('emailError').textContent = '올바른 이메일 형식이 아닙니다.';
|
||||
document.getElementById('emailError').classList.remove('hidden');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
document.getElementById('passwordError').textContent = '비밀번호는 8자 이상이어야 합니다.';
|
||||
document.getElementById('passwordError').classList.remove('hidden');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 로딩 표시
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '로그인 중...';
|
||||
|
||||
// 로그인 시뮬레이션 (실제로는 API 호출)
|
||||
setTimeout(() => {
|
||||
// 예제 사용자 정보 저장
|
||||
const user = KTEventApp.MockData.getDefaultUser();
|
||||
user.email = email;
|
||||
KTEventApp.Session.saveUser(user);
|
||||
|
||||
// 대시보드로 이동
|
||||
window.location.href = '05-대시보드.html';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// 비밀번호 찾기
|
||||
document.getElementById('findPassword').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '비밀번호 찾기',
|
||||
content: `
|
||||
<p class="text-body mb-md">가입하신 이메일 주소를 입력해주세요.</p>
|
||||
<input type="email" id="resetEmail" class="form-input" placeholder="example@email.com">
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
const resetEmail = document.getElementById('resetEmail').value;
|
||||
if (KTEventApp.Utils.validateEmail(resetEmail)) {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
KTEventApp.Feedback.showToast('비밀번호 재설정 이메일을 발송했습니다.');
|
||||
} else {
|
||||
KTEventApp.Feedback.showToast('올바른 이메일을 입력해주세요.');
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// SNS 로그인 시뮬레이션
|
||||
document.getElementById('kakaoLogin').addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showToast('카카오톡 로그인은 준비 중입니다.');
|
||||
});
|
||||
|
||||
document.getElementById('naverLogin').addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showToast('네이버 로그인은 준비 중입니다.');
|
||||
});
|
||||
|
||||
// 이미 로그인된 경우 대시보드로 이동
|
||||
if (KTEventApp.Session.isLoggedIn()) {
|
||||
window.location.href = '05-대시보드.html';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,400 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>회원가입 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page" style="padding-bottom: 0;">
|
||||
<!-- Custom Header -->
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
<button class="header-icon-btn" id="backBtn" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="header-title">회원가입</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Progress Indicator -->
|
||||
<div class="p-md bg-primary">
|
||||
<div id="progressBar"></div>
|
||||
<p class="text-body-small text-secondary mt-sm text-center" id="stepInfo">1/3 단계</p>
|
||||
</div>
|
||||
|
||||
<div class="container" style="max-width: 500px; padding-top: var(--spacing-lg);">
|
||||
<!-- Step 1: 계정 정보 -->
|
||||
<div id="step1" class="signup-step">
|
||||
<h2 class="text-title mb-sm">계정 정보를 입력해주세요</h2>
|
||||
<p class="text-body text-secondary mb-lg">로그인에 사용할 이메일과 비밀번호를 설정합니다</p>
|
||||
|
||||
<form id="step1Form">
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label form-label-required">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
class="form-input"
|
||||
placeholder="example@email.com"
|
||||
required
|
||||
autocomplete="email"
|
||||
>
|
||||
<span id="emailError" class="form-error hidden"></span>
|
||||
<span class="form-hint">이메일 주소로 로그인합니다</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label form-label-required">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="form-input"
|
||||
placeholder="8자 이상, 영문+숫자 조합"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<span id="passwordError" class="form-error hidden"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passwordConfirm" class="form-label form-label-required">비밀번호 확인</label>
|
||||
<input
|
||||
type="password"
|
||||
id="passwordConfirm"
|
||||
class="form-input"
|
||||
placeholder="비밀번호를 다시 입력하세요"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<span id="passwordConfirmError" class="form-error hidden"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-large btn-full mt-xl">
|
||||
다음
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: 개인 정보 -->
|
||||
<div id="step2" class="signup-step hidden">
|
||||
<h2 class="text-title mb-sm">개인 정보를 입력해주세요</h2>
|
||||
<p class="text-body text-secondary mb-lg">서비스 이용을 위한 기본 정보입니다</p>
|
||||
|
||||
<form id="step2Form">
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label form-label-required">이름</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
class="form-input"
|
||||
placeholder="홍길동"
|
||||
required
|
||||
autocomplete="name"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="phone" class="form-label form-label-required">휴대폰 번호</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
class="form-input"
|
||||
placeholder="010-1234-5678"
|
||||
required
|
||||
autocomplete="tel"
|
||||
>
|
||||
<span id="phoneError" class="form-error hidden"></span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-sm mt-xl">
|
||||
<button type="button" class="btn btn-secondary btn-large flex-1" id="prevBtn1">
|
||||
이전
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary btn-large flex-1">
|
||||
다음
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: 사업장 정보 -->
|
||||
<div id="step3" class="signup-step hidden">
|
||||
<h2 class="text-title mb-sm">사업장 정보를 입력해주세요</h2>
|
||||
<p class="text-body text-secondary mb-lg">맞춤형 이벤트 추천을 위한 정보입니다</p>
|
||||
|
||||
<form id="step3Form">
|
||||
<div class="form-group">
|
||||
<label for="businessName" class="form-label form-label-required">상호명</label>
|
||||
<input
|
||||
type="text"
|
||||
id="businessName"
|
||||
class="form-input"
|
||||
placeholder="홍길동 고깃집"
|
||||
required
|
||||
autocomplete="organization"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessType" class="form-label form-label-required">업종</label>
|
||||
<select id="businessType" class="form-select" required>
|
||||
<option value="">업종을 선택하세요</option>
|
||||
<option value="restaurant">음식점</option>
|
||||
<option value="cafe">카페/베이커리</option>
|
||||
<option value="retail">소매/편의점</option>
|
||||
<option value="beauty">미용/뷰티</option>
|
||||
<option value="fitness">헬스/피트니스</option>
|
||||
<option value="education">학원/교육</option>
|
||||
<option value="service">서비스업</option>
|
||||
<option value="other">기타</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessLocation" class="form-label">주요 지역</label>
|
||||
<input
|
||||
type="text"
|
||||
id="businessLocation"
|
||||
class="form-input"
|
||||
placeholder="예: 강남구"
|
||||
autocomplete="address-level2"
|
||||
>
|
||||
<span class="form-hint">선택 사항입니다</span>
|
||||
</div>
|
||||
|
||||
<!-- 약관 동의 -->
|
||||
<div class="mt-xl mb-lg">
|
||||
<div class="form-check mb-md">
|
||||
<input type="checkbox" id="agreeAll" class="form-check-input">
|
||||
<label for="agreeAll" class="form-check-label text-bold">전체 동의</label>
|
||||
</div>
|
||||
<div class="border-t pt-md">
|
||||
<div class="form-check mb-sm">
|
||||
<input type="checkbox" id="agreeTerms" class="form-check-input agree-item" required>
|
||||
<label for="agreeTerms" class="form-check-label">
|
||||
<span>[필수]</span> 이용약관 동의
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mb-sm">
|
||||
<input type="checkbox" id="agreePrivacy" class="form-check-input agree-item" required>
|
||||
<label for="agreePrivacy" class="form-check-label">
|
||||
<span>[필수]</span> 개인정보 처리방침 동의
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="agreeMarketing" class="form-check-input agree-item">
|
||||
<label for="agreeMarketing" class="form-check-label">
|
||||
<span>[선택]</span> 마케팅 정보 수신 동의
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-sm">
|
||||
<button type="button" class="btn btn-secondary btn-large flex-1" id="prevBtn2">
|
||||
이전
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary btn-large flex-1">
|
||||
가입완료
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
let currentStep = 1;
|
||||
const signupData = {};
|
||||
|
||||
// Progress Bar 초기화
|
||||
const progressBar = KTEventApp.Feedback.createProgressBar(33);
|
||||
document.getElementById('progressBar').appendChild(progressBar.element);
|
||||
|
||||
// Step 전환 함수
|
||||
function showStep(step) {
|
||||
document.querySelectorAll('.signup-step').forEach(el => el.classList.add('hidden'));
|
||||
document.getElementById(`step${step}`).classList.remove('hidden');
|
||||
|
||||
currentStep = step;
|
||||
progressBar.setValue(step * 33);
|
||||
document.getElementById('stepInfo').textContent = `${step}/3 단계`;
|
||||
}
|
||||
|
||||
// 뒤로가기
|
||||
document.getElementById('backBtn').addEventListener('click', () => {
|
||||
if (currentStep === 1) {
|
||||
window.location.href = '01-로그인.html';
|
||||
} else {
|
||||
showStep(currentStep - 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Step 1: 계정 정보
|
||||
document.getElementById('step1Form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const passwordConfirm = document.getElementById('passwordConfirm').value;
|
||||
|
||||
// 에러 초기화
|
||||
document.getElementById('emailError').classList.add('hidden');
|
||||
document.getElementById('passwordError').classList.add('hidden');
|
||||
document.getElementById('passwordConfirmError').classList.add('hidden');
|
||||
|
||||
let hasError = false;
|
||||
|
||||
// 이메일 검증
|
||||
if (!KTEventApp.Utils.validateEmail(email)) {
|
||||
document.getElementById('emailError').textContent = '올바른 이메일 형식이 아닙니다.';
|
||||
document.getElementById('emailError').classList.remove('hidden');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
// 비밀번호 검증
|
||||
if (!KTEventApp.Utils.validatePassword(password)) {
|
||||
document.getElementById('passwordError').textContent = '8자 이상, 영문과 숫자를 포함해야 합니다.';
|
||||
document.getElementById('passwordError').classList.remove('hidden');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
// 비밀번호 확인
|
||||
if (password !== passwordConfirm) {
|
||||
document.getElementById('passwordConfirmError').textContent = '비밀번호가 일치하지 않습니다.';
|
||||
document.getElementById('passwordConfirmError').classList.remove('hidden');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (!hasError) {
|
||||
signupData.email = email;
|
||||
signupData.password = password;
|
||||
showStep(2);
|
||||
}
|
||||
});
|
||||
|
||||
// Step 2: 개인 정보
|
||||
document.getElementById('prevBtn1').addEventListener('click', () => showStep(1));
|
||||
|
||||
document.getElementById('step2Form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const name = document.getElementById('name').value;
|
||||
const phone = document.getElementById('phone').value;
|
||||
|
||||
// 전화번호 형식 검증
|
||||
const phonePattern = /^010-\d{3,4}-\d{4}$/;
|
||||
if (!phonePattern.test(phone)) {
|
||||
document.getElementById('phoneError').textContent = '올바른 전화번호 형식이 아닙니다 (010-1234-5678).';
|
||||
document.getElementById('phoneError').classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
signupData.name = name;
|
||||
signupData.phone = phone;
|
||||
showStep(3);
|
||||
});
|
||||
|
||||
// 전화번호 자동 포맷팅
|
||||
document.getElementById('phone').addEventListener('input', function(e) {
|
||||
e.target.value = KTEventApp.Utils.formatPhoneNumber(e.target.value);
|
||||
document.getElementById('phoneError').classList.add('hidden');
|
||||
});
|
||||
|
||||
// Step 3: 사업장 정보
|
||||
document.getElementById('prevBtn2').addEventListener('click', () => showStep(2));
|
||||
|
||||
// 전체 동의 체크박스
|
||||
document.getElementById('agreeAll').addEventListener('change', function(e) {
|
||||
const checked = e.target.checked;
|
||||
document.querySelectorAll('.agree-item').forEach(cb => {
|
||||
cb.checked = checked;
|
||||
});
|
||||
});
|
||||
|
||||
// 개별 체크박스
|
||||
document.querySelectorAll('.agree-item').forEach(cb => {
|
||||
cb.addEventListener('change', function() {
|
||||
const allChecked = Array.from(document.querySelectorAll('.agree-item')).every(c => c.checked);
|
||||
document.getElementById('agreeAll').checked = allChecked;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('step3Form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const businessName = document.getElementById('businessName').value;
|
||||
const businessType = document.getElementById('businessType').value;
|
||||
const businessLocation = document.getElementById('businessLocation').value;
|
||||
const agreeTerms = document.getElementById('agreeTerms').checked;
|
||||
const agreePrivacy = document.getElementById('agreePrivacy').checked;
|
||||
const agreeMarketing = document.getElementById('agreeMarketing').checked;
|
||||
|
||||
if (!agreeTerms || !agreePrivacy) {
|
||||
KTEventApp.Feedback.showToast('필수 약관에 동의해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
signupData.businessName = businessName;
|
||||
signupData.businessType = businessType;
|
||||
signupData.businessLocation = businessLocation;
|
||||
signupData.agreeMarketing = agreeMarketing;
|
||||
|
||||
// 로딩 표시
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '가입 처리 중...';
|
||||
|
||||
// 회원가입 시뮬레이션
|
||||
setTimeout(() => {
|
||||
// 사용자 정보 저장
|
||||
const user = {
|
||||
id: KTEventApp.Utils.generateId(),
|
||||
name: signupData.name,
|
||||
email: signupData.email,
|
||||
phone: signupData.phone,
|
||||
businessName: signupData.businessName,
|
||||
businessType: signupData.businessType,
|
||||
businessLocation: signupData.businessLocation,
|
||||
joinDate: new Date().toISOString()
|
||||
};
|
||||
|
||||
KTEventApp.Session.saveUser(user);
|
||||
|
||||
// 성공 모달 표시
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '회원가입 완료',
|
||||
content: `
|
||||
<div class="text-center p-lg">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-success);">check_circle</span>
|
||||
<p class="text-headline mt-md mb-sm">환영합니다!</p>
|
||||
<p class="text-body text-secondary">회원가입이 완료되었습니다.<br>지금 바로 AI 이벤트를 시작해보세요.</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '시작하기',
|
||||
variant: 'primary',
|
||||
size: 'large',
|
||||
fullWidth: true,
|
||||
onClick: function() {
|
||||
window.location.href = '05-대시보드.html';
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// 이미 로그인된 경우 대시보드로 이동
|
||||
if (KTEventApp.Session.isLoggedIn()) {
|
||||
window.location.href = '05-대시보드.html';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>프로필 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 600px;">
|
||||
<!-- User Info Section -->
|
||||
<section class="mt-lg mb-xl text-center">
|
||||
<div class="mb-md" style="width: 80px; height: 80px; background: var(--color-gray-100); border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-400);">person</span>
|
||||
</div>
|
||||
<h2 class="text-title" id="userName">홍길동</h2>
|
||||
<p class="text-body-small text-secondary" id="userEmail">hong@example.com</p>
|
||||
</section>
|
||||
|
||||
<!-- Basic Info -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">기본 정보</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">이름</label>
|
||||
<input type="text" id="name" class="form-input" value="홍길동">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="phone" class="form-label">전화번호</label>
|
||||
<input type="tel" id="phone" class="form-input" value="010-1234-5678">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label">이메일</label>
|
||||
<input type="email" id="email" class="form-input" value="hong@example.com">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Business Info -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">매장 정보</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessName" class="form-label">매장명</label>
|
||||
<input type="text" id="businessName" class="form-input" value="홍길동 고깃집">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessType" class="form-label">업종</label>
|
||||
<select id="businessType" class="form-select">
|
||||
<option value="restaurant" selected>음식점</option>
|
||||
<option value="cafe">카페/베이커리</option>
|
||||
<option value="retail">소매/편의점</option>
|
||||
<option value="beauty">미용/뷰티</option>
|
||||
<option value="fitness">헬스/피트니스</option>
|
||||
<option value="education">학원/교육</option>
|
||||
<option value="service">서비스업</option>
|
||||
<option value="other">기타</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessLocation" class="form-label">주소</label>
|
||||
<input type="text" id="businessLocation" class="form-input" value="서울시 강남구">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="businessHours" class="form-label">영업시간</label>
|
||||
<input type="text" id="businessHours" class="form-input" value="10:00 ~ 22:00">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Password Change -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="text-headline mb-md">비밀번호 변경</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="currentPassword" class="form-label">현재 비밀번호</label>
|
||||
<input type="password" id="currentPassword" class="form-input" placeholder="현재 비밀번호를 입력하세요">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newPassword" class="form-label">새 비밀번호</label>
|
||||
<input type="password" id="newPassword" class="form-input" placeholder="새 비밀번호를 입력하세요">
|
||||
<span class="form-hint">8자 이상, 영문과 숫자를 포함해주세요</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword" class="form-label">비밀번호 확인</label>
|
||||
<input type="password" id="confirmPassword" class="form-input" placeholder="비밀번호를 다시 입력하세요">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<button id="saveBtn" class="btn btn-primary btn-large btn-full mb-sm">
|
||||
저장하기
|
||||
</button>
|
||||
<button id="logoutBtn" class="btn btn-text btn-large btn-full text-error">
|
||||
로그아웃
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
const user = KTEventApp.Session.getUser();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '프로필',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('profile');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// 사용자 정보 표시
|
||||
document.getElementById('userName').textContent = user.name;
|
||||
document.getElementById('userEmail').textContent = user.email;
|
||||
document.getElementById('name').value = user.name;
|
||||
document.getElementById('phone').value = user.phone;
|
||||
document.getElementById('email').value = user.email;
|
||||
document.getElementById('businessName').value = user.businessName;
|
||||
document.getElementById('businessLocation').value = user.businessLocation || '서울시 강남구';
|
||||
|
||||
// 전화번호 자동 포맷팅
|
||||
document.getElementById('phone').addEventListener('input', function(e) {
|
||||
e.target.value = KTEventApp.Utils.formatPhoneNumber(e.target.value);
|
||||
});
|
||||
|
||||
// 저장하기 버튼
|
||||
document.getElementById('saveBtn').addEventListener('click', function() {
|
||||
const currentPassword = document.getElementById('currentPassword').value;
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||
|
||||
// 비밀번호 변경 검증
|
||||
if (currentPassword || newPassword || confirmPassword) {
|
||||
if (!currentPassword) {
|
||||
KTEventApp.Feedback.showToast('현재 비밀번호를 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KTEventApp.Utils.validatePassword(newPassword)) {
|
||||
KTEventApp.Feedback.showToast('새 비밀번호는 8자 이상, 영문과 숫자를 포함해야 합니다');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
KTEventApp.Feedback.showToast('새 비밀번호가 일치하지 않습니다');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 사용자 정보 업데이트
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: document.getElementById('name').value,
|
||||
phone: document.getElementById('phone').value,
|
||||
email: document.getElementById('email').value,
|
||||
businessName: document.getElementById('businessName').value,
|
||||
businessType: document.getElementById('businessType').value,
|
||||
businessLocation: document.getElementById('businessLocation').value
|
||||
};
|
||||
|
||||
KTEventApp.Session.saveUser(updatedUser);
|
||||
|
||||
KTEventApp.Feedback.showModal({
|
||||
content: `
|
||||
<div class="p-xl text-center">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-success);">check_circle</span>
|
||||
<h3 class="text-headline mt-md mb-sm">저장 완료</h3>
|
||||
<p class="text-body text-secondary">프로필 정보가 업데이트되었습니다.</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// 로그아웃 버튼
|
||||
document.getElementById('logoutBtn').addEventListener('click', function() {
|
||||
window.location.href = '04-로그아웃확인.html';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>로그아웃 확인 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modal Backdrop -->
|
||||
<div class="modal-backdrop" id="logoutModal" style="display: flex;">
|
||||
<div class="modal-dialog" style="max-width: 400px;">
|
||||
<div class="modal-header">
|
||||
<h3 class="text-headline">로그아웃</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-body text-center">로그아웃 하시겠습니까?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-text" id="cancelBtn">취소</button>
|
||||
<button class="btn btn-primary" id="confirmBtn">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 취소 버튼 - 이전 페이지로 이동
|
||||
document.getElementById('cancelBtn').addEventListener('click', function() {
|
||||
window.history.back();
|
||||
});
|
||||
|
||||
// 배경 클릭 시 취소와 동일
|
||||
document.getElementById('logoutModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
|
||||
// 확인 버튼 - 로그아웃 처리
|
||||
document.getElementById('confirmBtn').addEventListener('click', function() {
|
||||
// 세션 종료
|
||||
KTEventApp.Session.logout();
|
||||
|
||||
// 로그인 화면으로 이동 (애니메이션 효과)
|
||||
setTimeout(() => {
|
||||
window.location.href = '01-로그인.html';
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,219 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>대시보드 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Welcome Section -->
|
||||
<section class="mt-lg mb-xl">
|
||||
<h2 class="text-title-large mb-sm" id="welcomeMessage">안녕하세요, 사용자님!</h2>
|
||||
<p class="text-body text-secondary">오늘도 성공적인 이벤트를 준비해보세요</p>
|
||||
</section>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<section class="mb-xl">
|
||||
<div class="grid grid-cols-3 gap-sm tablet:grid-cols-3">
|
||||
<div id="kpiEvents"></div>
|
||||
<div id="kpiParticipants"></div>
|
||||
<div id="kpiROI"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<section class="mb-lg">
|
||||
<div class="flex items-center justify-between mb-md">
|
||||
<h3 class="text-headline">빠른 시작</h3>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm">
|
||||
<button class="card card-clickable p-md" id="createEvent">
|
||||
<span class="material-icons text-kt-red" style="font-size: 32px;">add_circle</span>
|
||||
<p class="text-body-small mt-sm">새 이벤트</p>
|
||||
</button>
|
||||
<button class="card card-clickable p-md" id="viewAnalytics">
|
||||
<span class="material-icons text-secondary" style="font-size: 32px;">analytics</span>
|
||||
<p class="text-body-small mt-sm">분석</p>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Active Events -->
|
||||
<section class="mb-xl">
|
||||
<div class="flex items-center justify-between mb-md">
|
||||
<h3 class="text-headline">진행 중인 이벤트</h3>
|
||||
<a href="06-이벤트목록.html" class="text-body-small text-kt-red">
|
||||
전체보기
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">chevron_right</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="activeEvents" class="flex flex-col gap-md">
|
||||
<!-- 이벤트 카드가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<section class="mb-2xl">
|
||||
<h3 class="text-headline mb-md">최근 활동</h3>
|
||||
<div class="card">
|
||||
<div id="recentActivity">
|
||||
<!-- 최근 활동 목록 -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
|
||||
<!-- FAB -->
|
||||
<div id="fab"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
const user = KTEventApp.Session.getUser();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '대시보드',
|
||||
showBack: false,
|
||||
showMenu: false,
|
||||
showProfile: true
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Welcome 메시지
|
||||
document.getElementById('welcomeMessage').textContent = `안녕하세요, ${user.name}님!`;
|
||||
|
||||
// KPI 데이터 계산
|
||||
const events = KTEventApp.MockData.getEvents();
|
||||
const activeEvents = events.filter(e => e.status === '진행중');
|
||||
const totalParticipants = events.reduce((sum, e) => sum + e.participants, 0);
|
||||
const avgROI = events.length > 0
|
||||
? Math.round(events.reduce((sum, e) => sum + e.roi, 0) / events.length)
|
||||
: 0;
|
||||
|
||||
// KPI 카드 생성
|
||||
const kpiEventsCard = KTEventApp.Cards.createKPICard({
|
||||
icon: 'celebration',
|
||||
iconType: 'primary',
|
||||
label: '진행 중',
|
||||
value: `${activeEvents.length}개`
|
||||
});
|
||||
document.getElementById('kpiEvents').appendChild(kpiEventsCard);
|
||||
|
||||
const kpiParticipantsCard = KTEventApp.Cards.createKPICard({
|
||||
icon: 'group',
|
||||
iconType: 'success',
|
||||
label: '총 참여자',
|
||||
value: `${KTEventApp.Utils.formatNumber(totalParticipants)}명`
|
||||
});
|
||||
document.getElementById('kpiParticipants').appendChild(kpiParticipantsCard);
|
||||
|
||||
const kpiROICard = KTEventApp.Cards.createKPICard({
|
||||
icon: 'trending_up',
|
||||
iconType: 'ai',
|
||||
label: '평균 ROI',
|
||||
value: `${avgROI}%`
|
||||
});
|
||||
document.getElementById('kpiROI').appendChild(kpiROICard);
|
||||
|
||||
// 진행 중인 이벤트 표시
|
||||
const activeEventsContainer = document.getElementById('activeEvents');
|
||||
|
||||
if (activeEvents.length === 0) {
|
||||
activeEventsContainer.innerHTML = `
|
||||
<div class="card text-center p-xl">
|
||||
<span class="material-icons text-tertiary" style="font-size: 48px;">event_busy</span>
|
||||
<p class="text-body text-secondary mt-md">진행 중인 이벤트가 없습니다</p>
|
||||
<button class="btn btn-primary btn-medium mt-md" onclick="createNewEvent()">
|
||||
<span class="material-icons">add</span>
|
||||
새 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
activeEvents.forEach(event => {
|
||||
const card = KTEventApp.Cards.createEventCard({
|
||||
...event,
|
||||
onClick: (id) => {
|
||||
window.location.href = `13-이벤트상세.html?id=${id}`;
|
||||
}
|
||||
});
|
||||
activeEventsContainer.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// 최근 활동 표시
|
||||
const recentActivityContainer = document.getElementById('recentActivity');
|
||||
const activities = [
|
||||
{ icon: 'person_add', text: 'SNS 팔로우 이벤트에 새로운 참여자 12명', time: '5분 전' },
|
||||
{ icon: 'edit', text: '설 맞이 할인 이벤트 내용을 수정했습니다', time: '1시간 전' },
|
||||
{ icon: 'check_circle', text: '고객 만족도 조사가 종료되었습니다', time: '3시간 전' }
|
||||
];
|
||||
|
||||
activities.forEach((activity, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'flex items-start gap-md';
|
||||
if (index > 0) item.classList.add('mt-md', 'pt-md', 'border-t');
|
||||
|
||||
item.innerHTML = `
|
||||
<span class="material-icons text-kt-red">${activity.icon}</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body">${activity.text}</p>
|
||||
<p class="text-caption text-tertiary mt-xs">${activity.time}</p>
|
||||
</div>
|
||||
`;
|
||||
recentActivityContainer.appendChild(item);
|
||||
});
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('home');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// FAB 생성
|
||||
const fab = KTEventApp.Navigation.createFAB('add', createNewEvent);
|
||||
document.getElementById('fab').appendChild(fab);
|
||||
|
||||
// 빠른 시작 버튼 이벤트
|
||||
document.getElementById('createEvent').addEventListener('click', createNewEvent);
|
||||
|
||||
document.getElementById('viewAnalytics').addEventListener('click', () => {
|
||||
window.location.href = '17-성과분석.html';
|
||||
});
|
||||
|
||||
// 새 이벤트 생성 함수
|
||||
function createNewEvent() {
|
||||
KTEventApp.Feedback.showBottomSheet(`
|
||||
<h3 class="text-headline mb-lg">이벤트 만들기</h3>
|
||||
<div class="flex flex-col gap-sm">
|
||||
<button class="btn btn-primary btn-large btn-full" onclick="window.location.href='07-이벤트목적선택.html'">
|
||||
<span class="material-icons">auto_awesome</span>
|
||||
AI 추천으로 시작하기
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large btn-full" onclick="KTEventApp.Feedback.showToast('템플릿 기능은 준비 중입니다.')">
|
||||
<span class="material-icons">dashboard</span>
|
||||
템플릿으로 시작하기
|
||||
</button>
|
||||
<button class="btn btn-text btn-large btn-full" onclick="KTEventApp.Feedback.showToast('직접 만들기 기능은 준비 중입니다.')">
|
||||
<span class="material-icons">edit</span>
|
||||
직접 만들기
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 목록 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 1200px;">
|
||||
<!-- Search Section -->
|
||||
<section class="mt-lg mb-md">
|
||||
<div class="form-group">
|
||||
<div class="input-with-icon">
|
||||
<span class="material-icons input-icon">search</span>
|
||||
<input type="text" id="searchInput" class="form-input" placeholder="이벤트명 검색...">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Filters -->
|
||||
<section class="mb-md">
|
||||
<div class="flex items-center gap-sm flex-wrap">
|
||||
<span class="material-icons text-kt-red">filter_list</span>
|
||||
<select id="statusFilter" class="form-select" style="flex: 1; min-width: 120px;">
|
||||
<option value="all">전체</option>
|
||||
<option value="active">진행중</option>
|
||||
<option value="scheduled">예정</option>
|
||||
<option value="ended">종료</option>
|
||||
</select>
|
||||
<select id="periodFilter" class="form-select" style="flex: 1; min-width: 140px;">
|
||||
<option value="1month">최근 1개월</option>
|
||||
<option value="3months">최근 3개월</option>
|
||||
<option value="6months">최근 6개월</option>
|
||||
<option value="1year">최근 1년</option>
|
||||
<option value="all">전체</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sorting -->
|
||||
<section class="mb-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-body-small text-secondary">정렬:</p>
|
||||
<select id="sortBy" class="form-select" style="width: 160px;">
|
||||
<option value="latest">최신순</option>
|
||||
<option value="participants">참여자순</option>
|
||||
<option value="roi">투자대비수익률순</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event List -->
|
||||
<section id="eventList" class="mb-2xl">
|
||||
<!-- Events will be dynamically loaded here -->
|
||||
</section>
|
||||
|
||||
<!-- Pagination -->
|
||||
<section class="mb-2xl">
|
||||
<div class="flex items-center justify-center gap-sm" id="pagination">
|
||||
<!-- Pagination will be dynamically loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '이벤트 목록',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: true
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// Mock 이벤트 데이터
|
||||
const events = [
|
||||
{
|
||||
id: 1,
|
||||
title: '신규고객 유치 이벤트',
|
||||
status: 'active',
|
||||
daysLeft: 5,
|
||||
participants: 128,
|
||||
roi: 450,
|
||||
startDate: '2025-11-01',
|
||||
endDate: '2025-11-15',
|
||||
prize: '커피 쿠폰',
|
||||
method: '전화번호 입력'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '재방문 유도 이벤트',
|
||||
status: 'active',
|
||||
daysLeft: 12,
|
||||
participants: 56,
|
||||
roi: 320,
|
||||
startDate: '2025-11-05',
|
||||
endDate: '2025-11-20',
|
||||
prize: '할인 쿠폰',
|
||||
method: 'SNS 팔로우'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '매출증대 프로모션',
|
||||
status: 'ended',
|
||||
daysLeft: 0,
|
||||
participants: 234,
|
||||
roi: 580,
|
||||
startDate: '2025-10-15',
|
||||
endDate: '2025-10-31',
|
||||
prize: '상품권',
|
||||
method: '구매 인증'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '봄맞이 특별 이벤트',
|
||||
status: 'scheduled',
|
||||
daysLeft: 30,
|
||||
participants: 0,
|
||||
roi: 0,
|
||||
startDate: '2025-12-01',
|
||||
endDate: '2025-12-15',
|
||||
prize: '체험권',
|
||||
method: '이메일 등록'
|
||||
}
|
||||
];
|
||||
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 20;
|
||||
let filteredEvents = [...events];
|
||||
|
||||
// 이벤트 카드 생성
|
||||
function createEventCard(event) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'event-card';
|
||||
card.style.cursor = 'pointer';
|
||||
|
||||
const statusBadge = event.status === 'active' ? 'badge-active' :
|
||||
event.status === 'scheduled' ? 'badge-scheduled' : 'badge-inactive';
|
||||
|
||||
const statusText = event.status === 'active' ? '진행중' :
|
||||
event.status === 'scheduled' ? '예정' : '종료';
|
||||
|
||||
const daysInfo = event.status === 'active' ? `D-${event.daysLeft}` :
|
||||
event.status === 'scheduled' ? `D+${event.daysLeft}` : '';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex items-start justify-between mb-sm">
|
||||
<h3 class="text-headline">${event.title}</h3>
|
||||
<span class="event-card-badge ${statusBadge}">${statusText} ${daysInfo ? '| ' + daysInfo : ''}</span>
|
||||
</div>
|
||||
|
||||
<div class="event-card-stats mb-sm">
|
||||
<div class="event-card-stat">
|
||||
<span class="event-card-stat-label">참여</span>
|
||||
<span class="event-card-stat-value">${event.participants}명</span>
|
||||
</div>
|
||||
<div class="event-card-stat">
|
||||
<span class="event-card-stat-label">투자대비수익률</span>
|
||||
<span class="event-card-stat-value text-kt-red">${event.roi}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-body-small text-secondary">
|
||||
${event.startDate} ~ ${event.endDate}
|
||||
</p>
|
||||
`;
|
||||
|
||||
card.addEventListener('click', () => {
|
||||
window.location.href = `13-이벤트상세.html?id=${event.id}`;
|
||||
});
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 이벤트 목록 렌더링
|
||||
function renderEventList() {
|
||||
const eventList = document.getElementById('eventList');
|
||||
eventList.innerHTML = '';
|
||||
|
||||
if (filteredEvents.length === 0) {
|
||||
eventList.innerHTML = `
|
||||
<div class="text-center py-2xl">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-300);">event_busy</span>
|
||||
<p class="text-body text-secondary mt-md">검색 결과가 없습니다</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, filteredEvents.length);
|
||||
const pageEvents = filteredEvents.slice(startIndex, endIndex);
|
||||
|
||||
pageEvents.forEach(event => {
|
||||
eventList.appendChild(createEventCard(event));
|
||||
});
|
||||
|
||||
renderPagination();
|
||||
}
|
||||
|
||||
// 페이지네이션 렌더링
|
||||
function renderPagination() {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
const totalPages = Math.ceil(filteredEvents.length / itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) return;
|
||||
|
||||
// 이전 버튼
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'btn btn-text btn-small';
|
||||
prevBtn.innerHTML = '<span class="material-icons">chevron_left</span>';
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderEventList();
|
||||
}
|
||||
});
|
||||
pagination.appendChild(prevBtn);
|
||||
|
||||
// 페이지 번호
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
if (i === 1 || i === totalPages || (i >= currentPage - 1 && i <= currentPage + 1)) {
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = `btn ${i === currentPage ? 'btn-primary' : 'btn-text'} btn-small`;
|
||||
pageBtn.textContent = i;
|
||||
pageBtn.addEventListener('click', () => {
|
||||
currentPage = i;
|
||||
renderEventList();
|
||||
});
|
||||
pagination.appendChild(pageBtn);
|
||||
} else if (i === currentPage - 2 || i === currentPage + 2) {
|
||||
const ellipsis = document.createElement('span');
|
||||
ellipsis.textContent = '...';
|
||||
ellipsis.className = 'text-secondary px-sm';
|
||||
pagination.appendChild(ellipsis);
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 버튼
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'btn btn-text btn-small';
|
||||
nextBtn.innerHTML = '<span class="material-icons">chevron_right</span>';
|
||||
nextBtn.disabled = currentPage === totalPages;
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderEventList();
|
||||
}
|
||||
});
|
||||
pagination.appendChild(nextBtn);
|
||||
}
|
||||
|
||||
// 필터 적용
|
||||
function applyFilters() {
|
||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||
const statusFilter = document.getElementById('statusFilter').value;
|
||||
const periodFilter = document.getElementById('periodFilter').value;
|
||||
const sortBy = document.getElementById('sortBy').value;
|
||||
|
||||
// 필터링
|
||||
filteredEvents = events.filter(event => {
|
||||
const matchesSearch = event.title.toLowerCase().includes(searchTerm);
|
||||
const matchesStatus = statusFilter === 'all' || event.status === statusFilter;
|
||||
|
||||
// 기간 필터 (간단한 예시)
|
||||
let matchesPeriod = true;
|
||||
if (periodFilter !== 'all') {
|
||||
// 실제로는 날짜 비교 로직 필요
|
||||
matchesPeriod = true;
|
||||
}
|
||||
|
||||
return matchesSearch && matchesStatus && matchesPeriod;
|
||||
});
|
||||
|
||||
// 정렬
|
||||
filteredEvents.sort((a, b) => {
|
||||
if (sortBy === 'latest') {
|
||||
return new Date(b.startDate) - new Date(a.startDate);
|
||||
} else if (sortBy === 'participants') {
|
||||
return b.participants - a.participants;
|
||||
} else if (sortBy === 'roi') {
|
||||
return b.roi - a.roi;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
currentPage = 1;
|
||||
renderEventList();
|
||||
}
|
||||
|
||||
// 이벤트 리스너
|
||||
document.getElementById('searchInput').addEventListener('input', KTEventApp.Utils.debounce(applyFilters, 300));
|
||||
document.getElementById('statusFilter').addEventListener('change', applyFilters);
|
||||
document.getElementById('periodFilter').addEventListener('change', applyFilters);
|
||||
document.getElementById('sortBy').addEventListener('change', applyFilters);
|
||||
|
||||
// 초기 렌더링
|
||||
renderEventList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 목적 선택 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 600px;">
|
||||
<!-- Title Section -->
|
||||
<section class="mt-lg mb-xl text-center">
|
||||
<div class="mb-md">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-ai-blue);">auto_awesome</span>
|
||||
</div>
|
||||
<h2 class="text-title-large mb-sm">이벤트 목적을 선택해주세요</h2>
|
||||
<p class="text-body text-secondary">AI가 목적에 맞는 최적의 이벤트를 추천해드립니다</p>
|
||||
</section>
|
||||
|
||||
<!-- Purpose Options -->
|
||||
<section class="mb-xl">
|
||||
<div id="purposeOptions" class="grid gap-md tablet:grid-cols-2">
|
||||
<!-- 옵션 카드가 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<button id="nextBtn" class="btn btn-primary btn-large btn-full" disabled>
|
||||
다음
|
||||
</button>
|
||||
<button id="skipBtn" class="btn btn-text btn-large btn-full mt-sm">
|
||||
건너뛰기
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Info Box -->
|
||||
<section class="mb-2xl">
|
||||
<div class="card" style="background: rgba(0, 102, 255, 0.05); border: 1px solid rgba(0, 102, 255, 0.2);">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-ai-blue">info</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-secondary">
|
||||
선택하신 목적에 따라 AI가 업종, 지역, 계절 트렌드를 분석하여 가장 효과적인 이벤트를 추천합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
const user = KTEventApp.Session.getUser();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '이벤트 목적 선택',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// 이벤트 목적 옵션 데이터
|
||||
const purposes = KTEventApp.MockData.getEventPurposes();
|
||||
|
||||
// 선택된 목적 저장
|
||||
let selectedPurpose = null;
|
||||
|
||||
// 옵션 카드 생성
|
||||
const purposeOptionsContainer = document.getElementById('purposeOptions');
|
||||
|
||||
purposes.forEach(purpose => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card option-card';
|
||||
card.setAttribute('data-purpose-id', purpose.id);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="purpose" value="${purpose.id}">
|
||||
</div>
|
||||
<div class="flex items-start gap-md mb-md">
|
||||
<span class="material-icons text-kt-red" style="font-size: 40px;">${purpose.icon}</span>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-headline mb-xs">${purpose.title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-body-small text-secondary">${purpose.description}</p>
|
||||
`;
|
||||
|
||||
card.addEventListener('click', () => {
|
||||
// 모든 카드에서 selected 클래스 제거
|
||||
document.querySelectorAll('.option-card').forEach(c => {
|
||||
c.classList.remove('selected');
|
||||
c.querySelector('input[type="radio"]').checked = false;
|
||||
});
|
||||
|
||||
// 선택된 카드에 selected 클래스 추가
|
||||
card.classList.add('selected');
|
||||
card.querySelector('input[type="radio"]').checked = true;
|
||||
|
||||
// 선택된 목적 저장
|
||||
selectedPurpose = purpose.id;
|
||||
|
||||
// 다음 버튼 활성화
|
||||
document.getElementById('nextBtn').disabled = false;
|
||||
});
|
||||
|
||||
purposeOptionsContainer.appendChild(card);
|
||||
});
|
||||
|
||||
// 다음 버튼 클릭
|
||||
document.getElementById('nextBtn').addEventListener('click', () => {
|
||||
if (selectedPurpose) {
|
||||
// 선택한 목적 저장
|
||||
KTEventApp.Utils.saveToStorage('selected_purpose', selectedPurpose);
|
||||
|
||||
// AI 로딩 및 추천 페이지로 이동
|
||||
showLoadingAndNavigate();
|
||||
}
|
||||
});
|
||||
|
||||
// 건너뛰기 버튼 클릭
|
||||
document.getElementById('skipBtn').addEventListener('click', () => {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '건너뛰기',
|
||||
content: '<p class="text-body">목적을 선택하지 않으면 일반적인 추천을 받게 됩니다. 계속하시겠습니까?</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
KTEventApp.Utils.saveToStorage('selected_purpose', 'general');
|
||||
showLoadingAndNavigate();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// AI 로딩 표시 및 페이지 이동
|
||||
function showLoadingAndNavigate() {
|
||||
const loadingModal = KTEventApp.Feedback.showModal({
|
||||
content: `
|
||||
<div class="p-xl text-center">
|
||||
<div class="spinner mx-auto mb-lg"></div>
|
||||
<h3 class="text-headline mb-sm">AI가 분석 중입니다</h3>
|
||||
<p class="text-body text-secondary mb-md">
|
||||
업종, 지역, 계절 트렌드를 분석하여<br>
|
||||
최적의 이벤트를 찾고 있습니다
|
||||
</p>
|
||||
<div class="progress mt-lg">
|
||||
<div id="loadingProgress" class="progress-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-caption text-tertiary mt-sm" id="loadingStatus">트렌드 분석 중...</p>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
// 프로그레스 바 애니메이션
|
||||
const statusMessages = [
|
||||
'트렌드 분석 중...',
|
||||
'경쟁사 이벤트 분석 중...',
|
||||
'고객 선호도 분석 중...',
|
||||
'최적 이벤트 생성 중...'
|
||||
];
|
||||
|
||||
let progress = 0;
|
||||
let messageIndex = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
progress += 25;
|
||||
document.getElementById('loadingProgress').style.width = `${progress}%`;
|
||||
|
||||
if (messageIndex < statusMessages.length) {
|
||||
document.getElementById('loadingStatus').textContent = statusMessages[messageIndex];
|
||||
messageIndex++;
|
||||
}
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(interval);
|
||||
setTimeout(() => {
|
||||
window.location.href = '08-AI이벤트추천.html';
|
||||
}, 500);
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,473 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI 이벤트 추천 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
.editable-field {
|
||||
border: 1px dashed var(--color-gray-300);
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: text;
|
||||
transition: all var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
.editable-field:hover {
|
||||
border-color: var(--color-kt-red);
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
.editable-field:focus {
|
||||
outline: none;
|
||||
border-style: solid;
|
||||
border-color: var(--color-kt-red);
|
||||
background-color: var(--color-bg-primary);
|
||||
}
|
||||
.budget-section {
|
||||
scroll-margin-top: 72px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Trends Section -->
|
||||
<section class="mt-lg mb-xl">
|
||||
<h2 class="text-headline mb-md flex items-center gap-sm">
|
||||
<span class="material-icons text-ai-blue">insights</span>
|
||||
AI 트렌드 분석
|
||||
</h2>
|
||||
<div class="card">
|
||||
<div class="mb-md">
|
||||
<div class="flex items-center gap-sm mb-xs">
|
||||
<span class="material-icons text-kt-red" style="font-size: 20px;">store</span>
|
||||
<span class="text-body-small text-bold">업종 트렌드</span>
|
||||
</div>
|
||||
<p id="industryTrend" class="text-body text-secondary pl-lg">음식점업 신년 프로모션 트렌드</p>
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<div class="flex items-center gap-sm mb-xs">
|
||||
<span class="material-icons text-kt-red" style="font-size: 20px;">location_on</span>
|
||||
<span class="text-body-small text-bold">지역 트렌드</span>
|
||||
</div>
|
||||
<p id="locationTrend" class="text-body text-secondary pl-lg">강남구 음식점 할인 이벤트 증가</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-sm mb-xs">
|
||||
<span class="material-icons text-kt-red" style="font-size: 20px;">wb_sunny</span>
|
||||
<span class="text-body-small text-bold">시즌 트렌드</span>
|
||||
</div>
|
||||
<p id="seasonTrend" class="text-body text-secondary pl-lg">설 연휴 특수 대비 고객 유치 전략</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recommendations Title -->
|
||||
<section class="mb-lg">
|
||||
<h2 class="text-headline mb-sm">예산별 추천 이벤트</h2>
|
||||
<p class="text-body-small text-secondary">
|
||||
각 예산별 2가지 방식 (온라인 1개, 오프라인 1개)을 추천합니다
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Budget Options Navigation -->
|
||||
<section class="mb-lg" style="position: sticky; top: 56px; background: var(--color-bg-secondary); z-index: 10; padding: var(--spacing-md) 0;">
|
||||
<div class="flex gap-sm">
|
||||
<button class="btn btn-medium flex-1 budget-nav active" data-budget="low">
|
||||
💰 저비용
|
||||
</button>
|
||||
<button class="btn btn-medium flex-1 budget-nav" data-budget="medium">
|
||||
💰💰 중비용
|
||||
</button>
|
||||
<button class="btn btn-medium flex-1 budget-nav" data-budget="high">
|
||||
💰💰💰 고비용
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 저비용 옵션 -->
|
||||
<section id="budget-low" class="budget-section mb-2xl">
|
||||
<h3 class="text-title mb-md">💰 옵션 1: 저비용 (25~30만원)</h3>
|
||||
<div class="grid gap-md tablet:grid-cols-2">
|
||||
<div class="card option-card" data-recommendation-id="low-online">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="low-online">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">SNS 팔로우 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">커피 쿠폰</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">SNS 팔로우</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">180명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">25만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">520%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card option-card" data-recommendation-id="low-offline">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="low-offline">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">전화번호 등록 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">커피 쿠폰</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">전화번호 등록</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">150명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">30만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">450%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 중비용 옵션 -->
|
||||
<section id="budget-medium" class="budget-section mb-2xl">
|
||||
<h3 class="text-title mb-md">💰💰 옵션 2: 중비용 (150~180만원)</h3>
|
||||
<div class="grid gap-md tablet:grid-cols-2">
|
||||
<div class="card option-card" data-recommendation-id="medium-online">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="medium-online">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">리뷰 작성 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">5천원 상품권</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">리뷰 작성</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">250명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">150만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">380%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card option-card" data-recommendation-id="medium-offline">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="medium-offline">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">방문 도장 적립 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">무료 식사권</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">방문 도장 적립</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">200명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">180만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">320%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 고비용 옵션 -->
|
||||
<section id="budget-high" class="budget-section mb-2xl">
|
||||
<h3 class="text-title mb-md">💰💰💰 옵션 3: 고비용 (500~600만원)</h3>
|
||||
<div class="grid gap-md tablet:grid-cols-2">
|
||||
<div class="card option-card" data-recommendation-id="high-online">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="high-online">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">인플루언서 협업 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">1만원 할인권</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">인플루언서 팔로우</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">400명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">500만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">280%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card option-card" data-recommendation-id="high-offline">
|
||||
<div class="option-card-radio">
|
||||
<input type="radio" name="recommendation" value="high-offline">
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
|
||||
<h4 class="text-headline mb-xs">
|
||||
<span class="editable-field" contenteditable="true">VIP 고객 초대 이벤트</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="text-body-small mb-sm">
|
||||
<span class="text-secondary">경품:</span>
|
||||
<span class="editable-field" contenteditable="true">특별 메뉴 제공</span>
|
||||
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-sm text-body-small">
|
||||
<div>
|
||||
<span class="text-tertiary">참여 방법:</span>
|
||||
<span class="text-semibold">VIP 초대장</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 참여:</span>
|
||||
<span class="text-semibold">300명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">예상 비용:</span>
|
||||
<span class="text-semibold">600만원</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-tertiary">투자대비수익률:</span>
|
||||
<span class="text-kt-red text-bold">240%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<button id="nextBtn" class="btn btn-primary btn-large btn-full" disabled>
|
||||
선택한 이벤트로 계속
|
||||
</button>
|
||||
<button id="regenerateBtn" class="btn btn-secondary btn-large btn-full mt-sm">
|
||||
<span class="material-icons">refresh</span>
|
||||
다시 추천받기
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: 'AI 이벤트 추천',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// 선택된 추천 저장
|
||||
let selectedRecommendation = null;
|
||||
|
||||
// 옵션 카드 클릭 이벤트
|
||||
document.querySelectorAll('.option-card').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
// 모든 카드에서 selected 클래스 제거
|
||||
document.querySelectorAll('.option-card').forEach(c => {
|
||||
c.classList.remove('selected');
|
||||
c.querySelector('input[type="radio"]').checked = false;
|
||||
});
|
||||
|
||||
// 선택된 카드에 selected 클래스 추가
|
||||
card.classList.add('selected');
|
||||
card.querySelector('input[type="radio"]').checked = true;
|
||||
|
||||
// 선택된 추천 저장
|
||||
selectedRecommendation = card.getAttribute('data-recommendation-id');
|
||||
|
||||
// 다음 버튼 활성화
|
||||
document.getElementById('nextBtn').disabled = false;
|
||||
|
||||
// Toast 표시
|
||||
KTEventApp.Feedback.showToast('이벤트가 선택되었습니다');
|
||||
});
|
||||
});
|
||||
|
||||
// 예산 옵션 네비게이션
|
||||
document.querySelectorAll('.budget-nav').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const budget = this.getAttribute('data-budget');
|
||||
|
||||
// 버튼 활성화 상태 변경
|
||||
document.querySelectorAll('.budget-nav').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.classList.add('btn-secondary');
|
||||
b.classList.remove('btn-primary');
|
||||
});
|
||||
this.classList.add('active');
|
||||
this.classList.add('btn-primary');
|
||||
this.classList.remove('btn-secondary');
|
||||
|
||||
// 해당 섹션으로 스크롤
|
||||
document.getElementById(`budget-${budget}`).scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 다음 버튼 클릭
|
||||
document.getElementById('nextBtn').addEventListener('click', () => {
|
||||
if (selectedRecommendation) {
|
||||
// 선택한 추천 데이터 저장
|
||||
const selectedCard = document.querySelector(`[data-recommendation-id="${selectedRecommendation}"]`);
|
||||
const title = selectedCard.querySelector('.editable-field').textContent.trim();
|
||||
const prize = selectedCard.querySelectorAll('.editable-field')[1].textContent.trim();
|
||||
|
||||
const recommendationData = {
|
||||
id: selectedRecommendation,
|
||||
title: title,
|
||||
prize: prize,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
KTEventApp.Utils.saveToStorage('selected_recommendation', recommendationData);
|
||||
|
||||
// SNS 이미지 생성 페이지로 이동
|
||||
window.location.href = '09-콘텐츠미리보기.html';
|
||||
}
|
||||
});
|
||||
|
||||
// 다시 추천받기 버튼
|
||||
document.getElementById('regenerateBtn').addEventListener('click', () => {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '다시 추천받기',
|
||||
content: '<p class="text-body">새로운 추천을 받으시겠습니까? 현재 선택한 내용은 초기화됩니다.</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
window.location.href = '07-이벤트목적선택.html';
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// 편집 가능 필드 엔터 키 방지
|
||||
document.querySelectorAll('.editable-field').forEach(field => {
|
||||
field.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
field.blur();
|
||||
}
|
||||
});
|
||||
|
||||
field.addEventListener('blur', () => {
|
||||
KTEventApp.Feedback.showToast('수정되었습니다', 1500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,447 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SNS 이미지 생성 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"
|
||||
/>
|
||||
<style>
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.image-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.image-preview-placeholder {
|
||||
color: var(--color-gray-400);
|
||||
text-align: center;
|
||||
}
|
||||
.style-card {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
.style-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.style-card.selected {
|
||||
border-color: var(--color-kt-red);
|
||||
box-shadow: 0 4px 12px rgba(227, 30, 36, 0.2);
|
||||
}
|
||||
.style-card .form-check-input {
|
||||
display: none;
|
||||
}
|
||||
.selected-badge {
|
||||
position: absolute;
|
||||
top: var(--spacing-md);
|
||||
right: var(--spacing-md);
|
||||
background: var(--color-kt-red);
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.style-card.selected .selected-badge {
|
||||
display: flex;
|
||||
}
|
||||
.fullscreen-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
.fullscreen-modal.active {
|
||||
display: flex;
|
||||
}
|
||||
.fullscreen-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.fullscreen-close {
|
||||
position: absolute;
|
||||
top: var(--spacing-lg);
|
||||
right: var(--spacing-lg);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: var(--color-text-primary);
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 600px">
|
||||
<!-- Loading State -->
|
||||
<div id="loadingState" class="text-center mt-2xl mb-2xl">
|
||||
<span
|
||||
class="material-icons"
|
||||
style="
|
||||
font-size: 64px;
|
||||
color: var(--color-ai-blue);
|
||||
animation: spin 2s linear infinite;
|
||||
"
|
||||
>psychology</span
|
||||
>
|
||||
<h3 class="text-headline mt-md mb-sm">AI 이미지 생성 중</h3>
|
||||
<p class="text-body text-secondary mb-lg">
|
||||
딥러닝 모델이 이벤트에 어울리는<br />
|
||||
이미지를 생성하고 있어요...
|
||||
</p>
|
||||
<p class="text-caption text-tertiary mt-md">예상 시간: 5초</p>
|
||||
</div>
|
||||
|
||||
<!-- Generated Images (hidden initially) -->
|
||||
<div id="generatedImages" class="hidden">
|
||||
<!-- Style 1: 심플 -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">스타일 1: 심플</h3>
|
||||
<div class="card style-card" data-style="simple">
|
||||
<div class="selected-badge">
|
||||
<span class="material-icons" style="font-size: 20px">check</span>
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name="imageStyle"
|
||||
id="style1"
|
||||
value="simple"
|
||||
class="form-check-input"
|
||||
/>
|
||||
<div class="image-preview mb-md">
|
||||
<div class="image-preview-placeholder">
|
||||
<span class="material-icons" style="font-size: 48px"
|
||||
>celebration</span
|
||||
>
|
||||
<p class="text-body-small mt-sm" id="style1Title">
|
||||
SNS 팔로우 이벤트
|
||||
</p>
|
||||
<p class="text-caption text-secondary" id="style1Prize">
|
||||
커피 쿠폰
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
class="btn btn-text btn-small preview-btn"
|
||||
data-style="simple"
|
||||
>
|
||||
<span class="material-icons">zoom_in</span>
|
||||
크게보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Style 2: 화려 -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">스타일 2: 화려</h3>
|
||||
<div class="card style-card" data-style="fancy">
|
||||
<div class="selected-badge">
|
||||
<span class="material-icons" style="font-size: 20px">check</span>
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name="imageStyle"
|
||||
id="style2"
|
||||
value="fancy"
|
||||
class="form-check-input"
|
||||
/>
|
||||
<div
|
||||
class="image-preview mb-md"
|
||||
style="
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
"
|
||||
>
|
||||
<div class="image-preview-placeholder" style="color: white">
|
||||
<span class="material-icons" style="font-size: 48px"
|
||||
>auto_awesome</span
|
||||
>
|
||||
<p class="text-body-small mt-sm" id="style2Title">
|
||||
SNS 팔로우 이벤트
|
||||
</p>
|
||||
<p class="text-caption" style="opacity: 0.9" id="style2Prize">
|
||||
커피 쿠폰
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
class="btn btn-text btn-small preview-btn"
|
||||
data-style="fancy"
|
||||
>
|
||||
<span class="material-icons">zoom_in</span>
|
||||
크게보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Style 3: 트렌디 -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">스타일 3: 트렌디</h3>
|
||||
<div class="card style-card" data-style="trendy">
|
||||
<div class="selected-badge">
|
||||
<span class="material-icons" style="font-size: 20px">check</span>
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name="imageStyle"
|
||||
id="style3"
|
||||
value="trendy"
|
||||
class="form-check-input"
|
||||
/>
|
||||
<div
|
||||
class="image-preview mb-md"
|
||||
style="
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
"
|
||||
>
|
||||
<div class="image-preview-placeholder" style="color: white">
|
||||
<span class="material-icons" style="font-size: 48px"
|
||||
>trending_up</span
|
||||
>
|
||||
<p class="text-body-small mt-sm" id="style3Title">
|
||||
SNS 팔로우 이벤트
|
||||
</p>
|
||||
<p class="text-caption" style="opacity: 0.9" id="style3Prize">
|
||||
커피 쿠폰
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
class="btn btn-text btn-small preview-btn"
|
||||
data-style="trendy"
|
||||
>
|
||||
<span class="material-icons">zoom_in</span>
|
||||
크게보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<div class="grid grid-cols-2 gap-sm">
|
||||
<button id="skipBtn" class="btn btn-text btn-large">
|
||||
건너뛰기
|
||||
</button>
|
||||
<button id="nextBtn" class="btn btn-primary btn-large" disabled>
|
||||
다음
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<!-- Fullscreen Image Modal -->
|
||||
<div id="fullscreenModal" class="fullscreen-modal">
|
||||
<button class="fullscreen-close" onclick="closeFullscreen()">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
<img
|
||||
id="fullscreenImage"
|
||||
class="fullscreen-image"
|
||||
alt="이벤트 이미지 미리보기"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: "SNS 이미지 생성",
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false,
|
||||
});
|
||||
document.getElementById("header").appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav("events");
|
||||
document.getElementById("bottomNav").appendChild(bottomNav);
|
||||
|
||||
// 저장된 데이터 불러오기
|
||||
const recommendation = KTEventApp.Utils.getFromStorage(
|
||||
"selected_recommendation"
|
||||
) || {
|
||||
title: "SNS 팔로우 이벤트",
|
||||
prize: "커피 쿠폰",
|
||||
};
|
||||
|
||||
// 로딩 시뮬레이션
|
||||
setTimeout(() => {
|
||||
// 이미지 생성 완료
|
||||
document.getElementById("loadingState").classList.add("hidden");
|
||||
document.getElementById("generatedImages").classList.remove("hidden");
|
||||
|
||||
// 데이터 표시
|
||||
document.getElementById("style1Title").textContent =
|
||||
recommendation.title;
|
||||
document.getElementById("style1Prize").textContent =
|
||||
recommendation.prize;
|
||||
document.getElementById("style2Title").textContent =
|
||||
recommendation.title;
|
||||
document.getElementById("style2Prize").textContent =
|
||||
recommendation.prize;
|
||||
document.getElementById("style3Title").textContent =
|
||||
recommendation.title;
|
||||
document.getElementById("style3Prize").textContent =
|
||||
recommendation.prize;
|
||||
}, 5000);
|
||||
|
||||
// 선택 상태 관리
|
||||
let selectedStyle = null;
|
||||
|
||||
// 카드 클릭 이벤트
|
||||
document.querySelectorAll('.style-card').forEach((card) => {
|
||||
card.addEventListener('click', function(e) {
|
||||
// 크게보기 버튼 클릭은 무시
|
||||
if (e.target.closest('.preview-btn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styleValue = this.dataset.style;
|
||||
const radioInput = this.querySelector('input[name="imageStyle"]');
|
||||
|
||||
// 모든 카드의 선택 상태 제거
|
||||
document.querySelectorAll('.style-card').forEach(c => {
|
||||
c.classList.remove('selected');
|
||||
});
|
||||
|
||||
// 현재 카드 선택
|
||||
this.classList.add('selected');
|
||||
radioInput.checked = true;
|
||||
|
||||
selectedStyle = styleValue;
|
||||
document.getElementById("nextBtn").disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// 크게보기 버튼
|
||||
document.querySelectorAll(".preview-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.stopPropagation(); // 카드 클릭 이벤트 방지
|
||||
const style = this.dataset.style;
|
||||
openFullscreen(style);
|
||||
});
|
||||
});
|
||||
|
||||
function openFullscreen(style) {
|
||||
const modal = document.getElementById("fullscreenModal");
|
||||
const img = document.getElementById("fullscreenImage");
|
||||
|
||||
// 실제로는 생성된 이미지 URL을 사용
|
||||
// 여기서는 placeholder 사용
|
||||
img.src =
|
||||
'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800"><rect width="800" height="800" fill="%23f0f0f0"/><text x="50%" y="50%" text-anchor="middle" fill="%23666" font-size="24">' +
|
||||
recommendation.title +
|
||||
"</text></svg>";
|
||||
|
||||
modal.classList.add("active");
|
||||
}
|
||||
|
||||
window.closeFullscreen = function () {
|
||||
document.getElementById("fullscreenModal").classList.remove("active");
|
||||
};
|
||||
|
||||
// 배경 클릭 시 닫기
|
||||
document
|
||||
.getElementById("fullscreenModal")
|
||||
.addEventListener("click", function (e) {
|
||||
if (e.target === this) {
|
||||
closeFullscreen();
|
||||
}
|
||||
});
|
||||
|
||||
// 건너뛰기 버튼
|
||||
document.getElementById("skipBtn").addEventListener("click", function () {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: "건너뛰기",
|
||||
content:
|
||||
'<p class="text-body">이미지 없이 다음 단계로 진행하시겠습니까?</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: "취소",
|
||||
variant: "text",
|
||||
onClick: function () {
|
||||
this.closest(".modal-backdrop").remove();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "확인",
|
||||
variant: "primary",
|
||||
onClick: function () {
|
||||
this.closest(".modal-backdrop").remove();
|
||||
window.location.href = "11-배포채널선택.html";
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// 다음 버튼
|
||||
document.getElementById("nextBtn").addEventListener("click", function () {
|
||||
if (selectedStyle) {
|
||||
KTEventApp.Utils.saveToStorage("selected_image_style", selectedStyle);
|
||||
window.location.href = "10-콘텐츠편집.html";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,209 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>콘텐츠 편집 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
.preview-container {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.logo-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
}
|
||||
.color-picker-wrapper {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
}
|
||||
.color-swatch {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 2px solid var(--color-gray-300);
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="color"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<!-- Desktop Layout: Side-by-side -->
|
||||
<div class="grid desktop:grid-cols-2 gap-lg mt-lg mb-2xl">
|
||||
<!-- Preview Section -->
|
||||
<section>
|
||||
<h3 class="text-headline mb-md">미리보기</h3>
|
||||
<div class="card">
|
||||
<div id="preview" class="preview-container">
|
||||
<span class="material-icons" style="font-size: 48px; margin-bottom: 16px;">celebration</span>
|
||||
<h3 id="previewTitle" class="text-title" style="margin-bottom: 8px;">신규고객 유치 이벤트</h3>
|
||||
<p id="previewPrize" class="text-body" style="margin-bottom: 16px;">커피 쿠폰 100매</p>
|
||||
<p id="previewGuide" class="text-body-small">전화번호를 입력하고 참여하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Edit Section -->
|
||||
<section>
|
||||
<h3 class="text-headline mb-md">편집</h3>
|
||||
|
||||
<!-- Text Editing -->
|
||||
<div class="card mb-md">
|
||||
<h4 class="text-headline mb-md">
|
||||
<span class="material-icons" style="vertical-align: middle;">edit</span>
|
||||
텍스트 편집
|
||||
</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="titleInput" class="form-label">제목</label>
|
||||
<input type="text" id="titleInput" class="form-input" value="신규고객 유치 이벤트" maxlength="50">
|
||||
<span class="form-hint"><span id="titleCount">11</span>/50자</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="prizeInput" class="form-label">경품</label>
|
||||
<input type="text" id="prizeInput" class="form-input" value="커피 쿠폰 100매" maxlength="30">
|
||||
<span class="form-hint"><span id="prizeCount">9</span>/30자</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="guideInput" class="form-label">참여안내</label>
|
||||
<textarea id="guideInput" class="form-textarea" rows="3" maxlength="100">전화번호를 입력하고 참여하세요</textarea>
|
||||
<span class="form-hint"><span id="guideCount">17</span>/100자</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<div class="grid grid-cols-2 gap-sm">
|
||||
<button id="saveBtn" class="btn btn-secondary btn-large">저장</button>
|
||||
<button id="nextBtn" class="btn btn-primary btn-large">다음 단계</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '콘텐츠 편집',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// 저장된 데이터 불러오기
|
||||
const recommendation = KTEventApp.Utils.getFromStorage('selected_recommendation') || {
|
||||
title: '신규고객 유치 이벤트',
|
||||
prize: '커피 쿠폰 100매'
|
||||
};
|
||||
|
||||
// 초기 데이터 설정
|
||||
document.getElementById('titleInput').value = recommendation.title || '신규고객 유치 이벤트';
|
||||
document.getElementById('prizeInput').value = recommendation.prize || '커피 쿠폰 100매';
|
||||
updateCharCount();
|
||||
|
||||
// 실시간 미리보기 업데이트
|
||||
function updatePreview() {
|
||||
const title = document.getElementById('titleInput').value;
|
||||
const prize = document.getElementById('prizeInput').value;
|
||||
const guide = document.getElementById('guideInput').value;
|
||||
|
||||
document.getElementById('previewTitle').textContent = title || '제목을 입력하세요';
|
||||
document.getElementById('previewPrize').textContent = prize || '경품을 입력하세요';
|
||||
document.getElementById('previewGuide').textContent = guide || '참여 안내를 입력하세요';
|
||||
}
|
||||
|
||||
// 글자수 업데이트
|
||||
function updateCharCount() {
|
||||
document.getElementById('titleCount').textContent = document.getElementById('titleInput').value.length;
|
||||
document.getElementById('prizeCount').textContent = document.getElementById('prizeInput').value.length;
|
||||
document.getElementById('guideCount').textContent = document.getElementById('guideInput').value.length;
|
||||
}
|
||||
|
||||
// 텍스트 입력 이벤트
|
||||
document.getElementById('titleInput').addEventListener('input', function() {
|
||||
updatePreview();
|
||||
updateCharCount();
|
||||
});
|
||||
|
||||
document.getElementById('prizeInput').addEventListener('input', function() {
|
||||
updatePreview();
|
||||
updateCharCount();
|
||||
});
|
||||
|
||||
document.getElementById('guideInput').addEventListener('input', function() {
|
||||
updatePreview();
|
||||
updateCharCount();
|
||||
});
|
||||
|
||||
// 저장 버튼
|
||||
document.getElementById('saveBtn').addEventListener('click', function() {
|
||||
const editData = {
|
||||
title: document.getElementById('titleInput').value,
|
||||
prize: document.getElementById('prizeInput').value,
|
||||
guide: document.getElementById('guideInput').value
|
||||
};
|
||||
|
||||
KTEventApp.Utils.saveToStorage('content_edit', editData);
|
||||
KTEventApp.Feedback.showToast('편집 내용이 저장되었습니다');
|
||||
});
|
||||
|
||||
// 다음 단계 버튼
|
||||
document.getElementById('nextBtn').addEventListener('click', function() {
|
||||
// 저장 먼저 실행
|
||||
const editData = {
|
||||
title: document.getElementById('titleInput').value,
|
||||
prize: document.getElementById('prizeInput').value,
|
||||
guide: document.getElementById('guideInput').value
|
||||
};
|
||||
|
||||
KTEventApp.Utils.saveToStorage('content_edit', editData);
|
||||
|
||||
// 배포 채널 선택으로 이동
|
||||
window.location.href = '11-배포채널선택.html';
|
||||
});
|
||||
|
||||
// 초기 미리보기 업데이트
|
||||
updatePreview();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,421 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>배포 채널 선택 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
.channel-card {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.channel-card.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
.channel-options {
|
||||
display: none;
|
||||
margin-top: var(--spacing-md);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
.channel-card.selected .channel-options {
|
||||
display: block;
|
||||
}
|
||||
.channel-options .form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
.channel-options .form-check-input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.channel-options .form-check-label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<!-- Title Section -->
|
||||
<section class="mt-lg mb-lg text-center">
|
||||
<h2 class="text-title mb-sm">배포 채널을 선택해주세요</h2>
|
||||
<p class="text-body text-secondary">(최소 1개 이상)</p>
|
||||
</section>
|
||||
|
||||
<!-- Channel 1: 우리동네TV -->
|
||||
<section class="mb-md">
|
||||
<div class="card channel-card" id="channel1Card">
|
||||
<div class="flex items-start gap-md">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="channel1" class="form-check-input channel-checkbox" data-channel="uriTV">
|
||||
<label for="channel1" class="form-check-label">우리동네TV</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-options" id="channel1Options">
|
||||
<div class="form-group">
|
||||
<label class="form-label">반경</label>
|
||||
<select class="form-select" id="uriRadius">
|
||||
<option value="500">500m</option>
|
||||
<option value="1000">1km</option>
|
||||
<option value="2000">2km</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">노출 시간대</label>
|
||||
<select class="form-select" id="uriTime">
|
||||
<option value="morning">아침 (7-12시)</option>
|
||||
<option value="afternoon">점심 (12-17시)</option>
|
||||
<option value="evening" selected>저녁 (17-22시)</option>
|
||||
<option value="all">전체</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="text-body-small text-secondary">
|
||||
<p>예상 노출: <strong id="uriExposure">5만명</strong></p>
|
||||
<p>비용: <strong id="uriCost">8만원</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Channel 2: 링고비즈 -->
|
||||
<section class="mb-md">
|
||||
<div class="card channel-card" id="channel2Card">
|
||||
<div class="flex items-start gap-md">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="channel2" class="form-check-input channel-checkbox" data-channel="ringoBiz">
|
||||
<label for="channel2" class="form-check-label">링고비즈</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-options" id="channel2Options">
|
||||
<div class="form-group">
|
||||
<label class="form-label">매장 전화번호</label>
|
||||
<input type="tel" class="form-input" id="ringoPhone" value="010-1234-5678" readonly>
|
||||
</div>
|
||||
|
||||
<div class="text-body-small text-secondary">
|
||||
<p>연결음 자동 업데이트</p>
|
||||
<p>예상 노출: <strong id="ringoExposure">3만명</strong></p>
|
||||
<p>비용: <strong id="ringoCost">무료</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Channel 3: 지니TV 광고 -->
|
||||
<section class="mb-md">
|
||||
<div class="card channel-card" id="channel3Card">
|
||||
<div class="flex items-start gap-md">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="channel3" class="form-check-input channel-checkbox" data-channel="genieTV">
|
||||
<label for="channel3" class="form-check-label">지니TV 광고</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-options" id="channel3Options">
|
||||
<div class="form-group">
|
||||
<label class="form-label">지역</label>
|
||||
<select class="form-select" id="genieRegion">
|
||||
<option value="suwon" selected>수원</option>
|
||||
<option value="seoul">서울</option>
|
||||
<option value="busan">부산</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">노출 시간대</label>
|
||||
<select class="form-select" id="genieTime">
|
||||
<option value="all" selected>전체</option>
|
||||
<option value="prime">프라임 (19-23시)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">예산</label>
|
||||
<input type="number" class="form-input" id="genieBudget" placeholder="예산을 입력하세요" min="0" step="10000">
|
||||
</div>
|
||||
|
||||
<div class="text-body-small text-secondary">
|
||||
<p>예상 노출: <strong id="genieExposure">계산중...</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Channel 4: SNS -->
|
||||
<section class="mb-md">
|
||||
<div class="card channel-card" id="channel4Card">
|
||||
<div class="flex items-start gap-md">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="channel4" class="form-check-input channel-checkbox" data-channel="sns">
|
||||
<label for="channel4" class="form-check-label">SNS</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-options" id="channel4Options">
|
||||
<div class="form-group">
|
||||
<label class="form-label">플랫폼 선택</label>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="snsInstagram" class="form-check-input" checked>
|
||||
<label for="snsInstagram" class="form-check-label">Instagram</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="snsNaver" class="form-check-input" checked>
|
||||
<label for="snsNaver" class="form-check-label">Naver Blog</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="snsKakao" class="form-check-input">
|
||||
<label for="snsKakao" class="form-check-label">Kakao Channel</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">예약 게시</label>
|
||||
<select class="form-select" id="snsSchedule">
|
||||
<option value="now" selected>즉시</option>
|
||||
<option value="schedule">예약</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="text-body-small text-secondary">
|
||||
<p>예상 노출: <strong id="snsExposure">-</strong></p>
|
||||
<p>비용: <strong id="snsCost">무료</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Summary -->
|
||||
<section class="mb-lg">
|
||||
<div class="card" style="background: var(--color-gray-50);">
|
||||
<div class="flex items-center justify-between mb-sm">
|
||||
<span class="text-headline">총 예상 비용</span>
|
||||
<span class="text-title text-kt-red" id="totalCost">0원</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-headline">총 예상 노출</span>
|
||||
<span class="text-title text-ai-blue" id="totalExposure">0명</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Button -->
|
||||
<section class="mb-2xl">
|
||||
<button id="nextBtn" class="btn btn-primary btn-large btn-full" disabled>
|
||||
다음 단계
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '배포 채널 선택',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// 선택된 채널 추적
|
||||
const selectedChannels = new Set();
|
||||
|
||||
// 채널 선택 이벤트
|
||||
document.querySelectorAll('.channel-checkbox').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const channel = this.dataset.channel;
|
||||
const card = this.closest('.channel-card');
|
||||
|
||||
if (this.checked) {
|
||||
selectedChannels.add(channel);
|
||||
card.classList.add('selected');
|
||||
} else {
|
||||
selectedChannels.delete(channel);
|
||||
card.classList.remove('selected');
|
||||
}
|
||||
|
||||
updateSummary();
|
||||
updateNextButton();
|
||||
});
|
||||
});
|
||||
|
||||
// 지니TV 예산 입력 시 예상 노출 계산
|
||||
document.getElementById('genieBudget')?.addEventListener('input', function() {
|
||||
const budget = parseInt(this.value) || 0;
|
||||
const exposure = Math.floor(budget / 100) * 1000; // 10만원당 1만명 노출 (예시)
|
||||
document.getElementById('genieExposure').textContent = exposure > 0 ? `${(exposure / 10000).toFixed(1)}만명` : '계산중...';
|
||||
updateSummary();
|
||||
});
|
||||
|
||||
// SNS 예약 게시 선택 시 모달 표시
|
||||
let scheduledDateTime = null;
|
||||
document.getElementById('snsSchedule').addEventListener('change', function() {
|
||||
if (this.value === 'schedule') {
|
||||
showScheduleModal();
|
||||
}
|
||||
});
|
||||
|
||||
function showScheduleModal() {
|
||||
// 현재 날짜/시간 기본값 설정
|
||||
const now = new Date();
|
||||
const tomorrow = new Date(now);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
const dateStr = tomorrow.toISOString().split('T')[0];
|
||||
const timeStr = '09:00';
|
||||
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '예약 배포 설정',
|
||||
content: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">배포 날짜</label>
|
||||
<input type="date" id="scheduleDate" class="form-input" value="${dateStr}" min="${dateStr}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">배포 시간</label>
|
||||
<input type="time" id="scheduleTime" class="form-input" value="${timeStr}">
|
||||
</div>
|
||||
<p class="text-body-small text-secondary mt-md">
|
||||
* 설정한 시간에 자동으로 SNS에 게시됩니다
|
||||
</p>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
// 즉시로 되돌리기
|
||||
document.getElementById('snsSchedule').value = 'now';
|
||||
scheduledDateTime = null;
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
const date = document.getElementById('scheduleDate').value;
|
||||
const time = document.getElementById('scheduleTime').value;
|
||||
|
||||
if (!date || !time) {
|
||||
KTEventApp.Feedback.showToast('날짜와 시간을 모두 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
scheduledDateTime = {
|
||||
date: date,
|
||||
time: time,
|
||||
datetime: `${date} ${time}`
|
||||
};
|
||||
|
||||
KTEventApp.Feedback.showToast(`${date} ${time}에 배포 예약되었습니다`);
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 요약 업데이트
|
||||
function updateSummary() {
|
||||
let totalCost = 0;
|
||||
let totalExposure = 0;
|
||||
|
||||
if (selectedChannels.has('uriTV')) {
|
||||
totalCost += 80000;
|
||||
totalExposure += 50000;
|
||||
}
|
||||
|
||||
if (selectedChannels.has('ringoBiz')) {
|
||||
totalCost += 0;
|
||||
totalExposure += 30000;
|
||||
}
|
||||
|
||||
if (selectedChannels.has('genieTV')) {
|
||||
const budget = parseInt(document.getElementById('genieBudget').value) || 0;
|
||||
totalCost += budget;
|
||||
totalExposure += Math.floor(budget / 100) * 1000;
|
||||
}
|
||||
|
||||
if (selectedChannels.has('sns')) {
|
||||
totalCost += 0;
|
||||
// SNS는 팔로워 수에 따라 다름
|
||||
totalExposure += 0;
|
||||
}
|
||||
|
||||
// 표시 업데이트
|
||||
document.getElementById('totalCost').textContent = KTEventApp.Utils.formatNumber(totalCost) + '원';
|
||||
document.getElementById('totalExposure').textContent = totalExposure > 0 ?
|
||||
KTEventApp.Utils.formatNumber(totalExposure) + '명+' : '0명';
|
||||
}
|
||||
|
||||
// 다음 버튼 활성화
|
||||
function updateNextButton() {
|
||||
document.getElementById('nextBtn').disabled = selectedChannels.size === 0;
|
||||
}
|
||||
|
||||
// 다음 단계 버튼
|
||||
document.getElementById('nextBtn').addEventListener('click', function() {
|
||||
if (selectedChannels.size === 0) {
|
||||
KTEventApp.Feedback.showToast('최소 1개 이상의 채널을 선택해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
// 채널 선택 정보 저장
|
||||
const channelData = {
|
||||
channels: Array.from(selectedChannels),
|
||||
uriTV: selectedChannels.has('uriTV') ? {
|
||||
radius: document.getElementById('uriRadius').value,
|
||||
time: document.getElementById('uriTime').value
|
||||
} : null,
|
||||
ringoBiz: selectedChannels.has('ringoBiz') ? {
|
||||
phone: document.getElementById('ringoPhone').value
|
||||
} : null,
|
||||
genieTV: selectedChannels.has('genieTV') ? {
|
||||
region: document.getElementById('genieRegion').value,
|
||||
time: document.getElementById('genieTime').value,
|
||||
budget: document.getElementById('genieBudget').value
|
||||
} : null,
|
||||
sns: selectedChannels.has('sns') ? {
|
||||
instagram: document.getElementById('snsInstagram').checked,
|
||||
naver: document.getElementById('snsNaver').checked,
|
||||
kakao: document.getElementById('snsKakao').checked,
|
||||
schedule: document.getElementById('snsSchedule').value,
|
||||
scheduledDateTime: scheduledDateTime
|
||||
} : null
|
||||
};
|
||||
|
||||
KTEventApp.Utils.saveToStorage('selected_channels', channelData);
|
||||
|
||||
// 최종 승인 화면으로 이동
|
||||
window.location.href = '12-최종승인.html';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,367 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>최종 승인 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 600px">
|
||||
<!-- Title Section -->
|
||||
<section class="mt-lg mb-lg text-center">
|
||||
<div class="mb-md">
|
||||
<span
|
||||
class="material-icons"
|
||||
style="font-size: 64px; color: var(--color-success)"
|
||||
>check_circle</span
|
||||
>
|
||||
</div>
|
||||
<h2 class="text-title-large mb-sm">이벤트를 확인해주세요</h2>
|
||||
<p class="text-body text-secondary">
|
||||
모든 정보를 검토한 후 배포하세요
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Event Summary Card -->
|
||||
<section class="mb-lg">
|
||||
<div class="card">
|
||||
<h3 class="text-headline mb-md" id="eventTitle">
|
||||
SNS 팔로우 이벤트
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="event-card-badge badge-scheduled">배포 대기</span>
|
||||
<span
|
||||
class="event-card-badge"
|
||||
style="
|
||||
background: rgba(0, 102, 255, 0.1);
|
||||
color: var(--color-ai-blue);
|
||||
"
|
||||
>
|
||||
AI 추천
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-md">
|
||||
<div class="grid grid-cols-2 gap-md text-body-small">
|
||||
<div>
|
||||
<p class="text-tertiary mb-xs">이벤트 기간</p>
|
||||
<p class="text-semibold" id="eventPeriod">
|
||||
2025.02.01 ~ 2025.02.28
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-tertiary mb-xs">목표 참여자</p>
|
||||
<p class="text-semibold" id="targetParticipants">180명</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-tertiary mb-xs">예상 비용</p>
|
||||
<p class="text-semibold" id="estimatedCost">250,000원</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-tertiary mb-xs">예상 ROI</p>
|
||||
<p class="text-semibold text-kt-red" id="estimatedROI">
|
||||
520%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event Details -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">이벤트 상세</h3>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">celebration</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">이벤트 제목</p>
|
||||
<p class="text-body" id="detailTitle">SNS 팔로우 이벤트</p>
|
||||
</div>
|
||||
<button
|
||||
class="header-icon-btn"
|
||||
onclick="window.location.href='10-콘텐츠편집.html'"
|
||||
>
|
||||
<span class="material-icons">edit</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">card_giftcard</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">경품</p>
|
||||
<p class="text-body" id="detailPrize">커피 쿠폰</p>
|
||||
</div>
|
||||
<button
|
||||
class="header-icon-btn"
|
||||
onclick="window.location.href='10-콘텐츠편집.html'"
|
||||
>
|
||||
<span class="material-icons">edit</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">description</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">이벤트 설명</p>
|
||||
<p class="text-body" id="detailDescription">
|
||||
SNS를 팔로우하고 커피 쿠폰을 받으세요!<br />
|
||||
많은 참여 부탁드립니다.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="header-icon-btn"
|
||||
onclick="window.location.href='10-콘텐츠편집.html'"
|
||||
>
|
||||
<span class="material-icons">edit</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">how_to_reg</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">참여 방법</p>
|
||||
<p class="text-body" id="detailMethod">SNS 팔로우</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Distribution Channels -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">배포 채널</h3>
|
||||
<div class="card">
|
||||
<div id="channels" class="flex flex-wrap gap-sm">
|
||||
<span class="event-card-badge badge-active">
|
||||
<span
|
||||
class="material-icons"
|
||||
style="font-size: 16px; vertical-align: middle"
|
||||
>language</span
|
||||
>
|
||||
홈페이지
|
||||
</span>
|
||||
<span class="event-card-badge badge-active">
|
||||
<span
|
||||
class="material-icons"
|
||||
style="font-size: 16px; vertical-align: middle"
|
||||
>chat_bubble</span
|
||||
>
|
||||
카카오톡
|
||||
</span>
|
||||
<span class="event-card-badge badge-active">
|
||||
<span
|
||||
class="material-icons"
|
||||
style="font-size: 16px; vertical-align: middle"
|
||||
>share</span
|
||||
>
|
||||
Instagram
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-text btn-small mt-md"
|
||||
onclick="window.location.href='11-배포채널선택.html'"
|
||||
>
|
||||
<span class="material-icons">edit</span>
|
||||
채널 수정하기
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Terms Agreement -->
|
||||
<section class="mb-xl">
|
||||
<div class="card" style="background: var(--color-gray-50)">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="agreeTerms"
|
||||
class="form-check-input"
|
||||
required
|
||||
/>
|
||||
<label for="agreeTerms" class="form-check-label">
|
||||
이벤트 약관 및 개인정보 처리방침에 동의합니다
|
||||
<span class="text-kt-red">(필수)</span>
|
||||
</label>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="text-body-small text-kt-red mt-sm inline-block"
|
||||
id="viewTerms"
|
||||
>약관 보기</a
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-2xl">
|
||||
<button
|
||||
id="approveBtn"
|
||||
class="btn btn-primary btn-large btn-full mb-sm"
|
||||
disabled
|
||||
>
|
||||
<span class="material-icons">rocket_launch</span>
|
||||
배포하기
|
||||
</button>
|
||||
<button
|
||||
id="saveBtn"
|
||||
class="btn btn-secondary btn-large btn-full mb-sm"
|
||||
>
|
||||
<span class="material-icons">save</span>
|
||||
임시저장
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: "최종 승인",
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false,
|
||||
});
|
||||
document.getElementById("header").appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav("events");
|
||||
document.getElementById("bottomNav").appendChild(bottomNav);
|
||||
|
||||
// 저장된 데이터 불러오기
|
||||
const recommendation = KTEventApp.Utils.getFromStorage(
|
||||
"selected_recommendation"
|
||||
) || {
|
||||
title: "SNS 팔로우 이벤트",
|
||||
prize: "커피 쿠폰",
|
||||
};
|
||||
|
||||
// 데이터 표시
|
||||
document.getElementById("eventTitle").textContent = recommendation.title;
|
||||
document.getElementById("detailTitle").textContent = recommendation.title;
|
||||
document.getElementById("detailPrize").textContent = recommendation.prize;
|
||||
|
||||
// 약관 동의 체크박스
|
||||
document
|
||||
.getElementById("agreeTerms")
|
||||
.addEventListener("change", function (e) {
|
||||
document.getElementById("approveBtn").disabled = !e.target.checked;
|
||||
});
|
||||
|
||||
// 약관 보기
|
||||
document
|
||||
.getElementById("viewTerms")
|
||||
.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: "이벤트 약관",
|
||||
content: `
|
||||
<div class="p-md" style="max-height: 400px; overflow-y: auto;">
|
||||
<h4 class="text-headline mb-md">제1조 (목적)</h4>
|
||||
<p class="text-body-small mb-lg">
|
||||
본 약관은 KT AI 이벤트 마케팅 서비스를 통해 진행되는 이벤트의 참여 및 개인정보 처리에 관한 사항을 규정합니다.
|
||||
</p>
|
||||
<h4 class="text-headline mb-md">제2조 (개인정보 수집 및 이용)</h4>
|
||||
<p class="text-body-small mb-lg">
|
||||
수집 항목: 이름, 전화번호, 이메일<br>
|
||||
이용 목적: 이벤트 참여 확인 및 경품 제공<br>
|
||||
보유 기간: 이벤트 종료 후 6개월
|
||||
</p>
|
||||
<h4 class="text-headline mb-md">제3조 (당첨자 발표)</h4>
|
||||
<p class="text-body-small">
|
||||
당첨자는 이벤트 종료 후 7일 이내 개별 연락 드립니다.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: "확인",
|
||||
variant: "primary",
|
||||
onClick: function () {
|
||||
this.closest(".modal-backdrop").remove();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// 배포하기 버튼
|
||||
document
|
||||
.getElementById("approveBtn")
|
||||
.addEventListener("click", function () {
|
||||
const button = this;
|
||||
button.disabled = true;
|
||||
button.innerHTML =
|
||||
'<span class="material-icons">hourglass_empty</span> 배포 중...';
|
||||
|
||||
// 배포 시뮬레이션
|
||||
setTimeout(() => {
|
||||
// 성공 모달 표시
|
||||
const successModal = KTEventApp.Feedback.showModal({
|
||||
content: `
|
||||
<div class="p-xl text-center">
|
||||
<span class="material-icons" style="font-size: 80px; color: var(--color-success);">check_circle</span>
|
||||
<h3 class="text-title-large mt-md mb-sm">배포 완료!</h3>
|
||||
<p class="text-body text-secondary mb-lg">
|
||||
이벤트가 성공적으로 배포되었습니다.<br>
|
||||
실시간으로 참여자를 확인할 수 있습니다.
|
||||
</p>
|
||||
<div class="flex flex-col gap-sm">
|
||||
<button class="btn btn-primary btn-large btn-full" onclick="window.location.href='13-이벤트상세.html?id=new'">
|
||||
이벤트 상세 보기
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large btn-full" onclick="window.location.href='05-대시보드.html'">
|
||||
대시보드로 이동
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// 임시저장 버튼
|
||||
document.getElementById("saveBtn").addEventListener("click", function () {
|
||||
// 현재 데이터 저장
|
||||
const draftData = {
|
||||
...recommendation,
|
||||
savedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
KTEventApp.Utils.saveToStorage("event_draft", draftData);
|
||||
KTEventApp.Feedback.showToast("임시저장되었습니다");
|
||||
});
|
||||
|
||||
// 이전으로 버튼
|
||||
document.getElementById("backBtn").addEventListener("click", function () {
|
||||
window.history.back();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,437 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 상세 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Event Header -->
|
||||
<section class="mt-lg mb-lg">
|
||||
<div class="flex items-center justify-between mb-md">
|
||||
<h2 class="text-title-large" id="eventTitle">SNS 팔로우 이벤트</h2>
|
||||
<button class="header-icon-btn" id="moreBtn">
|
||||
<span class="material-icons">more_vert</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="event-card-badge badge-active" id="statusBadge">진행중</span>
|
||||
<span class="event-card-badge" style="background: rgba(0, 102, 255, 0.1); color: var(--color-ai-blue);">
|
||||
AI 추천
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-body text-secondary" id="eventPeriod">
|
||||
2025.01.15 ~ 2025.02.15
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Real-time KPIs -->
|
||||
<section class="mb-xl">
|
||||
<div class="flex items-center justify-between mb-md">
|
||||
<h3 class="text-headline">실시간 현황</h3>
|
||||
<div class="flex items-center gap-xs text-body-small text-success">
|
||||
<span class="material-icons" style="font-size: 16px;">fiber_manual_record</span>
|
||||
<span>실시간 업데이트</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-sm tablet:grid-cols-4">
|
||||
<div id="kpiParticipants"></div>
|
||||
<div id="kpiViews"></div>
|
||||
<div id="kpiROI"></div>
|
||||
<div id="kpiConversion"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="text-headline mb-md">참여 추이</h3>
|
||||
<div class="card">
|
||||
<div class="flex justify-between items-center mb-md">
|
||||
<div class="flex gap-xs">
|
||||
<button class="btn btn-small active" data-period="7d">7일</button>
|
||||
<button class="btn btn-small btn-text" data-period="30d">30일</button>
|
||||
<button class="btn btn-small btn-text" data-period="all">전체</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simple Chart Placeholder -->
|
||||
<div id="chartPlaceholder" style="height: 200px; background: var(--color-gray-50); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
|
||||
<div class="text-center">
|
||||
<span class="material-icons text-tertiary" style="font-size: 48px;">show_chart</span>
|
||||
<p class="text-body-small text-tertiary mt-sm">참여자 추이 차트</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event Details -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="text-headline mb-md">이벤트 정보</h3>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">card_giftcard</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">경품</p>
|
||||
<p class="text-body" id="detailPrize">커피 쿠폰</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">how_to_reg</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">참여 방법</p>
|
||||
<p class="text-body" id="detailMethod">SNS 팔로우</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-sm">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">attach_money</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">예상 비용</p>
|
||||
<p class="text-body" id="detailCost">250,000원</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex items-start gap-md">
|
||||
<span class="material-icons text-kt-red">share</span>
|
||||
<div class="flex-1">
|
||||
<p class="text-body-small text-tertiary mb-xs">배포 채널</p>
|
||||
<div class="flex flex-wrap gap-xs mt-sm">
|
||||
<span class="event-card-badge badge-active">홈페이지</span>
|
||||
<span class="event-card-badge badge-active">카카오톡</span>
|
||||
<span class="event-card-badge badge-active">Instagram</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="text-headline mb-md">빠른 작업</h3>
|
||||
<div class="grid grid-cols-2 gap-sm tablet:grid-cols-4">
|
||||
<button class="card card-clickable p-md" id="viewParticipants">
|
||||
<span class="material-icons text-kt-red" style="font-size: 32px;">people</span>
|
||||
<p class="text-body-small mt-sm">참여자 목록</p>
|
||||
</button>
|
||||
<button class="card card-clickable p-md" id="editEvent">
|
||||
<span class="material-icons text-ai-blue" style="font-size: 32px;">edit</span>
|
||||
<p class="text-body-small mt-sm">이벤트 수정</p>
|
||||
</button>
|
||||
<button class="card card-clickable p-md" id="shareEvent">
|
||||
<span class="material-icons text-secondary" style="font-size: 32px;">share</span>
|
||||
<p class="text-body-small mt-sm">공유하기</p>
|
||||
</button>
|
||||
<button class="card card-clickable p-md" id="exportData">
|
||||
<span class="material-icons text-secondary" style="font-size: 32px;">download</span>
|
||||
<p class="text-body-small mt-sm">데이터 다운</p>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recent Participants -->
|
||||
<section class="mb-2xl">
|
||||
<div class="flex items-center justify-between mb-md">
|
||||
<h3 class="text-headline">최근 참여자</h3>
|
||||
<a href="14-참여자목록.html" class="text-body-small text-kt-red">
|
||||
전체보기
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">chevron_right</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div id="recentParticipants">
|
||||
<!-- 참여자 목록 -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '이벤트 상세',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: false
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// URL에서 이벤트 ID 가져오기
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const eventId = urlParams.get('id');
|
||||
|
||||
// 이벤트 데이터 가져오기
|
||||
const events = KTEventApp.MockData.getEvents();
|
||||
const event = events.find(e => e.id === eventId) || events[0];
|
||||
|
||||
// 이벤트 정보 표시
|
||||
document.getElementById('eventTitle').textContent = event.title;
|
||||
document.getElementById('eventPeriod').textContent =
|
||||
`${KTEventApp.Utils.formatDate(event.startDate)} ~ ${KTEventApp.Utils.formatDate(event.endDate)}`;
|
||||
document.getElementById('detailPrize').textContent = event.prize;
|
||||
document.getElementById('detailMethod').textContent = `${event.channel} - SNS 팔로우`;
|
||||
document.getElementById('detailCost').textContent = event.budget === '저비용' ? '250,000원' : '1,500,000원';
|
||||
|
||||
// 상태 배지
|
||||
const statusBadge = document.getElementById('statusBadge');
|
||||
statusBadge.textContent = event.status;
|
||||
statusBadge.className = 'event-card-badge';
|
||||
if (event.status === '진행중') {
|
||||
statusBadge.classList.add('badge-active');
|
||||
} else if (event.status === '예정') {
|
||||
statusBadge.classList.add('badge-scheduled');
|
||||
} else {
|
||||
statusBadge.classList.add('badge-ended');
|
||||
}
|
||||
|
||||
// KPI 카드 생성
|
||||
const kpiParticipants = KTEventApp.Cards.createKPICard({
|
||||
icon: 'group',
|
||||
iconType: 'primary',
|
||||
label: '참여자',
|
||||
value: `${KTEventApp.Utils.formatNumber(event.participants)}명`
|
||||
});
|
||||
document.getElementById('kpiParticipants').appendChild(kpiParticipants);
|
||||
|
||||
const kpiViews = KTEventApp.Cards.createKPICard({
|
||||
icon: 'visibility',
|
||||
iconType: 'ai',
|
||||
label: '조회수',
|
||||
value: `${KTEventApp.Utils.formatNumber(event.views)}`
|
||||
});
|
||||
document.getElementById('kpiViews').appendChild(kpiViews);
|
||||
|
||||
const kpiROI = KTEventApp.Cards.createKPICard({
|
||||
icon: 'trending_up',
|
||||
iconType: 'success',
|
||||
label: 'ROI',
|
||||
value: `${event.roi}%`
|
||||
});
|
||||
document.getElementById('kpiROI').appendChild(kpiROI);
|
||||
|
||||
const conversion = event.views > 0 ? Math.round((event.participants / event.views) * 100) : 0;
|
||||
const kpiConversion = KTEventApp.Cards.createKPICard({
|
||||
icon: 'conversion_path',
|
||||
iconType: 'primary',
|
||||
label: '전환율',
|
||||
value: `${conversion}%`
|
||||
});
|
||||
document.getElementById('kpiConversion').appendChild(kpiConversion);
|
||||
|
||||
// 최근 참여자 표시
|
||||
const recentParticipantsContainer = document.getElementById('recentParticipants');
|
||||
const participants = [
|
||||
{ name: '김*진', phone: '010-****-1234', time: '5분 전' },
|
||||
{ name: '이*수', phone: '010-****-5678', time: '12분 전' },
|
||||
{ name: '박*영', phone: '010-****-9012', time: '25분 전' },
|
||||
{ name: '최*민', phone: '010-****-3456', time: '1시간 전' },
|
||||
{ name: '정*희', phone: '010-****-7890', time: '2시간 전' }
|
||||
];
|
||||
|
||||
participants.forEach((participant, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'flex items-center justify-between';
|
||||
if (index > 0) item.classList.add('mt-md', 'pt-md', 'border-t');
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="flex items-center gap-md">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; background: var(--color-gray-100); border-radius: var(--radius-full);">
|
||||
<span class="material-icons text-tertiary">person</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-body text-semibold">${participant.name}</p>
|
||||
<p class="text-caption text-tertiary">${participant.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-caption text-tertiary">${participant.time}</p>
|
||||
`;
|
||||
recentParticipantsContainer.appendChild(item);
|
||||
});
|
||||
|
||||
// 더보기 메뉴
|
||||
document.getElementById('moreBtn').addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showBottomSheet(`
|
||||
<div class="flex flex-col gap-sm">
|
||||
<button class="btn btn-text btn-large btn-full" onclick="editEvent()">
|
||||
<span class="material-icons">edit</span>
|
||||
이벤트 수정
|
||||
</button>
|
||||
<button class="btn btn-text btn-large btn-full" onclick="shareEvent()">
|
||||
<span class="material-icons">share</span>
|
||||
공유하기
|
||||
</button>
|
||||
<button class="btn btn-text btn-large btn-full" onclick="exportData()">
|
||||
<span class="material-icons">download</span>
|
||||
데이터 다운로드
|
||||
</button>
|
||||
<button class="btn btn-text btn-large btn-full text-error" onclick="deleteEvent()">
|
||||
<span class="material-icons">delete</span>
|
||||
이벤트 삭제
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
// 빠른 작업 버튼들
|
||||
document.getElementById('viewParticipants').addEventListener('click', () => {
|
||||
window.location.href = '14-참여자목록.html?eventId=' + event.id;
|
||||
});
|
||||
|
||||
document.getElementById('editEvent').addEventListener('click', editEvent);
|
||||
document.getElementById('shareEvent').addEventListener('click', shareEvent);
|
||||
document.getElementById('exportData').addEventListener('click', exportData);
|
||||
|
||||
// 차트 기간 버튼
|
||||
document.querySelectorAll('[data-period]').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('[data-period]').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.classList.add('btn-text');
|
||||
});
|
||||
this.classList.add('active');
|
||||
this.classList.remove('btn-text');
|
||||
|
||||
KTEventApp.Feedback.showToast('차트가 업데이트되었습니다');
|
||||
});
|
||||
});
|
||||
|
||||
// 이벤트 수정
|
||||
function editEvent() {
|
||||
KTEventApp.Feedback.showToast('이벤트 수정 기능은 준비 중입니다.');
|
||||
}
|
||||
|
||||
// 공유하기
|
||||
function shareEvent() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '이벤트 공유',
|
||||
content: `
|
||||
<p class="text-body mb-md">이벤트 링크를 공유하세요</p>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-input" value="https://kt-event.co.kr/evt/${event.id}" readonly>
|
||||
</div>
|
||||
<div class="flex gap-sm mt-lg">
|
||||
<button class="btn btn-secondary btn-large flex-1" onclick="KTEventApp.Feedback.showToast('카카오톡 공유 준비 중')">
|
||||
카카오톡
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large flex-1" onclick="copyLink()">
|
||||
링크 복사
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
// 링크 복사
|
||||
function copyLink() {
|
||||
KTEventApp.Feedback.showToast('링크가 복사되었습니다');
|
||||
}
|
||||
|
||||
// 데이터 다운로드
|
||||
function exportData() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '데이터 다운로드',
|
||||
content: `
|
||||
<p class="text-body mb-md">다운로드할 데이터 형식을 선택하세요</p>
|
||||
<div class="flex flex-col gap-sm">
|
||||
<button class="btn btn-secondary btn-large btn-full" onclick="download('excel')">
|
||||
<span class="material-icons">table_chart</span>
|
||||
Excel (XLSX)
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large btn-full" onclick="download('csv')">
|
||||
<span class="material-icons">description</span>
|
||||
CSV
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-large btn-full" onclick="download('pdf')">
|
||||
<span class="material-icons">picture_as_pdf</span>
|
||||
PDF 리포트
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
function download(format) {
|
||||
KTEventApp.Feedback.showToast(`${format.toUpperCase()} 파일 다운로드 준비 중...`);
|
||||
}
|
||||
|
||||
// 이벤트 삭제
|
||||
function deleteEvent() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '이벤트 삭제',
|
||||
content: '<p class="text-body">정말로 이벤트를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '삭제',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
KTEventApp.Feedback.showToast('이벤트가 삭제되었습니다');
|
||||
setTimeout(() => {
|
||||
window.location.href = '05-대시보드.html';
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 실시간 업데이트 시뮬레이션 (5초마다)
|
||||
if (event.status === '진행중') {
|
||||
setInterval(() => {
|
||||
// 참여자 수 업데이트 (랜덤 증가)
|
||||
const increase = Math.floor(Math.random() * 3);
|
||||
if (increase > 0) {
|
||||
event.participants += increase;
|
||||
document.querySelector('#kpiParticipants .kpi-value').textContent =
|
||||
`${KTEventApp.Utils.formatNumber(event.participants)}명`;
|
||||
|
||||
// 작은 애니메이션 효과
|
||||
const kpiCard = document.getElementById('kpiParticipants').firstChild;
|
||||
kpiCard.style.transform = 'scale(1.05)';
|
||||
setTimeout(() => {
|
||||
kpiCard.style.transform = 'scale(1)';
|
||||
}, 200);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,400 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>참여자 목록 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 1200px;">
|
||||
<!-- Search Section -->
|
||||
<section class="mt-lg mb-md">
|
||||
<div class="form-group">
|
||||
<div class="input-with-icon">
|
||||
<span class="material-icons input-icon">search</span>
|
||||
<input type="text" id="searchInput" class="form-input" placeholder="이름 또는 전화번호 검색...">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Filters -->
|
||||
<section class="mb-md">
|
||||
<div class="flex items-center gap-sm flex-wrap">
|
||||
<span class="material-icons text-kt-red">filter_list</span>
|
||||
<select id="channelFilter" class="form-select" style="flex: 1; min-width: 140px;">
|
||||
<option value="all">전체 경로</option>
|
||||
<option value="uriTV">우리동네TV</option>
|
||||
<option value="ringoBiz">링고비즈</option>
|
||||
<option value="sns">SNS</option>
|
||||
</select>
|
||||
<select id="statusFilter" class="form-select" style="flex: 1; min-width: 120px;">
|
||||
<option value="all">전체</option>
|
||||
<option value="waiting">당첨 대기</option>
|
||||
<option value="winner">당첨</option>
|
||||
<option value="loser">미당첨</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Total Count & Drawing Button -->
|
||||
<section class="mb-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-headline">총 <span id="totalCount">128</span>명 참여</p>
|
||||
<button id="drawingBtn" class="btn btn-primary btn-medium">
|
||||
<span class="material-icons">casino</span>
|
||||
당첨자 추첨
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Participant List -->
|
||||
<section id="participantList" class="mb-lg">
|
||||
<!-- Participants will be dynamically loaded here -->
|
||||
</section>
|
||||
|
||||
<!-- Pagination -->
|
||||
<section class="mb-md">
|
||||
<div class="flex items-center justify-center gap-sm" id="pagination">
|
||||
<!-- Pagination will be dynamically loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Excel Download Button (Desktop only) -->
|
||||
<section class="mb-2xl desktop-only">
|
||||
<button id="downloadBtn" class="btn btn-secondary btn-large">
|
||||
<span class="material-icons">download</span>
|
||||
엑셀 다운로드
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '참여자 목록',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: true
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// Mock 참여자 데이터
|
||||
const participants = [
|
||||
{
|
||||
id: '0001',
|
||||
name: '김**',
|
||||
phone: '010-****-1234',
|
||||
channel: 'SNS (Instagram)',
|
||||
channelType: 'sns',
|
||||
date: '2025-11-02 14:23',
|
||||
status: 'waiting'
|
||||
},
|
||||
{
|
||||
id: '0002',
|
||||
name: '이**',
|
||||
phone: '010-****-5678',
|
||||
channel: '우리동네TV',
|
||||
channelType: 'uriTV',
|
||||
date: '2025-11-02 15:45',
|
||||
status: 'waiting'
|
||||
},
|
||||
{
|
||||
id: '0003',
|
||||
name: '박**',
|
||||
phone: '010-****-9012',
|
||||
channel: '링고비즈',
|
||||
channelType: 'ringoBiz',
|
||||
date: '2025-11-02 16:12',
|
||||
status: 'waiting'
|
||||
},
|
||||
{
|
||||
id: '0004',
|
||||
name: '최**',
|
||||
phone: '010-****-3456',
|
||||
channel: 'SNS (Naver)',
|
||||
channelType: 'sns',
|
||||
date: '2025-11-02 17:30',
|
||||
status: 'waiting'
|
||||
},
|
||||
{
|
||||
id: '0005',
|
||||
name: '정**',
|
||||
phone: '010-****-7890',
|
||||
channel: '우리동네TV',
|
||||
channelType: 'uriTV',
|
||||
date: '2025-11-02 18:15',
|
||||
status: 'waiting'
|
||||
}
|
||||
];
|
||||
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 20;
|
||||
let filteredParticipants = [...participants];
|
||||
|
||||
// 참여자 카드 생성
|
||||
function createParticipantCard(participant) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.style.cursor = 'pointer';
|
||||
|
||||
const statusText = participant.status === 'waiting' ? '당첨 대기' :
|
||||
participant.status === 'winner' ? '당첨' : '미당첨';
|
||||
|
||||
const statusColor = participant.status === 'waiting' ? 'var(--color-gray-600)' :
|
||||
participant.status === 'winner' ? 'var(--color-success)' : 'var(--color-error)';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex items-start justify-between mb-sm">
|
||||
<div class="flex-1">
|
||||
<p class="text-caption text-secondary mb-xs">#${participant.id}</p>
|
||||
<h3 class="text-headline mb-xs">${participant.name}</h3>
|
||||
<p class="text-body-small text-secondary">${participant.phone}</p>
|
||||
</div>
|
||||
<span class="event-card-badge" style="background: ${statusColor}; color: white;">
|
||||
${statusText}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-sm mt-sm">
|
||||
<div class="flex items-center justify-between text-body-small">
|
||||
<span class="text-secondary">참여 경로</span>
|
||||
<span class="text-semibold">${participant.channel}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-body-small mt-xs">
|
||||
<span class="text-secondary">참여 일시</span>
|
||||
<span>${participant.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.addEventListener('click', () => {
|
||||
showParticipantDetail(participant);
|
||||
});
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 참여자 목록 렌더링
|
||||
function renderParticipantList() {
|
||||
const participantList = document.getElementById('participantList');
|
||||
participantList.innerHTML = '';
|
||||
|
||||
if (filteredParticipants.length === 0) {
|
||||
participantList.innerHTML = `
|
||||
<div class="text-center py-2xl">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-300);">people_outline</span>
|
||||
<p class="text-body text-secondary mt-md">검색 결과가 없습니다</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, filteredParticipants.length);
|
||||
const pageParticipants = filteredParticipants.slice(startIndex, endIndex);
|
||||
|
||||
pageParticipants.forEach(participant => {
|
||||
participantList.appendChild(createParticipantCard(participant));
|
||||
});
|
||||
|
||||
renderPagination();
|
||||
}
|
||||
|
||||
// 페이지네이션 렌더링
|
||||
function renderPagination() {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
const totalPages = Math.ceil(filteredParticipants.length / itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) return;
|
||||
|
||||
// 이전 버튼
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'btn btn-text btn-small';
|
||||
prevBtn.innerHTML = '<span class="material-icons">chevron_left</span>';
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderParticipantList();
|
||||
}
|
||||
});
|
||||
pagination.appendChild(prevBtn);
|
||||
|
||||
// 페이지 번호
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
if (i === 1 || i === totalPages || (i >= currentPage - 1 && i <= currentPage + 1)) {
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = `btn ${i === currentPage ? 'btn-primary' : 'btn-text'} btn-small`;
|
||||
pageBtn.textContent = i;
|
||||
pageBtn.addEventListener('click', () => {
|
||||
currentPage = i;
|
||||
renderParticipantList();
|
||||
});
|
||||
pagination.appendChild(pageBtn);
|
||||
} else if (i === currentPage - 2 || i === currentPage + 2) {
|
||||
const ellipsis = document.createElement('span');
|
||||
ellipsis.textContent = '...';
|
||||
ellipsis.className = 'text-secondary px-sm';
|
||||
pagination.appendChild(ellipsis);
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 버튼
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'btn btn-text btn-small';
|
||||
nextBtn.innerHTML = '<span class="material-icons">chevron_right</span>';
|
||||
nextBtn.disabled = currentPage === totalPages;
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderParticipantList();
|
||||
}
|
||||
});
|
||||
pagination.appendChild(nextBtn);
|
||||
}
|
||||
|
||||
// 필터 적용
|
||||
function applyFilters() {
|
||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||
const channelFilter = document.getElementById('channelFilter').value;
|
||||
const statusFilter = document.getElementById('statusFilter').value;
|
||||
|
||||
filteredParticipants = participants.filter(participant => {
|
||||
const matchesSearch = participant.name.includes(searchTerm) ||
|
||||
participant.phone.includes(searchTerm);
|
||||
const matchesChannel = channelFilter === 'all' || participant.channelType === channelFilter;
|
||||
const matchesStatus = statusFilter === 'all' || participant.status === statusFilter;
|
||||
|
||||
return matchesSearch && matchesChannel && matchesStatus;
|
||||
});
|
||||
|
||||
document.getElementById('totalCount').textContent = filteredParticipants.length;
|
||||
currentPage = 1;
|
||||
renderParticipantList();
|
||||
}
|
||||
|
||||
// 참여자 상세 정보 모달
|
||||
function showParticipantDetail(participant) {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '참여자 상세 정보',
|
||||
content: `
|
||||
<div class="p-md">
|
||||
<div class="mb-md">
|
||||
<p class="text-caption text-secondary mb-xs">응모번호</p>
|
||||
<p class="text-headline">#${participant.id}</p>
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<p class="text-caption text-secondary mb-xs">이름</p>
|
||||
<p class="text-body">${participant.name}</p>
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<p class="text-caption text-secondary mb-xs">전화번호</p>
|
||||
<p class="text-body">${participant.phone}</p>
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<p class="text-caption text-secondary mb-xs">참여 경로</p>
|
||||
<p class="text-body">${participant.channel}</p>
|
||||
</div>
|
||||
<div class="mb-md">
|
||||
<p class="text-caption text-secondary mb-xs">참여 일시</p>
|
||||
<p class="text-body">${participant.date}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-caption text-secondary mb-xs">당첨 여부</p>
|
||||
<p class="text-body">${participant.status === 'waiting' ? '당첨 대기' : participant.status === 'winner' ? '당첨' : '미당첨'}</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 이벤트 리스너
|
||||
document.getElementById('searchInput').addEventListener('input', KTEventApp.Utils.debounce(applyFilters, 300));
|
||||
document.getElementById('channelFilter').addEventListener('change', applyFilters);
|
||||
document.getElementById('statusFilter').addEventListener('change', applyFilters);
|
||||
|
||||
// 엑셀 다운로드 버튼
|
||||
document.getElementById('downloadBtn')?.addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showToast('참여자 목록을 다운로드합니다');
|
||||
// 실제로는 엑셀 다운로드 로직 구현
|
||||
});
|
||||
|
||||
// 당첨자 추첨 버튼
|
||||
document.getElementById('drawingBtn').addEventListener('click', function() {
|
||||
// 이벤트가 종료되었는지 확인 (실제로는 이벤트 상태 체크)
|
||||
const eventEnded = true; // Mock data
|
||||
|
||||
if (!eventEnded) {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '추첨 불가',
|
||||
content: '<p class="text-body text-center">이벤트가 종료된 후 추첨이 가능합니다.</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 참여자가 있는지 확인
|
||||
if (filteredParticipants.length === 0) {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '추첨 불가',
|
||||
content: '<p class="text-body text-center">참여자가 없어 추첨할 수 없습니다.</p>',
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 당첨자 추첨 화면으로 이동
|
||||
window.location.href = '16-당첨자추첨.html';
|
||||
});
|
||||
|
||||
// 초기 렌더링
|
||||
renderParticipantList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,309 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 참여 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
body {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
.participation-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
.event-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
.success-animation {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
z-index: 9999;
|
||||
}
|
||||
.success-animation.active {
|
||||
display: flex;
|
||||
}
|
||||
.checkmark {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-success);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
animation: scaleIn 0.5s ease-out;
|
||||
}
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="participation-container">
|
||||
<!-- Event Image -->
|
||||
<div class="event-image">
|
||||
<span class="material-icons" style="font-size: 64px; margin-bottom: 16px;">celebration</span>
|
||||
<h2 class="text-title-large" id="eventTitle">신규고객 유치 이벤트</h2>
|
||||
</div>
|
||||
|
||||
<!-- Event Info -->
|
||||
<section class="mb-lg">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">card_giftcard</span>
|
||||
<div>
|
||||
<p class="text-caption text-secondary">경품</p>
|
||||
<p class="text-headline" id="eventPrize">커피 쿠폰</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">calendar_today</span>
|
||||
<div>
|
||||
<p class="text-caption text-secondary">기간</p>
|
||||
<p class="text-body" id="eventPeriod">2025-11-01 ~ 2025-11-15</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t mb-lg"></div>
|
||||
|
||||
<!-- Participation Form -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-title mb-md">참여하기</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="participantName" class="form-label">이름 <span class="text-kt-red">*</span></label>
|
||||
<input type="text" id="participantName" class="form-input" placeholder="이름을 입력하세요" required>
|
||||
<span class="form-error" id="nameError"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="participantPhone" class="form-label">전화번호 <span class="text-kt-red">*</span></label>
|
||||
<input type="tel" id="participantPhone" class="form-input" placeholder="010-0000-0000" required>
|
||||
<span class="form-error" id="phoneError"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="agreePrivacy" class="form-check-input" required>
|
||||
<label for="agreePrivacy" class="form-check-label">
|
||||
개인정보 수집 및 이용에 동의합니다 <span class="text-kt-red">(필수)</span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-text btn-small mt-xs" id="viewPrivacy">
|
||||
전문보기
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<section class="mb-lg">
|
||||
<button id="submitBtn" class="btn btn-primary btn-large btn-full">
|
||||
참여하기
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Participant Count -->
|
||||
<section class="text-center">
|
||||
<p class="text-body-small text-secondary">
|
||||
참여자: <strong id="participantCount">128</strong>명
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Success Animation -->
|
||||
<div class="success-animation" id="successAnimation">
|
||||
<div class="checkmark">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">check</span>
|
||||
</div>
|
||||
<h2 class="text-title-large mb-sm">참여가 완료되었습니다!</h2>
|
||||
<p class="text-body text-secondary mb-lg">
|
||||
<span id="participantNameDisplay">홍길동</span>님의 행운을 기원합니다!
|
||||
</p>
|
||||
|
||||
<div class="border-t pt-lg mb-lg" style="width: 80%; max-width: 400px;">
|
||||
<p class="text-body-small text-secondary text-center mb-xs">당첨자 발표</p>
|
||||
<p class="text-headline text-center" id="announceDate">2025-11-16 (월)</p>
|
||||
</div>
|
||||
|
||||
<button id="confirmBtn" class="btn btn-primary btn-large">확인</button>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 이벤트 정보 표시
|
||||
const eventData = {
|
||||
title: '신규고객 유치 이벤트',
|
||||
prize: '커피 쿠폰',
|
||||
startDate: '2025-11-01',
|
||||
endDate: '2025-11-15',
|
||||
announceDate: '2025-11-16 (월)',
|
||||
participantCount: 128
|
||||
};
|
||||
|
||||
document.getElementById('eventTitle').textContent = eventData.title;
|
||||
document.getElementById('eventPrize').textContent = eventData.prize;
|
||||
document.getElementById('eventPeriod').textContent = `${eventData.startDate} ~ ${eventData.endDate}`;
|
||||
document.getElementById('participantCount').textContent = eventData.participantCount;
|
||||
document.getElementById('announceDate').textContent = eventData.announceDate;
|
||||
|
||||
// 전화번호 자동 포맷팅
|
||||
document.getElementById('participantPhone').addEventListener('input', function(e) {
|
||||
e.target.value = KTEventApp.Utils.formatPhoneNumber(e.target.value);
|
||||
});
|
||||
|
||||
// 개인정보 처리방침 보기
|
||||
document.getElementById('viewPrivacy').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '개인정보 처리방침',
|
||||
content: `
|
||||
<div class="p-md" style="max-height: 400px; overflow-y: auto;">
|
||||
<h4 class="text-headline mb-md">개인정보 수집 및 이용 안내</h4>
|
||||
<p class="text-body-small mb-lg">
|
||||
<strong>1. 수집 항목</strong><br>
|
||||
- 이름, 전화번호
|
||||
</p>
|
||||
<p class="text-body-small mb-lg">
|
||||
<strong>2. 이용 목적</strong><br>
|
||||
- 이벤트 참여 확인<br>
|
||||
- 당첨자 발표 및 경품 제공<br>
|
||||
- 이벤트 관련 안내
|
||||
</p>
|
||||
<p class="text-body-small mb-lg">
|
||||
<strong>3. 보유 기간</strong><br>
|
||||
- 이벤트 종료 후 6개월
|
||||
</p>
|
||||
<p class="text-body-small">
|
||||
<strong>4. 동의 거부 권리</strong><br>
|
||||
개인정보 수집 및 이용에 동의하지 않을 권리가 있으나, 동의하지 않을 경우 이벤트 참여가 제한됩니다.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// 폼 검증
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
|
||||
// 이름 검증
|
||||
const name = document.getElementById('participantName').value.trim();
|
||||
const nameError = document.getElementById('nameError');
|
||||
if (name.length < 2) {
|
||||
nameError.textContent = '이름은 2자 이상 입력해주세요';
|
||||
isValid = false;
|
||||
} else {
|
||||
nameError.textContent = '';
|
||||
}
|
||||
|
||||
// 전화번호 검증
|
||||
const phone = document.getElementById('participantPhone').value;
|
||||
const phoneError = document.getElementById('phoneError');
|
||||
const phonePattern = /^010-\d{4}-\d{4}$/;
|
||||
if (!phonePattern.test(phone)) {
|
||||
phoneError.textContent = '올바른 전화번호 형식이 아닙니다 (010-0000-0000)';
|
||||
isValid = false;
|
||||
} else {
|
||||
phoneError.textContent = '';
|
||||
}
|
||||
|
||||
// 개인정보 동의 검증
|
||||
const agreePrivacy = document.getElementById('agreePrivacy').checked;
|
||||
if (!agreePrivacy) {
|
||||
KTEventApp.Feedback.showToast('개인정보 수집 및 이용에 동의해주세요');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// 중복 참여 체크
|
||||
function checkDuplicateParticipation(phone) {
|
||||
// 실제로는 서버에서 체크
|
||||
const participatedPhones = KTEventApp.Utils.getFromStorage('participated_phones') || [];
|
||||
return participatedPhones.includes(phone);
|
||||
}
|
||||
|
||||
// 참여 처리
|
||||
document.getElementById('submitBtn').addEventListener('click', function() {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = document.getElementById('participantName').value.trim();
|
||||
const phone = document.getElementById('participantPhone').value;
|
||||
|
||||
// 중복 참여 체크
|
||||
if (checkDuplicateParticipation(phone)) {
|
||||
KTEventApp.Feedback.showToast('이미 참여하셨습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// 로딩 표시
|
||||
const button = this;
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<span class="spinner-small"></span> 참여 중...';
|
||||
|
||||
// 참여 처리 시뮬레이션
|
||||
setTimeout(() => {
|
||||
// 참여 정보 저장
|
||||
const participatedPhones = KTEventApp.Utils.getFromStorage('participated_phones') || [];
|
||||
participatedPhones.push(phone);
|
||||
KTEventApp.Utils.saveToStorage('participated_phones', participatedPhones);
|
||||
|
||||
// 성공 애니메이션 표시
|
||||
document.getElementById('participantNameDisplay').textContent = name;
|
||||
document.getElementById('successAnimation').classList.add('active');
|
||||
|
||||
// 버튼 복원
|
||||
button.disabled = false;
|
||||
button.innerHTML = '참여하기';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// 확인 버튼 (성공 화면 닫기)
|
||||
document.getElementById('confirmBtn').addEventListener('click', function() {
|
||||
// 실제로는 이벤트 상세 페이지나 메인으로 이동
|
||||
window.location.reload();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,536 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>당첨자 추첨 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
body {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
.drawing-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
.winner-count-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
.winner-count-btn {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 1px solid var(--color-gray-300);
|
||||
background: white;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.winner-count-btn:hover {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
.winner-count-display {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.drawing-animation {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
z-index: 9999;
|
||||
}
|
||||
.drawing-animation.active {
|
||||
display: flex;
|
||||
}
|
||||
.slot-machine {
|
||||
font-size: 64px;
|
||||
color: white;
|
||||
animation: spin 0.5s infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
50% { transform: rotate(180deg); }
|
||||
}
|
||||
.winner-card {
|
||||
position: relative;
|
||||
padding-left: 60px;
|
||||
}
|
||||
.rank-badge {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
.rank-1 { background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); color: white; }
|
||||
.rank-2 { background: linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%); color: white; }
|
||||
.rank-3 { background: linear-gradient(135deg, #CD7F32 0%, #B87333 100%); color: white; }
|
||||
.rank-other { background: var(--color-gray-200); color: var(--color-text-secondary); }
|
||||
.results-view {
|
||||
display: none;
|
||||
}
|
||||
.results-view.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="drawing-container">
|
||||
<!-- Setup View (Before Drawing) -->
|
||||
<div id="setupView">
|
||||
<!-- Event Info -->
|
||||
<section class="mb-lg">
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">event_note</span>
|
||||
<h3 class="text-headline">이벤트 정보</h3>
|
||||
</div>
|
||||
<div class="mb-sm">
|
||||
<p class="text-caption text-secondary mb-xs">이벤트명</p>
|
||||
<p class="text-body" id="eventName">신규고객 유치 이벤트</p>
|
||||
</div>
|
||||
<div class="mb-sm">
|
||||
<p class="text-caption text-secondary mb-xs">총 참여자</p>
|
||||
<p class="text-headline" id="totalParticipants">127명</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-caption text-secondary mb-xs">추첨 상태</p>
|
||||
<p class="text-body" id="drawingStatus">추첨 전</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Drawing Settings -->
|
||||
<section class="mb-lg">
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">tune</span>
|
||||
<h3 class="text-headline">추첨 설정</h3>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">당첨 인원</label>
|
||||
<div class="winner-count-control">
|
||||
<button class="winner-count-btn" id="decreaseBtn">-</button>
|
||||
<div class="winner-count-display" id="winnerCount">5</div>
|
||||
<button class="winner-count-btn" id="increaseBtn">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="storeBonus" class="form-check-input">
|
||||
<label for="storeBonus" class="form-check-label">
|
||||
매장 방문 고객 가산점 (가중치: 1.5배)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-md p-md" style="background: var(--color-gray-50); border-radius: var(--radius-sm);">
|
||||
<p class="text-body-small text-secondary mb-xs">
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">info</span>
|
||||
추첨 방식
|
||||
</p>
|
||||
<p class="text-body-small">• 난수 기반 무작위 추첨</p>
|
||||
<p class="text-body-small">• 모든 추첨 과정은 자동 기록됩니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Drawing Start Button -->
|
||||
<section class="mb-lg">
|
||||
<button id="startDrawingBtn" class="btn btn-primary btn-large btn-full">
|
||||
<span class="material-icons">casino</span>
|
||||
추첨 시작
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Drawing History -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">📜 추첨 이력 (최근 3건)</h3>
|
||||
<div id="historyList">
|
||||
<!-- History items will be dynamically loaded -->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Results View (After Drawing) -->
|
||||
<div id="resultsView" class="results-view">
|
||||
<!-- Results Header -->
|
||||
<section class="mb-lg text-center">
|
||||
<h2 class="text-title-large mb-sm">🎉 추첨 완료!</h2>
|
||||
<p class="text-headline">총 <span id="totalCount">127</span>명 중 <span id="winnerCountDisplay">5</span>명 당첨</p>
|
||||
</section>
|
||||
|
||||
<!-- Winner List -->
|
||||
<section class="mb-lg">
|
||||
<h3 class="text-headline mb-md">🏆 당첨자 목록</h3>
|
||||
<div id="winnerList">
|
||||
<!-- Winners will be dynamically loaded -->
|
||||
</div>
|
||||
<div class="mt-md text-center">
|
||||
<p class="text-body-small text-secondary">🌟 매장 방문 고객 가산점 적용</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section class="mb-lg">
|
||||
<div class="grid grid-cols-2 gap-sm mb-sm">
|
||||
<button id="excelBtn" class="btn btn-secondary btn-large">
|
||||
<span class="material-icons">download</span>
|
||||
엑셀다운로드
|
||||
</button>
|
||||
<button id="redrawBtn" class="btn btn-text btn-large">
|
||||
<span class="material-icons">refresh</span>
|
||||
재추첨
|
||||
</button>
|
||||
</div>
|
||||
<button id="notifyBtn" class="btn btn-primary btn-large btn-full">
|
||||
당첨자에게 알림 전송
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Back to Events -->
|
||||
<section class="mb-lg">
|
||||
<button id="backToEventsBtn" class="btn btn-text btn-large btn-full">
|
||||
이벤트 목록으로
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drawing Animation -->
|
||||
<div id="drawingAnimation" class="drawing-animation">
|
||||
<div class="slot-machine mb-lg">🎰</div>
|
||||
<h2 class="text-title-large mb-sm" style="color: white;" id="animationText">추첨 중...</h2>
|
||||
<p class="text-body" style="color: rgba(255,255,255,0.7);" id="animationSubtext">난수 생성 중</p>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// Event data
|
||||
const eventData = {
|
||||
name: '신규고객 유치 이벤트',
|
||||
totalParticipants: 127,
|
||||
participants: [
|
||||
{ id: '00042', name: '김**', phone: '010-****-1234', channel: '우리동네TV', hasBonus: true },
|
||||
{ id: '00089', name: '이**', phone: '010-****-5678', channel: 'SNS', hasBonus: false },
|
||||
{ id: '00103', name: '박**', phone: '010-****-9012', channel: '링고비즈', hasBonus: true },
|
||||
{ id: '00012', name: '최**', phone: '010-****-3456', channel: 'SNS', hasBonus: false },
|
||||
{ id: '00067', name: '정**', phone: '010-****-7890', channel: '우리동네TV', hasBonus: false },
|
||||
{ id: '00025', name: '강**', phone: '010-****-2468', channel: '링고비즈', hasBonus: true },
|
||||
{ id: '00078', name: '조**', phone: '010-****-1357', channel: 'SNS', hasBonus: false }
|
||||
]
|
||||
};
|
||||
|
||||
// Drawing history data
|
||||
const drawingHistory = [
|
||||
{ date: '2025-01-15 14:30', winnerCount: 5, isRedraw: false },
|
||||
{ date: '2025-01-15 14:25', winnerCount: 5, isRedraw: true }
|
||||
];
|
||||
|
||||
// State
|
||||
let winnerCount = 5;
|
||||
let storeBonus = false;
|
||||
let winners = [];
|
||||
|
||||
// Initialize
|
||||
document.getElementById('eventName').textContent = eventData.name;
|
||||
document.getElementById('totalParticipants').textContent = `${eventData.totalParticipants}명`;
|
||||
|
||||
// Winner count controls
|
||||
document.getElementById('decreaseBtn').addEventListener('click', function() {
|
||||
if (winnerCount > 1) {
|
||||
winnerCount--;
|
||||
document.getElementById('winnerCount').textContent = winnerCount;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('increaseBtn').addEventListener('click', function() {
|
||||
if (winnerCount < 100 && winnerCount < eventData.totalParticipants) {
|
||||
winnerCount++;
|
||||
document.getElementById('winnerCount').textContent = winnerCount;
|
||||
}
|
||||
});
|
||||
|
||||
// Store bonus toggle
|
||||
document.getElementById('storeBonus').addEventListener('change', function() {
|
||||
storeBonus = this.checked;
|
||||
});
|
||||
|
||||
// Start drawing button
|
||||
document.getElementById('startDrawingBtn').addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '추첨 확인',
|
||||
content: `<p class="text-body text-center">총 ${eventData.totalParticipants}명 중 ${winnerCount}명을 추첨하시겠습니까?</p>`,
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
executeDrawing();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Execute drawing
|
||||
function executeDrawing() {
|
||||
const animation = document.getElementById('drawingAnimation');
|
||||
const animationText = document.getElementById('animationText');
|
||||
const animationSubtext = document.getElementById('animationSubtext');
|
||||
|
||||
animation.classList.add('active');
|
||||
|
||||
// Phase 1: 난수 생성 중 (1 second)
|
||||
setTimeout(() => {
|
||||
animationText.textContent = '당첨자 선정 중...';
|
||||
animationSubtext.textContent = '공정한 추첨을 진행하고 있습니다';
|
||||
}, 1000);
|
||||
|
||||
// Phase 2: 완료 (2 seconds)
|
||||
setTimeout(() => {
|
||||
animationText.textContent = '완료!';
|
||||
animationSubtext.textContent = '추첨이 완료되었습니다';
|
||||
}, 2000);
|
||||
|
||||
// Phase 3: Show results (3 seconds)
|
||||
setTimeout(() => {
|
||||
animation.classList.remove('active');
|
||||
|
||||
// Select random winners
|
||||
const shuffled = [...eventData.participants].sort(() => Math.random() - 0.5);
|
||||
winners = shuffled.slice(0, winnerCount);
|
||||
|
||||
// Show results
|
||||
showResults();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Show results
|
||||
function showResults() {
|
||||
document.getElementById('setupView').style.display = 'none';
|
||||
document.getElementById('resultsView').classList.add('active');
|
||||
|
||||
document.getElementById('totalCount').textContent = eventData.totalParticipants;
|
||||
document.getElementById('winnerCountDisplay').textContent = winnerCount;
|
||||
|
||||
renderWinners();
|
||||
}
|
||||
|
||||
// Render winners
|
||||
function renderWinners() {
|
||||
const winnerList = document.getElementById('winnerList');
|
||||
winnerList.innerHTML = '';
|
||||
|
||||
winners.forEach((winner, index) => {
|
||||
const rank = index + 1;
|
||||
const rankClass = rank === 1 ? 'rank-1' : rank === 2 ? 'rank-2' : rank === 3 ? 'rank-3' : 'rank-other';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-sm winner-card';
|
||||
card.innerHTML = `
|
||||
<div class="rank-badge ${rankClass}">${rank}위</div>
|
||||
<div>
|
||||
<p class="text-caption text-secondary mb-xs">응모번호: #${winner.id}</p>
|
||||
<p class="text-headline mb-xs">${winner.name} (${winner.phone})</p>
|
||||
<p class="text-body-small text-secondary">
|
||||
참여: ${winner.channel} ${winner.hasBonus && storeBonus ? '🌟' : ''}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
winnerList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Excel download button
|
||||
document.getElementById('excelBtn')?.addEventListener('click', function() {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}_${String(now.getHours()).padStart(2,'0')}${String(now.getMinutes()).padStart(2,'0')}`;
|
||||
const filename = `당첨자목록_${eventData.name}_${dateStr}.xlsx`;
|
||||
|
||||
KTEventApp.Feedback.showToast(`${filename} 다운로드를 시작합니다`);
|
||||
// 실제로는 엑셀 파일 생성 및 다운로드 로직
|
||||
});
|
||||
|
||||
// Redraw button
|
||||
document.getElementById('redrawBtn')?.addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '재추첨 확인',
|
||||
content: `
|
||||
<div class="text-center">
|
||||
<p class="text-body mb-md">재추첨 시 현재 당첨자 정보가 변경됩니다.</p>
|
||||
<p class="text-body mb-md">계속하시겠습니까?</p>
|
||||
<p class="text-body-small text-secondary">이전 추첨 이력은 보관됩니다</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '재추첨',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
|
||||
// Add to history
|
||||
drawingHistory.unshift({
|
||||
date: new Date().toLocaleString('ko-KR'),
|
||||
winnerCount: winnerCount,
|
||||
isRedraw: true
|
||||
});
|
||||
|
||||
// Execute new drawing
|
||||
document.getElementById('resultsView').classList.remove('active');
|
||||
document.getElementById('setupView').style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
executeDrawing();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Notify button
|
||||
document.getElementById('notifyBtn')?.addEventListener('click', function() {
|
||||
const cost = winnerCount * 100;
|
||||
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '알림 전송',
|
||||
content: `
|
||||
<div class="text-center">
|
||||
<p class="text-body mb-md">${winnerCount}명의 당첨자에게 SMS 알림을 전송하시겠습니까?</p>
|
||||
<p class="text-body-small text-secondary">예상 비용: ${KTEventApp.Utils.formatNumber(cost)}원 (100원/건)</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '취소',
|
||||
variant: 'text',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '전송',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
|
||||
// Simulate sending
|
||||
setTimeout(() => {
|
||||
KTEventApp.Feedback.showToast('알림이 전송되었습니다');
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Back to events button
|
||||
document.getElementById('backToEventsBtn')?.addEventListener('click', function() {
|
||||
window.location.href = '06-이벤트목록.html';
|
||||
});
|
||||
|
||||
// Render drawing history
|
||||
function renderHistory() {
|
||||
const historyList = document.getElementById('historyList');
|
||||
|
||||
if (drawingHistory.length === 0) {
|
||||
historyList.innerHTML = `
|
||||
<div class="card text-center py-lg">
|
||||
<p class="text-body text-secondary">추첨 이력이 없습니다</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = '';
|
||||
drawingHistory.slice(0, 3).forEach(history => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-sm';
|
||||
card.innerHTML = `
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-body mb-xs">${history.date} ${history.isRedraw ? '(재추첨)' : ''}</p>
|
||||
<p class="text-body-small text-secondary">당첨자 ${history.winnerCount}명</p>
|
||||
</div>
|
||||
<button class="btn btn-text btn-small history-detail-btn">
|
||||
<span class="material-icons">visibility</span>
|
||||
상세보기
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.querySelector('.history-detail-btn').addEventListener('click', function() {
|
||||
KTEventApp.Feedback.showModal({
|
||||
title: '추첨 이력 상세',
|
||||
content: `
|
||||
<div class="p-md">
|
||||
<p class="text-body mb-sm">추첨 일시: ${history.date}</p>
|
||||
<p class="text-body mb-sm">당첨 인원: ${history.winnerCount}명</p>
|
||||
<p class="text-body mb-sm">재추첨 여부: ${history.isRedraw ? '예' : '아니오'}</p>
|
||||
<p class="text-body-small text-secondary mt-md">※ 당첨자 정보는 개인정보 보호를 위해 마스킹 처리됩니다</p>
|
||||
</div>
|
||||
`,
|
||||
buttons: [
|
||||
{
|
||||
text: '확인',
|
||||
variant: 'primary',
|
||||
onClick: function() {
|
||||
this.closest('.modal-backdrop').remove();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
historyList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize history
|
||||
renderHistory();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,430 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>실시간 대시보드 - KT AI 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
|
||||
<style>
|
||||
.summary-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.summary-cards {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
.summary-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-md);
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.summary-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
margin: var(--spacing-sm) 0;
|
||||
}
|
||||
.summary-value.positive {
|
||||
color: var(--color-success);
|
||||
}
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: var(--color-gray-50);
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
.line-chart-placeholder {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.roi-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.roi-table td {
|
||||
padding: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
.roi-table td:first-child {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.roi-table td:last-child {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
.profile-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
.profile-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
.profile-label {
|
||||
min-width: 60px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.profile-bar-bg {
|
||||
flex: 1;
|
||||
height: 24px;
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-sm);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.profile-bar-fill {
|
||||
height: 100%;
|
||||
background: var(--color-ai-blue);
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: var(--spacing-xs);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.refresh-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.update-pulse {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-success);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page page-with-header">
|
||||
<!-- Header -->
|
||||
<div id="header"></div>
|
||||
|
||||
<div class="container" style="max-width: 1200px;">
|
||||
<!-- Title with Real-time Indicator -->
|
||||
<section class="mt-lg mb-md flex items-center justify-between">
|
||||
<h2 class="text-title">📊 요약 (실시간)</h2>
|
||||
<div class="refresh-indicator">
|
||||
<div class="update-pulse"></div>
|
||||
<span id="lastUpdate">방금 전</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Summary KPI Cards -->
|
||||
<section class="mb-lg">
|
||||
<div class="summary-cards">
|
||||
<div class="summary-card">
|
||||
<p class="text-body-small text-secondary">참여자 수</p>
|
||||
<div class="summary-value">128명</div>
|
||||
<p class="text-caption text-success">↑ 12명 (오늘)</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p class="text-body-small text-secondary">총 비용</p>
|
||||
<div class="summary-value">30만원</div>
|
||||
<p class="text-caption text-secondary">경품 25만 + 채널 5만</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p class="text-body-small text-secondary">예상 수익</p>
|
||||
<div class="summary-value positive">135만원</div>
|
||||
<p class="text-caption text-success">매출증가 100만 + LTV 35만</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p class="text-body-small text-secondary">투자대비수익률</p>
|
||||
<div class="summary-value positive">450%</div>
|
||||
<p class="text-caption text-success">목표 300% 달성</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Charts Grid -->
|
||||
<div class="grid desktop:grid-cols-2 gap-lg mb-lg">
|
||||
<!-- Channel Performance -->
|
||||
<section>
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">pie_chart</span>
|
||||
<h3 class="text-headline">채널별 성과</h3>
|
||||
</div>
|
||||
<div class="chart-placeholder">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-300);">donut_large</span>
|
||||
<p class="text-body-small text-secondary mt-md">파이 차트</p>
|
||||
</div>
|
||||
<div class="chart-legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot" style="background: #E31E24;"></div>
|
||||
<span class="text-body-small flex-1">우리동네TV</span>
|
||||
<span class="text-body-small text-semibold">45% (58명)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot" style="background: #0066FF;"></div>
|
||||
<span class="text-body-small flex-1">링고비즈</span>
|
||||
<span class="text-body-small text-semibold">30% (38명)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot" style="background: #FFB800;"></div>
|
||||
<span class="text-body-small flex-1">SNS</span>
|
||||
<span class="text-body-small text-semibold">25% (32명)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Time Trend -->
|
||||
<section>
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">show_chart</span>
|
||||
<h3 class="text-headline">시간대별 참여 추이</h3>
|
||||
</div>
|
||||
<div class="chart-placeholder line-chart-placeholder">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-300);">trending_up</span>
|
||||
<p class="text-body-small text-secondary mt-md">라인 차트</p>
|
||||
</div>
|
||||
<div class="mt-md">
|
||||
<p class="text-body-small text-secondary">피크 시간: 오후 2-4시 (35명)</p>
|
||||
<p class="text-body-small text-secondary">평균 시간당: 8명</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ROI Detail & Participant Profile -->
|
||||
<div class="grid desktop:grid-cols-2 gap-lg mb-2xl">
|
||||
<!-- ROI Detail -->
|
||||
<section>
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">payments</span>
|
||||
<h3 class="text-headline">투자대비수익률 상세</h3>
|
||||
</div>
|
||||
<table class="roi-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2" class="text-headline">총 비용: 30만원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>• 경품 비용</td>
|
||||
<td>25만원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>• 채널 비용</td>
|
||||
<td>5만원</td>
|
||||
</tr>
|
||||
<tr style="height: var(--spacing-md);"></tr>
|
||||
<tr>
|
||||
<td colspan="2" class="text-headline">예상 수익: 135만원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>• 매출 증가</td>
|
||||
<td class="text-success">100만원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>• 신규 고객 LTV</td>
|
||||
<td class="text-success">35만원</td>
|
||||
</tr>
|
||||
<tr style="height: var(--spacing-md);"></tr>
|
||||
<tr>
|
||||
<td colspan="2" class="text-headline text-success">투자대비수익률</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="p-md" style="background: var(--color-gray-50); border-radius: var(--radius-sm);">
|
||||
<p class="text-body-small text-center mb-xs">(수익 - 비용) ÷ 비용 × 100</p>
|
||||
<p class="text-body text-center">(135만 - 30만) ÷ 30만 × 100</p>
|
||||
<p class="text-headline text-center text-success mt-sm">= 450%</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Participant Profile -->
|
||||
<section>
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-sm mb-md">
|
||||
<span class="material-icons text-kt-red">people</span>
|
||||
<h3 class="text-headline">참여자 프로필</h3>
|
||||
</div>
|
||||
|
||||
<div class="mb-lg">
|
||||
<p class="text-body mb-sm">연령별</p>
|
||||
<div class="profile-bars">
|
||||
<div class="profile-bar">
|
||||
<span class="profile-label">20대</span>
|
||||
<div class="profile-bar-bg">
|
||||
<div class="profile-bar-fill" style="width: 35%;">35%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-bar">
|
||||
<span class="profile-label">30대</span>
|
||||
<div class="profile-bar-bg">
|
||||
<div class="profile-bar-fill" style="width: 40%;">40%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-bar">
|
||||
<span class="profile-label">40대</span>
|
||||
<div class="profile-bar-bg">
|
||||
<div class="profile-bar-fill" style="width: 25%;">25%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-body mb-sm">성별</p>
|
||||
<div class="profile-bars">
|
||||
<div class="profile-bar">
|
||||
<span class="profile-label">여성</span>
|
||||
<div class="profile-bar-bg">
|
||||
<div class="profile-bar-fill" style="width: 60%; background: var(--color-kt-red);">60%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-bar">
|
||||
<span class="profile-label">남성</span>
|
||||
<div class="profile-bar-bg">
|
||||
<div class="profile-bar-fill" style="width: 40%; background: var(--color-kt-red);">40%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div id="bottomNav"></div>
|
||||
</div>
|
||||
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
// 로그인 확인
|
||||
KTEventApp.Session.requireAuth();
|
||||
|
||||
// Header 생성
|
||||
const header = KTEventApp.Navigation.createHeader({
|
||||
title: '실시간 대시보드',
|
||||
showBack: true,
|
||||
showMenu: false,
|
||||
showProfile: true
|
||||
});
|
||||
document.getElementById('header').appendChild(header);
|
||||
|
||||
// Bottom Navigation 생성
|
||||
const bottomNav = KTEventApp.Navigation.createBottomNav('analytics');
|
||||
document.getElementById('bottomNav').appendChild(bottomNav);
|
||||
|
||||
// Real-time update simulation (every 5 minutes)
|
||||
let updateInterval = null;
|
||||
let lastUpdateTime = new Date();
|
||||
|
||||
function updateLastUpdateTime() {
|
||||
const now = new Date();
|
||||
const diff = Math.floor((now - lastUpdateTime) / 1000);
|
||||
|
||||
let timeText;
|
||||
if (diff < 60) {
|
||||
timeText = '방금 전';
|
||||
} else if (diff < 3600) {
|
||||
timeText = `${Math.floor(diff / 60)}분 전`;
|
||||
} else {
|
||||
timeText = `${Math.floor(diff / 3600)}시간 전`;
|
||||
}
|
||||
|
||||
document.getElementById('lastUpdate').textContent = timeText;
|
||||
}
|
||||
|
||||
function simulateUpdate() {
|
||||
// Simulate data update
|
||||
lastUpdateTime = new Date();
|
||||
updateLastUpdateTime();
|
||||
|
||||
// Show toast notification
|
||||
KTEventApp.Feedback.showToast('데이터가 업데이트되었습니다');
|
||||
}
|
||||
|
||||
// Update time display every 30 seconds
|
||||
setInterval(updateLastUpdateTime, 30000);
|
||||
|
||||
// Simulate data update every 5 minutes (300000ms)
|
||||
updateInterval = setInterval(simulateUpdate, 300000);
|
||||
|
||||
// Initial update
|
||||
updateLastUpdateTime();
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (updateInterval) {
|
||||
clearInterval(updateInterval);
|
||||
}
|
||||
});
|
||||
|
||||
// Pull to refresh (Mobile)
|
||||
let touchStartY = 0;
|
||||
let isPulling = false;
|
||||
|
||||
document.addEventListener('touchstart', function(e) {
|
||||
if (window.scrollY === 0) {
|
||||
touchStartY = e.touches[0].clientY;
|
||||
isPulling = true;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('touchmove', function(e) {
|
||||
if (!isPulling) return;
|
||||
|
||||
const touchY = e.touches[0].clientY;
|
||||
const pullDistance = touchY - touchStartY;
|
||||
|
||||
if (pullDistance > 80) {
|
||||
isPulling = false;
|
||||
simulateUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('touchend', function() {
|
||||
isPulling = false;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+1071
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,973 @@
|
||||
/* =================================================================
|
||||
KT AI 이벤트 마케팅 서비스 - 공통 스타일시트
|
||||
Version: 1.0.0
|
||||
Mobile First Design Philosophy
|
||||
================================================================= */
|
||||
|
||||
/* =================================================================
|
||||
1. CSS Variables - Design System
|
||||
================================================================= */
|
||||
:root {
|
||||
/* Brand Colors */
|
||||
--color-kt-red: #E31E24;
|
||||
--color-ai-blue: #0066FF;
|
||||
|
||||
/* Grayscale */
|
||||
--color-gray-50: #F9FAFB;
|
||||
--color-gray-100: #F3F4F6;
|
||||
--color-gray-200: #E5E7EB;
|
||||
--color-gray-300: #D1D5DB;
|
||||
--color-gray-400: #9CA3AF;
|
||||
--color-gray-500: #6B7280;
|
||||
--color-gray-600: #4B5563;
|
||||
--color-gray-700: #374151;
|
||||
--color-gray-800: #1F2937;
|
||||
--color-gray-900: #111827;
|
||||
|
||||
/* Semantic Colors */
|
||||
--color-success: #10B981;
|
||||
--color-warning: #F59E0B;
|
||||
--color-error: #EF4444;
|
||||
--color-info: #3B82F6;
|
||||
|
||||
/* Background */
|
||||
--color-bg-primary: #FFFFFF;
|
||||
--color-bg-secondary: #F9FAFB;
|
||||
--color-bg-tertiary: #F3F4F6;
|
||||
|
||||
/* Text */
|
||||
--color-text-primary: #111827;
|
||||
--color-text-secondary: #4B5563;
|
||||
--color-text-tertiary: #9CA3AF;
|
||||
--color-text-inverse: #FFFFFF;
|
||||
|
||||
/* Gradients */
|
||||
--gradient-primary: linear-gradient(135deg, #E31E24 0%, #B71419 100%);
|
||||
--gradient-ai: linear-gradient(135deg, #0066FF 0%, #0052CC 100%);
|
||||
|
||||
/* Typography Scale */
|
||||
--font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
--font-size-display: 28px;
|
||||
--font-size-title-large: 24px;
|
||||
--font-size-title: 20px;
|
||||
--font-size-headline: 18px;
|
||||
--font-size-body-large: 16px;
|
||||
--font-size-body: 14px;
|
||||
--font-size-body-small: 13px;
|
||||
--font-size-caption: 12px;
|
||||
|
||||
--line-height-display: 1.2;
|
||||
--line-height-title: 1.3;
|
||||
--line-height-body: 1.5;
|
||||
--line-height-caption: 1.4;
|
||||
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-regular: 400;
|
||||
|
||||
/* Spacing System (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Z-Index */
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-fixed: 1030;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
|
||||
/* Animation */
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Tablet Responsive Variables */
|
||||
@media (min-width: 768px) {
|
||||
:root {
|
||||
--font-size-display: 32px;
|
||||
--font-size-title-large: 28px;
|
||||
--font-size-title: 24px;
|
||||
--font-size-headline: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop Responsive Variables */
|
||||
@media (min-width: 1024px) {
|
||||
:root {
|
||||
--font-size-display: 36px;
|
||||
--font-size-title-large: 32px;
|
||||
--font-size-title: 28px;
|
||||
--font-size-headline: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
2. Reset & Base Styles
|
||||
================================================================= */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body);
|
||||
line-height: var(--line-height-body);
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-primary);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
3. Typography
|
||||
================================================================= */
|
||||
.text-display {
|
||||
font-size: var(--font-size-display);
|
||||
line-height: var(--line-height-display);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.text-title-large {
|
||||
font-size: var(--font-size-title-large);
|
||||
line-height: var(--line-height-title);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.text-title {
|
||||
font-size: var(--font-size-title);
|
||||
line-height: var(--line-height-title);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.text-headline {
|
||||
font-size: var(--font-size-headline);
|
||||
line-height: var(--line-height-title);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.text-body-large {
|
||||
font-size: var(--font-size-body-large);
|
||||
line-height: var(--line-height-body);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.text-body {
|
||||
font-size: var(--font-size-body);
|
||||
line-height: var(--line-height-body);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.text-body-small {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
font-size: var(--font-size-caption);
|
||||
line-height: var(--line-height-caption);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.text-primary { color: var(--color-text-primary); }
|
||||
.text-secondary { color: var(--color-text-secondary); }
|
||||
.text-tertiary { color: var(--color-text-tertiary); }
|
||||
.text-inverse { color: var(--color-text-inverse); }
|
||||
.text-kt-red { color: var(--color-kt-red); }
|
||||
.text-ai-blue { color: var(--color-ai-blue); }
|
||||
.text-success { color: var(--color-success); }
|
||||
.text-warning { color: var(--color-warning); }
|
||||
.text-error { color: var(--color-error); }
|
||||
|
||||
.text-bold { font-weight: var(--font-weight-bold); }
|
||||
.text-semibold { font-weight: var(--font-weight-semibold); }
|
||||
.text-medium { font-weight: var(--font-weight-medium); }
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
/* =================================================================
|
||||
4. Layout Utilities
|
||||
================================================================= */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-md);
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding-bottom: 76px; /* Bottom nav height + spacing */
|
||||
}
|
||||
|
||||
.page-with-header {
|
||||
padding-top: 56px; /* Header height */
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
5. Button Components
|
||||
================================================================= */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-family);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast) var(--ease-out);
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Button Sizes */
|
||||
.btn-large {
|
||||
height: 48px;
|
||||
padding: 0 var(--spacing-lg);
|
||||
font-size: var(--font-size-body-large);
|
||||
}
|
||||
|
||||
.btn-medium {
|
||||
height: 44px;
|
||||
padding: 0 var(--spacing-md);
|
||||
font-size: var(--font-size-body);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
height: 36px;
|
||||
padding: 0 var(--spacing-md);
|
||||
font-size: var(--font-size-body-small);
|
||||
}
|
||||
|
||||
/* Button Variants */
|
||||
.btn-primary {
|
||||
background: var(--gradient-primary);
|
||||
color: var(--color-text-inverse);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-kt-red);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background-color: var(--color-gray-50);
|
||||
border-color: var(--color-kt-red);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
background-color: transparent;
|
||||
color: var(--color-kt-red);
|
||||
}
|
||||
|
||||
.btn-text:hover:not(:disabled) {
|
||||
background-color: rgba(227, 30, 36, 0.08);
|
||||
}
|
||||
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
6. Card Components
|
||||
================================================================= */
|
||||
.card {
|
||||
background-color: var(--color-bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.card-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-clickable:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.event-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.event-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.event-card-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: var(--font-size-caption);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.badge-scheduled {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
.badge-ended {
|
||||
background-color: rgba(156, 163, 175, 0.1);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.event-card-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-md);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: var(--font-size-caption);
|
||||
color: var(--color-text-tertiary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: var(--font-size-headline);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* KPI Card */
|
||||
.kpi-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.kpi-icon-primary {
|
||||
background: rgba(227, 30, 36, 0.1);
|
||||
color: var(--color-kt-red);
|
||||
}
|
||||
|
||||
.kpi-icon-ai {
|
||||
background: rgba(0, 102, 255, 0.1);
|
||||
color: var(--color-ai-blue);
|
||||
}
|
||||
|
||||
.kpi-icon-success {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.kpi-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: var(--font-size-title);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Option Card for Selection */
|
||||
.option-card {
|
||||
position: relative;
|
||||
border: 2px solid var(--color-gray-200);
|
||||
transition: all var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
border-color: var(--color-kt-red);
|
||||
}
|
||||
|
||||
.option-card.selected {
|
||||
border-color: var(--color-kt-red);
|
||||
background-color: rgba(227, 30, 36, 0.02);
|
||||
}
|
||||
|
||||
.option-card-radio {
|
||||
position: absolute;
|
||||
top: var(--spacing-md);
|
||||
right: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
7. Form Components
|
||||
================================================================= */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.form-label-required::after {
|
||||
content: " *";
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 12px var(--spacing-md);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body);
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-primary);
|
||||
transition: all var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.form-input::placeholder,
|
||||
.form-textarea::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-kt-red);
|
||||
box-shadow: 0 0 0 3px rgba(227, 30, 36, 0.1);
|
||||
}
|
||||
|
||||
.form-input:disabled,
|
||||
.form-select:disabled,
|
||||
.form-textarea:disabled {
|
||||
background-color: var(--color-gray-100);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
display: block;
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
display: block;
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
/* Checkbox & Radio */
|
||||
.form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
font-size: var(--font-size-body);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
8. Navigation Components
|
||||
================================================================= */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-bg-primary);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--spacing-md);
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: var(--font-size-headline);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.header-icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
transition: background-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.header-icon-btn:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
background-color: var(--color-bg-primary);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 0 var(--spacing-sm);
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.bottom-nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-sm);
|
||||
color: var(--color-text-tertiary);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-caption);
|
||||
transition: color var(--duration-fast) var(--ease-out);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bottom-nav-item:hover {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.bottom-nav-item.active {
|
||||
color: var(--color-kt-red);
|
||||
}
|
||||
|
||||
.bottom-nav-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/* FAB (Floating Action Button) */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 76px; /* Bottom nav height + spacing */
|
||||
right: var(--spacing-md);
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: var(--gradient-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast) var(--ease-out);
|
||||
z-index: var(--z-fixed);
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
box-shadow: var(--shadow-xl);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
9. Feedback Components
|
||||
================================================================= */
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 92px; /* Bottom nav + spacing */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background-color: var(--color-gray-900);
|
||||
color: var(--color-text-inverse);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
font-size: var(--font-size-body);
|
||||
z-index: var(--z-tooltip);
|
||||
animation: slideUp var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, 20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: var(--z-modal-backdrop);
|
||||
animation: fadeIn var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--color-bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-modal);
|
||||
max-width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: scaleIn var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--font-size-title);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Bottom Sheet */
|
||||
.bottom-sheet {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-bg-primary);
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-modal);
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
animation: slideUpSheet var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
@keyframes slideUpSheet {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-sheet-handle {
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background-color: var(--color-gray-300);
|
||||
border-radius: var(--radius-full);
|
||||
margin: var(--spacing-sm) auto;
|
||||
}
|
||||
|
||||
.bottom-sheet-content {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid var(--color-gray-200);
|
||||
border-top-color: var(--color-kt-red);
|
||||
border-radius: var(--radius-full);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.spinner-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Progress Bar */
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-200);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: var(--gradient-primary);
|
||||
border-radius: var(--radius-full);
|
||||
transition: width var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
10. Utility Classes
|
||||
================================================================= */
|
||||
/* Spacing */
|
||||
.m-0 { margin: 0; }
|
||||
.mt-xs { margin-top: var(--spacing-xs); }
|
||||
.mt-sm { margin-top: var(--spacing-sm); }
|
||||
.mt-md { margin-top: var(--spacing-md); }
|
||||
.mt-lg { margin-top: var(--spacing-lg); }
|
||||
.mt-xl { margin-top: var(--spacing-xl); }
|
||||
.mt-2xl { margin-top: var(--spacing-2xl); }
|
||||
|
||||
.mb-xs { margin-bottom: var(--spacing-xs); }
|
||||
.mb-sm { margin-bottom: var(--spacing-sm); }
|
||||
.mb-md { margin-bottom: var(--spacing-md); }
|
||||
.mb-lg { margin-bottom: var(--spacing-lg); }
|
||||
.mb-xl { margin-bottom: var(--spacing-xl); }
|
||||
.mb-2xl { margin-bottom: var(--spacing-2xl); }
|
||||
|
||||
.p-0 { padding: 0; }
|
||||
.p-xs { padding: var(--spacing-xs); }
|
||||
.p-sm { padding: var(--spacing-sm); }
|
||||
.p-md { padding: var(--spacing-md); }
|
||||
.p-lg { padding: var(--spacing-lg); }
|
||||
.p-xl { padding: var(--spacing-xl); }
|
||||
.p-2xl { padding: var(--spacing-2xl); }
|
||||
|
||||
.gap-xs { gap: var(--spacing-xs); }
|
||||
.gap-sm { gap: var(--spacing-sm); }
|
||||
.gap-md { gap: var(--spacing-md); }
|
||||
.gap-lg { gap: var(--spacing-lg); }
|
||||
|
||||
/* Flexbox */
|
||||
.flex { display: flex; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-row { flex-direction: row; }
|
||||
.items-center { align-items: center; }
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.flex-1 { flex: 1; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
|
||||
/* Grid */
|
||||
.grid { display: grid; }
|
||||
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
|
||||
|
||||
/* Display */
|
||||
.hidden { display: none; }
|
||||
.block { display: block; }
|
||||
.inline-block { display: inline-block; }
|
||||
|
||||
/* Width */
|
||||
.w-full { width: 100%; }
|
||||
.w-auto { width: auto; }
|
||||
|
||||
/* Background */
|
||||
.bg-primary { background-color: var(--color-bg-primary); }
|
||||
.bg-secondary { background-color: var(--color-bg-secondary); }
|
||||
.bg-tertiary { background-color: var(--color-bg-tertiary); }
|
||||
|
||||
/* Border */
|
||||
.border { border: 1px solid var(--color-gray-200); }
|
||||
.border-t { border-top: 1px solid var(--color-gray-200); }
|
||||
.border-b { border-bottom: 1px solid var(--color-gray-200); }
|
||||
.border-none { border: none; }
|
||||
|
||||
.rounded-sm { border-radius: var(--radius-sm); }
|
||||
.rounded-md { border-radius: var(--radius-md); }
|
||||
.rounded-lg { border-radius: var(--radius-lg); }
|
||||
.rounded-xl { border-radius: var(--radius-xl); }
|
||||
.rounded-full { border-radius: var(--radius-full); }
|
||||
|
||||
/* Shadow */
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow-md { box-shadow: var(--shadow-md); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
.shadow-none { box-shadow: none; }
|
||||
|
||||
/* Cursor */
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-not-allowed { cursor: not-allowed; }
|
||||
|
||||
/* =================================================================
|
||||
11. Responsive Grid System
|
||||
================================================================= */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.tablet\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
.tablet\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.tablet\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
padding: 0 var(--spacing-xl);
|
||||
}
|
||||
|
||||
.desktop\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
.desktop\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.desktop\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
|
||||
.desktop\:grid-cols-5 { grid-template-columns: repeat(5, 1fr); }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2473
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user