This commit is contained in:
박세원 2025-10-21 15:53:07 +09:00
commit 16c8a6a5b3
11 changed files with 4789 additions and 0 deletions

View File

@ -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="display: inline-block; 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: true,
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>

View File

@ -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>

View File

@ -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: true,
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>

View File

@ -0,0 +1,296 @@
<!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;
}
.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;
}
</style>
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container" style="max-width: 800px;">
<!-- 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>
<div class="spinner mx-auto"></div>
<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">
<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-between">
<div class="form-check">
<input type="radio" name="imageStyle" id="style1" value="simple" class="form-check-input">
<label for="style1" class="form-check-label">선택</label>
</div>
<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">
<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-between">
<div class="form-check">
<input type="radio" name="imageStyle" id="style2" value="fancy" class="form-check-input">
<label for="style2" class="form-check-label">선택</label>
</div>
<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">
<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-between">
<div class="form-check">
<input type="radio" name="imageStyle" id="style3" value="trendy" class="form-check-input">
<label for="style3" class="form-check-label">선택</label>
</div>
<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('input[name="imageStyle"]').forEach(radio => {
radio.addEventListener('change', function() {
selectedStyle = this.value;
document.getElementById('nextBtn').disabled = false;
});
});
// 크게보기 버튼
document.querySelectorAll('.preview-btn').forEach(btn => {
btn.addEventListener('click', function() {
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>

View File

@ -0,0 +1,329 @@
<!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: 1000px;">
<!-- 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>
<!-- Color Adjustment -->
<div class="card mb-md">
<h4 class="text-headline mb-md">
<span class="material-icons" style="vertical-align: middle;">palette</span>
색상 조정
</h4>
<div class="form-group">
<label class="form-label">배경색</label>
<div class="color-picker-wrapper">
<div class="color-swatch" id="bgColorSwatch" onclick="document.getElementById('bgColorInput').click()" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"></div>
<input type="color" id="bgColorInput" value="#667eea">
<span class="text-body-small text-secondary">클릭하여 선택</span>
</div>
</div>
<div class="form-group">
<label class="form-label">텍스트색</label>
<div class="color-picker-wrapper">
<div class="color-swatch" id="textColorSwatch" onclick="document.getElementById('textColorInput').click()" style="background: #ffffff;"></div>
<input type="color" id="textColorInput" value="#ffffff">
<span class="text-body-small text-secondary">클릭하여 선택</span>
</div>
</div>
<div class="form-group">
<label class="form-label">강조색</label>
<div class="color-picker-wrapper">
<div class="color-swatch" id="accentColorSwatch" onclick="document.getElementById('accentColorInput').click()" style="background: #E31E24;"></div>
<input type="color" id="accentColorInput" value="#E31E24">
<span class="text-body-small text-secondary">클릭하여 선택</span>
</div>
</div>
</div>
<!-- Logo Position -->
<div class="card mb-md">
<h4 class="text-headline mb-md">
<span class="material-icons" style="vertical-align: middle;">business</span>
로고 위치
</h4>
<div class="form-group">
<label class="form-label">위치 이동</label>
<div class="logo-controls">
<button class="btn btn-secondary btn-small" onclick="moveLogo('left')">
<span class="material-icons">chevron_left</span>
</button>
<span class="text-body-small text-secondary flex-1 text-center">좌/우 이동</span>
<button class="btn btn-secondary btn-small" onclick="moveLogo('right')">
<span class="material-icons">chevron_right</span>
</button>
</div>
</div>
<div class="form-group">
<label class="form-label">크기 조절</label>
<div class="logo-controls">
<button class="btn btn-secondary btn-small" onclick="resizeLogo(-1)">
<span class="material-icons">remove</span>
</button>
<span class="text-body-small text-secondary flex-1 text-center" id="logoSize">중간</span>
<button class="btn btn-secondary btn-small" onclick="resizeLogo(1)">
<span class="material-icons">add</span>
</button>
</div>
</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;
const bgColor = document.getElementById('bgColorInput').value;
const textColor = document.getElementById('textColorInput').value;
const preview = document.getElementById('preview');
preview.style.background = `linear-gradient(135deg, ${bgColor} 0%, #764ba2 100%)`;
preview.style.color = textColor;
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('bgColorInput').addEventListener('change', function() {
document.getElementById('bgColorSwatch').style.background = this.value;
updatePreview();
});
document.getElementById('textColorInput').addEventListener('change', function() {
document.getElementById('textColorSwatch').style.background = this.value;
updatePreview();
});
document.getElementById('accentColorInput').addEventListener('change', function() {
document.getElementById('accentColorSwatch').style.background = this.value;
});
// 로고 위치 이동
let logoPosition = 0;
window.moveLogo = function(direction) {
if (direction === 'left' && logoPosition > -2) {
logoPosition--;
} else if (direction === 'right' && logoPosition < 2) {
logoPosition++;
}
// 실제로는 로고 위치 조정 로직 구현
KTEventApp.Feedback.showToast(`로고를 ${direction === 'left' ? '왼쪽' : '오른쪽'}으로 이동했습니다`);
};
// 로고 크기 조절
let logoSize = 1;
const logoSizeLabels = ['작게', '중간', '크게'];
window.resizeLogo = function(delta) {
logoSize = Math.max(0, Math.min(2, logoSize + delta));
document.getElementById('logoSize').textContent = logoSizeLabels[logoSize];
KTEventApp.Feedback.showToast(`로고 크기: ${logoSizeLabels[logoSize]}`);
};
// 저장 버튼
document.getElementById('saveBtn').addEventListener('click', function() {
const editData = {
title: document.getElementById('titleInput').value,
prize: document.getElementById('prizeInput').value,
guide: document.getElementById('guideInput').value,
bgColor: document.getElementById('bgColorInput').value,
textColor: document.getElementById('textColorInput').value,
accentColor: document.getElementById('accentColorInput').value,
logoPosition: logoPosition,
logoSize: logoSize
};
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,
bgColor: document.getElementById('bgColorInput').value,
textColor: document.getElementById('textColorInput').value,
accentColor: document.getElementById('accentColorInput').value,
logoPosition: logoPosition,
logoSize: logoSize
};
KTEventApp.Utils.saveToStorage('content_edit', editData);
// 배포 채널 선택으로 이동
window.location.href = '11-배포채널선택.html';
});
// 초기 미리보기 업데이트
updatePreview();
</script>
</body>
</html>

View File

@ -0,0 +1,336 @@
<!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;
}
</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();
});
// 요약 업데이트
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
} : null
};
KTEventApp.Utils.saveToStorage('selected_channels', channelData);
// 최종 승인 화면으로 이동
window.location.href = '12-최종승인.html';
});
</script>
</body>
</html>

View File

@ -0,0 +1,350 @@
<!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 -->
<section class="mb-md">
<p class="text-headline"><span id="totalCount">128</span>명 참여</p>
</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: true,
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('참여자 목록을 다운로드합니다');
// 실제로는 엑셀 다운로드 로직 구현
});
// 초기 렌더링
renderParticipantList();
</script>
</body>
</html>

View File

@ -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>

View File

@ -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>

View File

@ -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: true,
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>

File diff suppressed because it is too large Load Diff