mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 12:06:24 +00:00
프로토타입 파일 구조 개선 및 CSS 디렉토리 통합
- 중복 프로토타입 파일 삭제 - common.css/js를 css 디렉토리로 통합 - uiux.md 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d1bb9d0231
commit
58ab3bac44
@ -1,307 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-primary" style="font-size: 28px;">store</span>
|
||||
<span class="h3 text-primary">KT 이벤트 도우미</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Welcome Section -->
|
||||
<section class="mb-l">
|
||||
<h1 class="h2 mb-s">안녕하세요, <span id="userName">정우진</span>님!</h1>
|
||||
<p class="body-l text-gray-700" id="businessName">우진이네 고깃집</p>
|
||||
</section>
|
||||
|
||||
<!-- Active Events Section -->
|
||||
<section class="mb-xl">
|
||||
<div class="flex items-center justify-between mb-m">
|
||||
<h2 class="h3">진행 중인 이벤트</h2>
|
||||
<a href="#" class="body-s text-secondary" id="viewAllEvents">전체보기 ></a>
|
||||
</div>
|
||||
|
||||
<div id="activeEventsList" class="flex flex-col gap-m">
|
||||
<!-- 이벤트 카드들이 JavaScript로 생성됨 -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Actions Section -->
|
||||
<section class="mb-xl">
|
||||
<h2 class="h3 mb-m">빠른 실행</h2>
|
||||
|
||||
<div class="grid gap-m">
|
||||
<!-- 새 이벤트 만들기 -->
|
||||
<div class="col-2 card card-clickable" onclick="NavManager.navigate('03', '이벤트목적선택')">
|
||||
<div class="card-body flex flex-col items-center justify-center gap-s p-l">
|
||||
<span class="material-icons text-primary" style="font-size: 40px;">add_circle</span>
|
||||
<span class="body-l text-semibold">새 이벤트<br/>만들기</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 이벤트 관리 -->
|
||||
<div class="col-2 card card-clickable" onclick="showEventManagement()">
|
||||
<div class="card-body flex flex-col items-center justify-center gap-s p-l">
|
||||
<span class="material-icons text-secondary" style="font-size: 40px;">event_note</span>
|
||||
<span class="body-l text-semibold">이벤트<br/>관리</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 보기 -->
|
||||
<div class="col-2 card card-clickable" onclick="NavManager.navigate('21', '실시간대시보드')">
|
||||
<div class="card-body flex flex-col items-center justify-center gap-s p-l">
|
||||
<span class="material-icons text-success" style="font-size: 40px;">bar_chart</span>
|
||||
<span class="body-l text-semibold">통계<br/>보기</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 도움말 -->
|
||||
<div class="col-2 card card-clickable" onclick="showHelp()">
|
||||
<div class="card-body flex flex-col items-center justify-center gap-s p-l">
|
||||
<span class="material-icons text-warning" style="font-size: 40px;">help</span>
|
||||
<span class="body-l text-semibold">도움말</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tips Section -->
|
||||
<section class="mb-xl">
|
||||
<div class="card bg-secondary-light" style="border: none;">
|
||||
<div class="card-body flex gap-m">
|
||||
<span class="material-icons text-secondary" style="font-size: 32px;">lightbulb</span>
|
||||
<div>
|
||||
<h3 class="body-l text-semibold mb-xs text-secondary">AI 활용 팁</h3>
|
||||
<p class="body-m text-gray-700">이벤트 목적을 명확히 입력하면 더 정확한 기획안을 받을 수 있어요!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item active" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// 사용자 정보 로드
|
||||
function loadUserInfo() {
|
||||
const user = AppState.loadUser() || SampleData.user;
|
||||
document.getElementById('userName').textContent = user.name;
|
||||
document.getElementById('businessName').textContent = user.businessName;
|
||||
}
|
||||
|
||||
// 진행 중인 이벤트 카드 렌더링
|
||||
function renderActiveEvents() {
|
||||
const events = SampleData.events.filter(e => e.status === 'in_progress');
|
||||
const container = document.getElementById('activeEventsList');
|
||||
|
||||
if (events.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="card bg-gray-100" style="border: none;">
|
||||
<div class="card-body text-center p-xl">
|
||||
<span class="material-icons text-gray-500" style="font-size: 48px;">event_busy</span>
|
||||
<p class="body-m text-gray-600 mt-m">진행 중인 이벤트가 없습니다.</p>
|
||||
<button class="btn btn-primary mt-m" onclick="NavManager.navigate('03', '이벤트목적선택')">
|
||||
새 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = events.slice(0, 3).map(event => {
|
||||
const daysLeft = calculateDaysLeft(event.startDate);
|
||||
const badgeClass = daysLeft <= 3 ? 'badge-error' : daysLeft <= 7 ? 'badge-warning' : 'badge-success';
|
||||
|
||||
return `
|
||||
<div class="card card-clickable" onclick="showEventDetail('${event.id}')">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-m">
|
||||
<h3 class="body-l text-semibold">${event.name}</h3>
|
||||
<span class="badge ${badgeClass}">D-${daysLeft}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-m text-gray-700">
|
||||
<div class="flex items-center gap-xs">
|
||||
<span class="material-icons" style="font-size: 18px;">calendar_today</span>
|
||||
<span class="body-s">${event.startDate}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-xs">
|
||||
<span class="material-icons" style="font-size: 18px;">people</span>
|
||||
<span class="body-s">${Utils.formatNumber(event.participants)}명</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-m">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${Math.min((event.participants / 200) * 100, 100)}%"></div>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 mt-xs text-right">
|
||||
목표 대비 ${Math.round((event.participants / 200) * 100)}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// D-Day 계산
|
||||
function calculateDaysLeft(startDateStr) {
|
||||
const parts = startDateStr.split('.');
|
||||
const startDate = new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
const today = new Date();
|
||||
const diffTime = startDate - today;
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
return Math.max(diffDays, 0);
|
||||
}
|
||||
|
||||
// 이벤트 상세 보기
|
||||
function showEventDetail(eventId) {
|
||||
const event = SampleData.events.find(e => e.id === eventId);
|
||||
if (!event) return;
|
||||
|
||||
UIManager.showModal({
|
||||
title: event.name,
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-gray-700">calendar_today</span>
|
||||
<span class="body-m">${event.startDate} 시작</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-gray-700">people</span>
|
||||
<span class="body-m">참여자 ${Utils.formatNumber(event.participants)}명</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-gray-700">info</span>
|
||||
<span class="body-m">상태: ${event.status === 'in_progress' ? '진행 중' : '완료'}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '이벤트 관리',
|
||||
cancelText: '닫기',
|
||||
onConfirm: () => {
|
||||
NavManager.navigate('21', '실시간대시보드');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 이벤트 관리
|
||||
function showEventManagement() {
|
||||
const allEvents = SampleData.events;
|
||||
|
||||
const eventListHTML = allEvents.map(event => {
|
||||
const statusText = event.status === 'in_progress' ? '진행 중' : '완료';
|
||||
const statusClass = event.status === 'in_progress' ? 'badge-success' : 'badge-gray';
|
||||
|
||||
return `
|
||||
<div class="flex items-center justify-between p-m" style="border-bottom: 1px solid var(--color-gray-200);">
|
||||
<div>
|
||||
<p class="body-m text-semibold">${event.name}</p>
|
||||
<p class="body-s text-gray-600">${event.startDate}</p>
|
||||
</div>
|
||||
<span class="badge ${statusClass}">${statusText}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
UIManager.showBottomSheet({
|
||||
content: `
|
||||
<h2 class="h2 mb-l">이벤트 관리</h2>
|
||||
<div class="mb-l">
|
||||
${eventListHTML}
|
||||
</div>
|
||||
<button class="btn btn-primary btn-full" onclick="UIManager.closeBottomSheet(); NavManager.navigate('03', '이벤트목적선택');">
|
||||
새 이벤트 만들기
|
||||
</button>
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
// 도움말
|
||||
function showHelp() {
|
||||
UIManager.showModal({
|
||||
title: 'KT 이벤트 도우미 사용 가이드',
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div>
|
||||
<h3 class="body-l text-semibold mb-xs">1. 새 이벤트 만들기</h3>
|
||||
<p class="body-s text-gray-700">AI가 자동으로 이벤트 기획안을 생성해드립니다.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="body-l text-semibold mb-xs">2. 이벤트 관리</h3>
|
||||
<p class="body-s text-gray-700">진행 중이거나 완료된 이벤트를 확인하세요.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="body-l text-semibold mb-xs">3. 통계 보기</h3>
|
||||
<p class="body-s text-gray-700">실시간 참여 현황과 성과를 분석합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('home');
|
||||
|
||||
// 사용자 정보 로드
|
||||
loadUserInfo();
|
||||
|
||||
// 진행 중인 이벤트 렌더링
|
||||
renderActiveEvents();
|
||||
|
||||
// 전체보기 버튼
|
||||
document.getElementById('viewAllEvents').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
showEventManagement();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,407 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">회원가입</h1>
|
||||
<div style="width: 44px;"></div> <!-- Spacer for centering -->
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page" style="padding-bottom: 24px;">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Welcome Section -->
|
||||
<section class="text-center mb-xl">
|
||||
<span class="material-icons text-primary" style="font-size: 60px;">store</span>
|
||||
<h2 class="h1 mt-m">KT 이벤트 도우미</h2>
|
||||
<p class="body-l text-gray-700 mt-s">환영합니다!</p>
|
||||
</section>
|
||||
|
||||
<!-- Sign Up Form -->
|
||||
<form id="signUpForm" novalidate>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="input-group">
|
||||
<label for="email">이메일 *</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
class="input"
|
||||
placeholder="example@email.com"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">올바른 이메일 형식이 아닙니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="input-group">
|
||||
<label for="password">비밀번호 *</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="input"
|
||||
placeholder="8자 이상, 영문과 숫자 조합"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">비밀번호는 8자 이상, 영문과 숫자 조합이어야 합니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- Password Confirm -->
|
||||
<div class="input-group">
|
||||
<label for="passwordConfirm">비밀번호 확인 *</label>
|
||||
<input
|
||||
type="password"
|
||||
id="passwordConfirm"
|
||||
class="input"
|
||||
placeholder="비밀번호를 다시 입력하세요"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">비밀번호가 일치하지 않습니다.</span>
|
||||
</div>
|
||||
|
||||
<div class="divider my-l"></div>
|
||||
|
||||
<!-- Business Name -->
|
||||
<div class="input-group">
|
||||
<label for="businessName">상호명 *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="businessName"
|
||||
class="input"
|
||||
placeholder="예: 우진이네 고깃집"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">상호명을 입력해주세요.</span>
|
||||
</div>
|
||||
|
||||
<!-- Business Type -->
|
||||
<div class="input-group">
|
||||
<label for="businessType">업종 *</label>
|
||||
<select
|
||||
id="businessType"
|
||||
class="input select"
|
||||
required
|
||||
>
|
||||
<option value="">업종 선택</option>
|
||||
<option value="한식당">한식당</option>
|
||||
<option value="중식당">중식당</option>
|
||||
<option value="일식당">일식당</option>
|
||||
<option value="양식당">양식당</option>
|
||||
<option value="카페/디저트">카페/디저트</option>
|
||||
<option value="베이커리">베이커리</option>
|
||||
<option value="의류/잡화">의류/잡화</option>
|
||||
<option value="화장품/미용">화장품/미용</option>
|
||||
<option value="스포츠/레저">스포츠/레저</option>
|
||||
<option value="기타">기타</option>
|
||||
</select>
|
||||
<span class="input-error">업종을 선택해주세요.</span>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="input-group">
|
||||
<label for="location">위치 *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="location"
|
||||
class="input"
|
||||
placeholder="예: 서울 강남구"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">위치를 입력해주세요.</span>
|
||||
</div>
|
||||
|
||||
<!-- Phone (Optional) -->
|
||||
<div class="input-group">
|
||||
<label for="phone">전화번호</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
class="input"
|
||||
placeholder="010-1234-5678"
|
||||
/>
|
||||
<span class="input-error">올바른 전화번호 형식이 아닙니다.</span>
|
||||
</div>
|
||||
|
||||
<div class="divider my-l"></div>
|
||||
|
||||
<!-- Terms Agreements -->
|
||||
<div class="mb-l">
|
||||
<label class="checkbox mb-m">
|
||||
<input type="checkbox" id="agreeAll" />
|
||||
<span class="body-m text-semibold">전체 동의</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox mb-s">
|
||||
<input type="checkbox" id="agreeTerms" required />
|
||||
<span class="body-m">서비스 이용약관 동의 (필수)</span>
|
||||
<button type="button" class="btn-text body-s ml-xs" onclick="showTerms('terms')">보기</button>
|
||||
</label>
|
||||
|
||||
<label class="checkbox mb-s">
|
||||
<input type="checkbox" id="agreePrivacy" required />
|
||||
<span class="body-m">개인정보 처리방침 (필수)</span>
|
||||
<button type="button" class="btn-text body-s ml-xs" onclick="showTerms('privacy')">보기</button>
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="agreeMarketing" />
|
||||
<span class="body-m text-gray-700">마케팅 정보 수신 동의 (선택)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="btn btn-primary btn-full">
|
||||
가입하기
|
||||
</button>
|
||||
|
||||
<!-- Login Link -->
|
||||
<p class="text-center mt-m">
|
||||
<span class="body-m text-gray-700">이미 계정이 있으신가요?</span>
|
||||
<a href="#" class="body-m text-secondary text-semibold ml-xs" onclick="showLogin(event)">로그인</a>
|
||||
</p>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// 전체 동의 체크박스
|
||||
const agreeAll = document.getElementById('agreeAll');
|
||||
const agreeTerms = document.getElementById('agreeTerms');
|
||||
const agreePrivacy = document.getElementById('agreePrivacy');
|
||||
const agreeMarketing = document.getElementById('agreeMarketing');
|
||||
|
||||
agreeAll.addEventListener('change', (e) => {
|
||||
const checked = e.target.checked;
|
||||
agreeTerms.checked = checked;
|
||||
agreePrivacy.checked = checked;
|
||||
agreeMarketing.checked = checked;
|
||||
});
|
||||
|
||||
// 개별 체크박스 변경 시 전체 동의 상태 업데이트
|
||||
[agreeTerms, agreePrivacy, agreeMarketing].forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
agreeAll.checked = agreeTerms.checked && agreePrivacy.checked && agreeMarketing.checked;
|
||||
});
|
||||
});
|
||||
|
||||
// Form Submit
|
||||
const form = document.getElementById('signUpForm');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Form validation
|
||||
if (!validateSignUpForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loading 표시
|
||||
UIManager.showLoading('회원가입 처리 중...');
|
||||
|
||||
// API 호출 시뮬레이션 (1.5초 대기)
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// 사용자 정보 저장
|
||||
const userData = {
|
||||
email: document.getElementById('email').value,
|
||||
name: document.getElementById('businessName').value.replace(/이네|네/, '').trim() || '사장님',
|
||||
businessName: document.getElementById('businessName').value,
|
||||
businessType: document.getElementById('businessType').value,
|
||||
location: document.getElementById('location').value,
|
||||
phone: document.getElementById('phone').value || ''
|
||||
};
|
||||
|
||||
AppState.saveUser(userData);
|
||||
|
||||
UIManager.hideLoading();
|
||||
|
||||
// 성공 메시지
|
||||
UIManager.showToast('회원가입이 완료되었습니다!', 'success');
|
||||
|
||||
// 메인 대시보드로 이동
|
||||
setTimeout(() => {
|
||||
NavManager.navigate('00', '메인대시보드');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
function validateSignUpForm() {
|
||||
let isValid = true;
|
||||
|
||||
// Clear all errors
|
||||
document.querySelectorAll('.input-group').forEach(group => {
|
||||
group.classList.remove('error');
|
||||
});
|
||||
|
||||
// Email
|
||||
const email = document.getElementById('email');
|
||||
if (!FormValidator.validateRequired(email.value)) {
|
||||
FormValidator.showError(email, '이메일을 입력해주세요.');
|
||||
isValid = false;
|
||||
} else if (!FormValidator.validateEmail(email.value)) {
|
||||
FormValidator.showError(email, '올바른 이메일 형식이 아닙니다.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Password
|
||||
const password = document.getElementById('password');
|
||||
if (!FormValidator.validateRequired(password.value)) {
|
||||
FormValidator.showError(password, '비밀번호를 입력해주세요.');
|
||||
isValid = false;
|
||||
} else if (!FormValidator.validatePassword(password.value)) {
|
||||
FormValidator.showError(password, '비밀번호는 8자 이상, 영문과 숫자 조합이어야 합니다.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Password Confirm
|
||||
const passwordConfirm = document.getElementById('passwordConfirm');
|
||||
if (!FormValidator.validateRequired(passwordConfirm.value)) {
|
||||
FormValidator.showError(passwordConfirm, '비밀번호 확인을 입력해주세요.');
|
||||
isValid = false;
|
||||
} else if (password.value !== passwordConfirm.value) {
|
||||
FormValidator.showError(passwordConfirm, '비밀번호가 일치하지 않습니다.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Business Name
|
||||
const businessName = document.getElementById('businessName');
|
||||
if (!FormValidator.validateRequired(businessName.value)) {
|
||||
FormValidator.showError(businessName, '상호명을 입력해주세요.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Business Type
|
||||
const businessType = document.getElementById('businessType');
|
||||
if (!FormValidator.validateRequired(businessType.value)) {
|
||||
FormValidator.showError(businessType, '업종을 선택해주세요.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Location
|
||||
const location = document.getElementById('location');
|
||||
if (!FormValidator.validateRequired(location.value)) {
|
||||
FormValidator.showError(location, '위치를 입력해주세요.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Phone (optional but validate if provided)
|
||||
const phone = document.getElementById('phone');
|
||||
if (phone.value && !FormValidator.validatePhone(phone.value)) {
|
||||
FormValidator.showError(phone, '올바른 전화번호 형식이 아닙니다. (예: 010-1234-5678)');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Terms Agreement
|
||||
if (!agreeTerms.checked) {
|
||||
UIManager.showToast('서비스 이용약관에 동의해주세요.', 'error');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!agreePrivacy.checked) {
|
||||
UIManager.showToast('개인정보 처리방침에 동의해주세요.', 'error');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// 약관 보기
|
||||
function showTerms(type) {
|
||||
const titles = {
|
||||
'terms': '서비스 이용약관',
|
||||
'privacy': '개인정보 처리방침'
|
||||
};
|
||||
|
||||
const contents = {
|
||||
'terms': `
|
||||
<div class="body-s text-gray-700" style="max-height: 300px; overflow-y: auto;">
|
||||
<p class="mb-m"><strong>제1조 (목적)</strong></p>
|
||||
<p class="mb-l">본 약관은 KT 이벤트 도우미 서비스의 이용 조건 및 절차에 관한 기본적인 사항을 규정함을 목적으로 합니다.</p>
|
||||
|
||||
<p class="mb-m"><strong>제2조 (용어의 정의)</strong></p>
|
||||
<p class="mb-l">1. "서비스"란 KT가 제공하는 AI 기반 이벤트 자동 생성 서비스를 의미합니다.<br/>
|
||||
2. "회원"이란 본 약관에 동의하고 서비스를 이용하는 자를 말합니다.</p>
|
||||
|
||||
<p class="mb-m"><strong>제3조 (서비스의 제공)</strong></p>
|
||||
<p>회사는 회원에게 다음과 같은 서비스를 제공합니다:<br/>
|
||||
1. AI 기반 이벤트 기획안 생성<br/>
|
||||
2. 이벤트 이미지 생성<br/>
|
||||
3. 이벤트 관리 및 분석</p>
|
||||
</div>
|
||||
`,
|
||||
'privacy': `
|
||||
<div class="body-s text-gray-700" style="max-height: 300px; overflow-y: auto;">
|
||||
<p class="mb-m"><strong>1. 개인정보의 수집 및 이용 목적</strong></p>
|
||||
<p class="mb-l">회사는 다음의 목적을 위하여 개인정보를 수집 및 이용합니다:<br/>
|
||||
- 회원 가입 및 관리<br/>
|
||||
- 서비스 제공 및 개선<br/>
|
||||
- 이벤트 운영 및 통계 분석</p>
|
||||
|
||||
<p class="mb-m"><strong>2. 수집하는 개인정보 항목</strong></p>
|
||||
<p class="mb-l">- 필수항목: 이메일, 비밀번호, 상호명, 업종, 위치<br/>
|
||||
- 선택항목: 전화번호</p>
|
||||
|
||||
<p class="mb-m"><strong>3. 개인정보의 보유 및 이용 기간</strong></p>
|
||||
<p>회사는 회원 탈퇴 시까지 개인정보를 보유하며, 탈퇴 후 즉시 파기합니다.</p>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
UIManager.showModal({
|
||||
title: titles[type],
|
||||
body: contents[type],
|
||||
confirmText: '확인',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// 로그인 (시뮬레이션)
|
||||
function showLogin(e) {
|
||||
e.preventDefault();
|
||||
|
||||
UIManager.showModal({
|
||||
title: '로그인',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">로그인 화면은 프로토타입에 포함되지 않습니다.</p>
|
||||
<p class="body-s text-gray-600">데모를 위해 메인 대시보드로 이동합니다.</p>
|
||||
`,
|
||||
confirmText: '메인으로',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
NavManager.navigate('00', '메인대시보드');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 입력 중 실시간 에러 제거
|
||||
document.querySelectorAll('input, select').forEach(input => {
|
||||
input.addEventListener('input', () => {
|
||||
FormValidator.clearError(input);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,291 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">이벤트 만들기</h1>
|
||||
<div style="width: 44px;"></div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Stepper -->
|
||||
<div class="stepper mb-xl">
|
||||
<div class="step active">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-label">목적</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-label">정보</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-label">기획</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-label">이미지</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">5</div>
|
||||
<div class="step-label">배포</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<section class="mb-xl">
|
||||
<h2 class="h2 mb-s">이벤트 목적을 선택하세요</h2>
|
||||
<p class="body-m text-gray-700">AI가 목적에 맞는 이벤트를 기획합니다</p>
|
||||
</section>
|
||||
|
||||
<!-- Purpose Selection Cards -->
|
||||
<section class="mb-xl">
|
||||
<div class="flex flex-col gap-m">
|
||||
|
||||
<!-- 신규 고객 유치 -->
|
||||
<div class="card card-clickable purpose-card" data-purpose="new-customer" onclick="selectPurpose('new-customer')">
|
||||
<div class="card-body flex items-center gap-m p-l">
|
||||
<div class="flex items-center justify-center" style="width: 60px; height: 60px; border-radius: 50%; background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 32px;">person_add</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">신규 고객 유치</h3>
|
||||
<p class="body-s text-gray-600">처음 방문하는 고객을 늘립니다</p>
|
||||
</div>
|
||||
<span class="material-icons text-gray-300 check-icon">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 재방문 유도 -->
|
||||
<div class="card card-clickable purpose-card" data-purpose="revisit" onclick="selectPurpose('revisit')">
|
||||
<div class="card-body flex items-center gap-m p-l">
|
||||
<div class="flex items-center justify-center" style="width: 60px; height: 60px; border-radius: 50%; background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 32px;">replay</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">재방문 유도</h3>
|
||||
<p class="body-s text-gray-600">기존 고객의 재방문을 늘립니다</p>
|
||||
</div>
|
||||
<span class="material-icons text-gray-300 check-icon">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 신메뉴 홍보 -->
|
||||
<div class="card card-clickable purpose-card" data-purpose="new-menu" onclick="selectPurpose('new-menu')">
|
||||
<div class="card-body flex items-center gap-m p-l">
|
||||
<div class="flex items-center justify-center" style="width: 60px; height: 60px; border-radius: 50%; background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 32px;">restaurant_menu</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">신메뉴 홍보</h3>
|
||||
<p class="body-s text-gray-600">새로운 메뉴를 알립니다</p>
|
||||
</div>
|
||||
<span class="material-icons text-gray-300 check-icon">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기타 -->
|
||||
<div class="card card-clickable purpose-card" data-purpose="other" onclick="selectPurpose('other')">
|
||||
<div class="card-body flex items-center gap-m p-l">
|
||||
<div class="flex items-center justify-center" style="width: 60px; height: 60px; border-radius: 50%; background-color: var(--color-gray-200);">
|
||||
<span class="material-icons text-gray-700" style="font-size: 32px;">more_horiz</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">기타</h3>
|
||||
<p class="body-s text-gray-600">직접 목적을 입력합니다</p>
|
||||
</div>
|
||||
<span class="material-icons text-gray-300 check-icon">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Next Button -->
|
||||
<section>
|
||||
<button id="nextButton" class="btn btn-primary btn-full" disabled onclick="goToNextStep()">
|
||||
다음
|
||||
</button>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
let selectedPurpose = null;
|
||||
|
||||
// Purpose 선택
|
||||
function selectPurpose(purpose) {
|
||||
selectedPurpose = purpose;
|
||||
|
||||
// 모든 카드 비활성화
|
||||
document.querySelectorAll('.purpose-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
card.style.borderColor = 'var(--color-gray-200)';
|
||||
card.querySelector('.check-icon').style.color = 'var(--color-gray-300)';
|
||||
});
|
||||
|
||||
// 선택한 카드 활성화
|
||||
const selectedCard = document.querySelector(`[data-purpose="${purpose}"]`);
|
||||
if (selectedCard) {
|
||||
selectedCard.classList.add('selected');
|
||||
selectedCard.style.borderColor = 'var(--color-primary)';
|
||||
selectedCard.style.borderWidth = '2px';
|
||||
selectedCard.querySelector('.check-icon').style.color = 'var(--color-primary)';
|
||||
}
|
||||
|
||||
// 다음 버튼 활성화
|
||||
const nextButton = document.getElementById('nextButton');
|
||||
nextButton.disabled = false;
|
||||
|
||||
// 데이터 저장
|
||||
AppState.save('eventPurpose', {
|
||||
purpose: purpose,
|
||||
label: selectedCard.querySelector('h3').textContent
|
||||
});
|
||||
}
|
||||
|
||||
// 다음 단계로 이동
|
||||
function goToNextStep() {
|
||||
if (!selectedPurpose) {
|
||||
UIManager.showToast('이벤트 목적을 선택해주세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 기타 선택 시 입력 받기
|
||||
if (selectedPurpose === 'other') {
|
||||
UIManager.showModal({
|
||||
title: '이벤트 목적 입력',
|
||||
body: `
|
||||
<div class="input-group">
|
||||
<label for="customPurpose">이벤트 목적을 입력하세요</label>
|
||||
<input type="text" id="customPurpose" class="input" placeholder="예: 신규 매장 오픈 홍보" />
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
const customPurpose = document.getElementById('customPurpose').value;
|
||||
if (!customPurpose || customPurpose.trim() === '') {
|
||||
UIManager.showToast('목적을 입력해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
AppState.save('eventPurpose', {
|
||||
purpose: 'other',
|
||||
label: customPurpose
|
||||
});
|
||||
|
||||
proceedToPlanning();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
proceedToPlanning();
|
||||
}
|
||||
}
|
||||
|
||||
// AI 기획 진행
|
||||
function proceedToPlanning() {
|
||||
// Loading 표시
|
||||
UIManager.showLoading('AI가 이벤트 기획안을 생성하고 있습니다...');
|
||||
|
||||
// Progress bar 업데이트 시뮬레이션
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
progress += 10;
|
||||
UIManager.updateProgress(progress);
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(progressInterval);
|
||||
|
||||
// AI 생성 완료
|
||||
setTimeout(() => {
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('기획안이 생성되었습니다!', 'success');
|
||||
|
||||
// 08-이벤트기획안승인으로 이동
|
||||
setTimeout(() => {
|
||||
NavManager.navigate('08', '이벤트기획안승인');
|
||||
}, 500);
|
||||
}, 500);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('event');
|
||||
|
||||
// 이전에 선택한 목적이 있으면 복원
|
||||
const savedPurpose = AppState.load('eventPurpose');
|
||||
if (savedPurpose && savedPurpose.purpose) {
|
||||
selectPurpose(savedPurpose.purpose);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Purpose Card Transition */
|
||||
.purpose-card {
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.purpose-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.purpose-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,330 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">이벤트 기획안</h1>
|
||||
<div style="width: 44px;"></div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Stepper -->
|
||||
<div class="stepper mb-xl">
|
||||
<div class="step completed">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-label">목적</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-label">정보</div>
|
||||
</div>
|
||||
<div class="step active">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-label">기획</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-label">이미지</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">5</div>
|
||||
<div class="step-label">배포</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Title Section -->
|
||||
<section class="mb-l">
|
||||
<div class="flex items-center gap-s mb-s">
|
||||
<span class="material-icons text-secondary" style="font-size: 28px;">auto_awesome</span>
|
||||
<h2 class="h2">AI가 생성한 기획안</h2>
|
||||
</div>
|
||||
<p class="body-m text-gray-700">내용을 확인하고 승인해주세요</p>
|
||||
</section>
|
||||
|
||||
<!-- Event Plan Card -->
|
||||
<section class="mb-xl">
|
||||
<div class="card">
|
||||
<div class="card-body p-l">
|
||||
|
||||
<!-- Event Title -->
|
||||
<div class="mb-l">
|
||||
<label class="body-s text-gray-600 mb-xs block">이벤트 제목</label>
|
||||
<h3 class="h2" id="eventTitle">우진이네 여름 특별 할인 이벤트</h3>
|
||||
</div>
|
||||
|
||||
<div class="divider mb-l"></div>
|
||||
|
||||
<!-- Event Description -->
|
||||
<div class="mb-l">
|
||||
<label class="body-s text-gray-600 mb-xs block">이벤트 내용</label>
|
||||
<p class="body-l text-gray-900" id="eventDescription" style="line-height: 1.6;">
|
||||
여름 시즌 신규 고객 유치를 위한 20% 할인 이벤트입니다. 점심 시간대 직장인을 타겟으로 하며, SNS 공유 시 추가 쿠폰을 제공합니다. 시원한 여름 메뉴와 함께 특별한 혜택을 경험하세요!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="divider mb-l"></div>
|
||||
|
||||
<!-- Event Details -->
|
||||
<div class="flex flex-col gap-m">
|
||||
|
||||
<!-- Target -->
|
||||
<div class="flex items-center gap-m">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">group</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">타겟 고객</div>
|
||||
<div class="body-l text-semibold" id="eventTarget">20-30대 직장인</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Period -->
|
||||
<div class="flex items-center gap-m">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">calendar_month</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">이벤트 기간</div>
|
||||
<div class="body-l text-semibold" id="eventPeriod">2025.06.01 ~ 2025.06.30 (30일)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discount -->
|
||||
<div class="flex items-center gap-m">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">local_offer</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">할인 혜택</div>
|
||||
<div class="body-l text-semibold" id="eventDiscount">전 메뉴 20% 할인</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Benefit -->
|
||||
<div class="flex items-center gap-m">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-warning-light);">
|
||||
<span class="material-icons text-warning" style="font-size: 20px;">card_giftcard</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">추가 혜택</div>
|
||||
<div class="body-l text-semibold" id="eventBenefit">SNS 공유 시 10% 쿠폰 추가 증정</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AI Tips -->
|
||||
<section class="mb-xl">
|
||||
<div class="card bg-secondary-light" style="border: none;">
|
||||
<div class="card-body flex gap-m">
|
||||
<span class="material-icons text-secondary" style="font-size: 28px;">tips_and_updates</span>
|
||||
<div>
|
||||
<h3 class="body-l text-semibold mb-xs text-secondary">AI 제안</h3>
|
||||
<p class="body-m text-gray-700">타겟 고객층에 맞춰 점심 시간대(11:00-14:00)에 집중 홍보하면 더 좋은 효과를 얻을 수 있습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<section>
|
||||
<div class="flex gap-m">
|
||||
<button class="btn btn-secondary flex-1" onclick="showEditModal()">
|
||||
<span class="material-icons mr-xs" style="font-size: 20px;">edit</span>
|
||||
수정
|
||||
</button>
|
||||
<button class="btn btn-primary flex-1" onclick="approveAndContinue()">
|
||||
<span class="material-icons mr-xs" style="font-size: 20px;">check_circle</span>
|
||||
승인
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// 이벤트 기획안 데이터
|
||||
const eventPlan = {
|
||||
title: '우진이네 여름 특별 할인 이벤트',
|
||||
description: '여름 시즌 신규 고객 유치를 위한 20% 할인 이벤트입니다. 점심 시간대 직장인을 타겟으로 하며, SNS 공유 시 추가 쿠폰을 제공합니다. 시원한 여름 메뉴와 함께 특별한 혜택을 경험하세요!',
|
||||
target: '20-30대 직장인',
|
||||
startDate: '2025.06.01',
|
||||
endDate: '2025.06.30',
|
||||
duration: 30,
|
||||
discount: '전 메뉴 20% 할인',
|
||||
benefit: 'SNS 공유 시 10% 쿠폰 추가 증정'
|
||||
};
|
||||
|
||||
// 페이지 데이터 로드
|
||||
function loadEventPlan() {
|
||||
document.getElementById('eventTitle').textContent = eventPlan.title;
|
||||
document.getElementById('eventDescription').textContent = eventPlan.description;
|
||||
document.getElementById('eventTarget').textContent = eventPlan.target;
|
||||
document.getElementById('eventPeriod').textContent =
|
||||
`${eventPlan.startDate} ~ ${eventPlan.endDate} (${eventPlan.duration}일)`;
|
||||
document.getElementById('eventDiscount').textContent = eventPlan.discount;
|
||||
document.getElementById('eventBenefit').textContent = eventPlan.benefit;
|
||||
}
|
||||
|
||||
// 수정 모달 표시
|
||||
function showEditModal() {
|
||||
UIManager.showModal({
|
||||
title: '기획안 수정',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
기획안을 수정하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-info-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-s text-gray-700">
|
||||
💡 프로토타입에서는 수정 기능이 제한됩니다.
|
||||
실제 서비스에서는 AI에게 수정 요청을 할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: 'AI에게 수정 요청',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
showAIEditRequest();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// AI 수정 요청 (시뮬레이션)
|
||||
function showAIEditRequest() {
|
||||
UIManager.showModal({
|
||||
title: 'AI에게 수정 요청',
|
||||
body: `
|
||||
<div class="input-group">
|
||||
<label for="editRequest">수정 요청 내용을 입력하세요</label>
|
||||
<textarea
|
||||
id="editRequest"
|
||||
class="input textarea"
|
||||
placeholder="예: 할인율을 30%로 높여주세요"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 mt-s">
|
||||
AI가 요청사항을 반영하여 기획안을 다시 생성합니다.
|
||||
</p>
|
||||
`,
|
||||
confirmText: '수정 요청',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
const request = document.getElementById('editRequest').value;
|
||||
if (!request || request.trim() === '') {
|
||||
UIManager.showToast('수정 요청 내용을 입력해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// AI 재생성 시뮬레이션
|
||||
UIManager.showLoading('AI가 기획안을 수정하고 있습니다...');
|
||||
|
||||
setTimeout(() => {
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('기획안이 수정되었습니다!', 'success');
|
||||
|
||||
// 실제로는 수정된 내용 반영
|
||||
// 프로토타입에서는 같은 내용 유지
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 승인 및 다음 단계
|
||||
function approveAndContinue() {
|
||||
UIManager.showModal({
|
||||
title: '이벤트 기획안 승인',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
기획안을 승인하고 다음 단계로 진행하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-success-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<h4 class="body-m text-semibold mb-xs text-success">승인 후 진행사항</h4>
|
||||
<ul class="body-s text-gray-700" style="padding-left: 20px; line-height: 1.8;">
|
||||
<li>AI가 이벤트 이미지를 생성합니다</li>
|
||||
<li>다양한 채널에 배포할 수 있습니다</li>
|
||||
<li>실시간으로 성과를 확인할 수 있습니다</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '승인하고 계속',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
// 기획안 데이터 저장
|
||||
AppState.save('approvedEventPlan', eventPlan);
|
||||
|
||||
// 성공 메시지
|
||||
UIManager.showToast('기획안이 승인되었습니다!', 'success');
|
||||
|
||||
// 다음 단계로 이동 (09-AI이미지생성)
|
||||
setTimeout(() => {
|
||||
NavManager.navigate('09', 'AI이미지생성');
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('event');
|
||||
|
||||
// 이벤트 기획안 로드
|
||||
loadEventPlan();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,401 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
|
||||
<style>
|
||||
.image-card {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 3px solid transparent;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.image-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.image-card.selected {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
border-radius: var(--radius-medium);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-placeholder::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y="50" font-size="40" fill="white" opacity="0.2">🍖</text></svg>') center/cover;
|
||||
}
|
||||
|
||||
.check-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.image-card.selected .check-badge {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.regenerating {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">AI 이미지 생성</h1>
|
||||
<div style="width: 44px;"></div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Stepper -->
|
||||
<div class="stepper mb-xl">
|
||||
<div class="step completed">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-label">목적</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-label">정보</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-label">기획</div>
|
||||
</div>
|
||||
<div class="step active">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-label">이미지</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">5</div>
|
||||
<div class="step-label">배포</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Title Section -->
|
||||
<section class="mb-l">
|
||||
<div class="flex items-center gap-s mb-s">
|
||||
<span class="material-icons text-secondary" style="font-size: 28px;">image</span>
|
||||
<h2 class="h2">AI가 생성한 이미지</h2>
|
||||
</div>
|
||||
<p class="body-m text-gray-700">마음에 드는 이미지를 선택하세요 (복수 선택 가능)</p>
|
||||
</section>
|
||||
|
||||
<!-- Images Grid -->
|
||||
<section class="mb-l" id="imagesContainer">
|
||||
<div class="grid gap-m">
|
||||
|
||||
<!-- Image 1 -->
|
||||
<div class="col-4 image-card selected card" data-image-id="1" onclick="toggleImageSelection(1)">
|
||||
<div class="image-placeholder">
|
||||
<div style="position: relative; z-index: 1; text-align: center;">
|
||||
<div style="font-size: 48px; margin-bottom: 8px;">🔥</div>
|
||||
<div style="font-size: 16px; font-weight: 600;">여름 특별 이벤트</div>
|
||||
<div style="font-size: 14px; opacity: 0.9;">20% 할인</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="check-badge">
|
||||
<span class="material-icons text-primary">check</span>
|
||||
</div>
|
||||
<div class="card-body p-m">
|
||||
<p class="body-s text-gray-700">스타일: 모던, 밝은 느낌</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image 2 -->
|
||||
<div class="col-4 image-card card" data-image-id="2" onclick="toggleImageSelection(2)">
|
||||
<div class="image-placeholder" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<div style="position: relative; z-index: 1; text-align: center;">
|
||||
<div style="font-size: 48px; margin-bottom: 8px;">🍖</div>
|
||||
<div style="font-size: 16px; font-weight: 600;">우진이네 고깃집</div>
|
||||
<div style="font-size: 14px; opacity: 0.9;">여름 시즌 특가</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="check-badge">
|
||||
<span class="material-icons text-primary">check</span>
|
||||
</div>
|
||||
<div class="card-body p-m">
|
||||
<p class="body-s text-gray-700">스타일: 따뜻한 색감, 식욕 자극</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image 3 -->
|
||||
<div class="col-4 image-card card" data-image-id="3" onclick="toggleImageSelection(3)">
|
||||
<div class="image-placeholder" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||
<div style="position: relative; z-index: 1; text-align: center;">
|
||||
<div style="font-size: 48px; margin-bottom: 8px;">❄️</div>
|
||||
<div style="font-size: 16px; font-weight: 600;">시원한 여름</div>
|
||||
<div style="font-size: 14px; opacity: 0.9;">20% OFF</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="check-badge">
|
||||
<span class="material-icons text-primary">check</span>
|
||||
</div>
|
||||
<div class="card-body p-m">
|
||||
<p class="body-s text-gray-700">스타일: 시원한 느낌, 여름 테마</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Regenerate Button -->
|
||||
<section class="mb-l">
|
||||
<button class="btn btn-text btn-full" onclick="regenerateImages()">
|
||||
<span class="material-icons mr-xs">refresh</span>
|
||||
다른 이미지 생성
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Tips -->
|
||||
<section class="mb-xl">
|
||||
<div class="card bg-info-light" style="border: none;">
|
||||
<div class="card-body flex gap-m">
|
||||
<span class="material-icons text-info" style="font-size: 24px;">info</span>
|
||||
<div>
|
||||
<p class="body-s text-gray-700">
|
||||
💡 여러 이미지를 선택하여 다양한 채널에 활용할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Complete Button -->
|
||||
<section>
|
||||
<button id="completeButton" class="btn btn-primary btn-full" onclick="completeImageSelection()">
|
||||
선택 완료
|
||||
</button>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// 선택된 이미지 ID 목록
|
||||
let selectedImages = [1]; // 기본으로 첫 번째 이미지 선택
|
||||
|
||||
// 이미지 선택 토글
|
||||
function toggleImageSelection(imageId) {
|
||||
const card = document.querySelector(`[data-image-id="${imageId}"]`);
|
||||
|
||||
if (selectedImages.includes(imageId)) {
|
||||
// 선택 해제
|
||||
selectedImages = selectedImages.filter(id => id !== imageId);
|
||||
card.classList.remove('selected');
|
||||
} else {
|
||||
// 선택
|
||||
selectedImages.push(imageId);
|
||||
card.classList.add('selected');
|
||||
}
|
||||
|
||||
// 완료 버튼 활성화/비활성화
|
||||
updateCompleteButton();
|
||||
|
||||
// 데이터 저장
|
||||
AppState.save('selectedImages', selectedImages);
|
||||
}
|
||||
|
||||
// 완료 버튼 상태 업데이트
|
||||
function updateCompleteButton() {
|
||||
const button = document.getElementById('completeButton');
|
||||
if (selectedImages.length > 0) {
|
||||
button.disabled = false;
|
||||
button.innerHTML = `선택 완료 (${selectedImages.length}개)`;
|
||||
} else {
|
||||
button.disabled = true;
|
||||
button.innerHTML = '이미지를 선택하세요';
|
||||
}
|
||||
}
|
||||
|
||||
// 이미지 재생성
|
||||
function regenerateImages() {
|
||||
UIManager.showModal({
|
||||
title: '이미지 다시 생성',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
새로운 이미지를 생성하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-warning-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-s text-gray-700">
|
||||
⚠️ 현재 선택한 이미지는 유지됩니다.
|
||||
새로 생성된 이미지 중에서 추가로 선택할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '새로 생성',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
// AI 이미지 생성 시뮬레이션
|
||||
UIManager.showLoading('AI가 새로운 이미지를 생성하고 있습니다...');
|
||||
|
||||
const container = document.getElementById('imagesContainer');
|
||||
container.classList.add('regenerating');
|
||||
|
||||
// Progress 업데이트
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
progress += 15;
|
||||
UIManager.updateProgress(progress);
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(progressInterval);
|
||||
|
||||
setTimeout(() => {
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('새로운 이미지가 생성되었습니다!', 'success');
|
||||
container.classList.remove('regenerating');
|
||||
|
||||
// 실제로는 새 이미지로 교체
|
||||
// 프로토타입에서는 같은 이미지 유지
|
||||
}, 500);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 선택 완료
|
||||
function completeImageSelection() {
|
||||
if (selectedImages.length === 0) {
|
||||
UIManager.showToast('이미지를 선택해주세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 이미지 데이터 저장
|
||||
AppState.save('selectedImages', selectedImages);
|
||||
|
||||
UIManager.showModal({
|
||||
title: '이미지 선택 완료',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
${selectedImages.length}개의 이미지를 선택하셨습니다.
|
||||
</p>
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
다음 단계에서 이벤트를 배포할 채널을 선택합니다.
|
||||
</p>
|
||||
<div class="card bg-success-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<h4 class="body-m text-semibold mb-xs text-success">다음 단계</h4>
|
||||
<ul class="body-s text-gray-700" style="padding-left: 20px; line-height: 1.8;">
|
||||
<li>배포 채널 선택 (SNS, 문자, 이메일 등)</li>
|
||||
<li>일정 설정</li>
|
||||
<li>최종 검토 및 배포</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '다음 단계로',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
UIManager.showToast('이미지 선택이 완료되었습니다!', 'success');
|
||||
|
||||
// 다음 단계로 이동 (15-다중채널배포설정)
|
||||
setTimeout(() => {
|
||||
NavManager.navigate('15', '다중채널배포설정');
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('event');
|
||||
|
||||
// 이전에 선택한 이미지 복원
|
||||
const savedImages = AppState.load('selectedImages');
|
||||
if (savedImages && savedImages.length > 0) {
|
||||
selectedImages = savedImages;
|
||||
|
||||
// UI 업데이트
|
||||
selectedImages.forEach(imageId => {
|
||||
const card = document.querySelector(`[data-image-id="${imageId}"]`);
|
||||
if (card) {
|
||||
card.classList.add('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 완료 버튼 상태 업데이트
|
||||
updateCompleteButton();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,681 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
as="style"
|
||||
crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"
|
||||
/>
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">배포 설정</h1>
|
||||
<div style="width: 44px"></div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
<!-- Stepper -->
|
||||
<div class="stepper mb-xl">
|
||||
<div class="step completed">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-label">목적</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-label">정보</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-label">기획</div>
|
||||
</div>
|
||||
<div class="step completed">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-label">이미지</div>
|
||||
</div>
|
||||
<div class="step active">
|
||||
<div class="step-number">5</div>
|
||||
<div class="step-label">배포</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Selection Section -->
|
||||
<section class="mb-xl">
|
||||
<h2 class="h3 mb-m">배포 채널 선택</h2>
|
||||
<p class="body-m text-gray-700 mb-l">
|
||||
이벤트를 배포할 채널을 선택하세요 (복수 선택 가능)
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col gap-m">
|
||||
<!-- 우리동네TV -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="channelUriDongneTV"
|
||||
value="uridongnetv"
|
||||
checked
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-primary"
|
||||
style="font-size: 24px"
|
||||
>tv</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">우리동네TV</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
지니 TV 기반 로컬 마케팅 플랫폼
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- 링고비즈 -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="channelRingoBiz"
|
||||
value="ringobiz"
|
||||
checked
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-secondary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-secondary"
|
||||
style="font-size: 24px"
|
||||
>phone_in_talk</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">링고비즈</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
특별한 통화연결음으로 매장 홍보
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- genie TV -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="channelGenieTV"
|
||||
value="genietv"
|
||||
checked
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-warning-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-warning"
|
||||
style="font-size: 24px"
|
||||
>live_tv</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">genie TV</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
다양한 콘텐츠를 즐기는 미디어 포털
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- 하이오더 -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="channelHighOrder"
|
||||
value="highorder"
|
||||
checked
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-success-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-success"
|
||||
style="font-size: 24px"
|
||||
>restaurant_menu</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">하이오더</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
테이블에서 주문과 결제하는 서비스
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- SNS -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="checkbox" id="channelSNS" value="sns" checked />
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-primary"
|
||||
style="font-size: 24px"
|
||||
>share</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">SNS</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
Instagram, Facebook, 네이버 플레이스
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- SMS -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="checkbox" id="channelSMS" value="sms" checked />
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-secondary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-secondary"
|
||||
style="font-size: 24px"
|
||||
>sms</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">문자 메시지</h3>
|
||||
<p class="body-s text-gray-600">기존 고객에게 SMS 발송</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Email -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="checkbox" id="channelEmail" value="email" />
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-success-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-success"
|
||||
style="font-size: 24px"
|
||||
>email</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">이메일</h3>
|
||||
<p class="body-s text-gray-600">뉴스레터 및 이메일 마케팅</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Offline -->
|
||||
<label class="checkbox card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="checkbox" id="channelOffline" value="offline" />
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-warning-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-warning"
|
||||
style="font-size: 24px"
|
||||
>print</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">오프라인 인쇄물</h3>
|
||||
<p class="body-s text-gray-600">포스터, 전단지, POP</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Schedule Section -->
|
||||
<section class="mb-xl">
|
||||
<h2 class="h3 mb-m">배포 일정</h2>
|
||||
|
||||
<div class="flex flex-col gap-m">
|
||||
<!-- Immediate -->
|
||||
<label class="radio card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="radio"
|
||||
name="schedule"
|
||||
id="scheduleImmediate"
|
||||
value="immediate"
|
||||
checked
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-primary"
|
||||
style="font-size: 24px"
|
||||
>bolt</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">즉시 배포</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
선택한 채널에 바로 배포합니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Scheduled -->
|
||||
<label class="radio card p-l" style="cursor: pointer">
|
||||
<div class="flex items-center gap-m">
|
||||
<input
|
||||
type="radio"
|
||||
name="schedule"
|
||||
id="scheduleReserved"
|
||||
value="reserved"
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-center"
|
||||
style="
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-secondary-light);
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="material-icons text-secondary"
|
||||
style="font-size: 24px"
|
||||
>schedule</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="body-l text-semibold mb-xs">예약 배포</h3>
|
||||
<p class="body-s text-gray-600">
|
||||
원하는 날짜와 시간에 배포합니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Date/Time Picker (hidden by default) -->
|
||||
<div
|
||||
id="dateTimeSection"
|
||||
class="card p-l"
|
||||
style="display: none; border-color: var(--color-secondary)"
|
||||
>
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="input-group" style="margin-bottom: 0">
|
||||
<label for="scheduleDate">배포 날짜</label>
|
||||
<input type="date" id="scheduleDate" class="input" />
|
||||
</div>
|
||||
<div class="input-group" style="margin-bottom: 0">
|
||||
<label for="scheduleTime">배포 시간</label>
|
||||
<input
|
||||
type="time"
|
||||
id="scheduleTime"
|
||||
class="input"
|
||||
value="12:00"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Summary -->
|
||||
<section class="mb-xl">
|
||||
<div class="card bg-gray-100" style="border: none">
|
||||
<div class="card-body">
|
||||
<h3 class="body-l text-semibold mb-m">배포 요약</h3>
|
||||
<div class="flex flex-col gap-s body-m text-gray-700">
|
||||
<div class="flex items-center gap-xs">
|
||||
<span class="material-icons" style="font-size: 18px"
|
||||
>check_circle</span
|
||||
>
|
||||
<span id="summaryChannels"
|
||||
>우리동네TV, 링고비즈, genie TV, 하이오더, SNS, 문자 메시지
|
||||
(6개 채널)</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-xs">
|
||||
<span class="material-icons" style="font-size: 18px"
|
||||
>schedule</span
|
||||
>
|
||||
<span id="summarySchedule">즉시 배포</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-xs">
|
||||
<span class="material-icons" style="font-size: 18px"
|
||||
>image</span
|
||||
>
|
||||
<span id="summaryImages">선택된 이미지 1개</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Deploy Button -->
|
||||
<section>
|
||||
<button
|
||||
id="deployButton"
|
||||
class="btn btn-primary btn-full"
|
||||
onclick="startDeployment()"
|
||||
>
|
||||
<span class="material-icons mr-xs">rocket_launch</span>
|
||||
배포 시작
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// 채널 체크박스 변경 시 요약 업데이트
|
||||
document
|
||||
.querySelectorAll('input[type="checkbox"]')
|
||||
.forEach((checkbox) => {
|
||||
checkbox.addEventListener("change", updateSummary);
|
||||
});
|
||||
|
||||
// 일정 라디오 변경 시 날짜/시간 섹션 표시
|
||||
document.querySelectorAll('input[name="schedule"]').forEach((radio) => {
|
||||
radio.addEventListener("change", (e) => {
|
||||
const dateTimeSection = document.getElementById("dateTimeSection");
|
||||
if (e.target.value === "reserved") {
|
||||
dateTimeSection.style.display = "block";
|
||||
|
||||
// 기본 날짜 설정 (내일)
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
document.getElementById("scheduleDate").value =
|
||||
Utils.formatDateISO(tomorrow);
|
||||
} else {
|
||||
dateTimeSection.style.display = "none";
|
||||
}
|
||||
updateSummary();
|
||||
});
|
||||
});
|
||||
|
||||
// 요약 업데이트
|
||||
function updateSummary() {
|
||||
// 선택된 채널
|
||||
const channels = [];
|
||||
const channelLabels = {
|
||||
uridongnetv: "우리동네TV",
|
||||
ringobiz: "링고비즈",
|
||||
genietv: "genie TV",
|
||||
highorder: "하이오더",
|
||||
sns: "SNS",
|
||||
sms: "문자 메시지",
|
||||
email: "이메일",
|
||||
offline: "오프라인 인쇄물",
|
||||
};
|
||||
|
||||
document
|
||||
.querySelectorAll('input[type="checkbox"]:checked')
|
||||
.forEach((checkbox) => {
|
||||
channels.push(channelLabels[checkbox.value]);
|
||||
});
|
||||
|
||||
document.getElementById("summaryChannels").textContent =
|
||||
channels.length > 0
|
||||
? `${channels.join(", ")} (${channels.length}개 채널)`
|
||||
: "선택된 채널 없음";
|
||||
|
||||
// 선택된 일정
|
||||
const scheduleType = document.querySelector(
|
||||
'input[name="schedule"]:checked'
|
||||
).value;
|
||||
let scheduleText = "즉시 배포";
|
||||
|
||||
if (scheduleType === "reserved") {
|
||||
const date = document.getElementById("scheduleDate").value;
|
||||
const time = document.getElementById("scheduleTime").value;
|
||||
if (date) {
|
||||
const dateObj = new Date(date);
|
||||
scheduleText = `${Utils.formatDate(dateObj)} ${time}`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("summarySchedule").textContent = scheduleText;
|
||||
|
||||
// 선택된 이미지 개수
|
||||
const selectedImages = AppState.load("selectedImages") || [1];
|
||||
document.getElementById(
|
||||
"summaryImages"
|
||||
).textContent = `선택된 이미지 ${selectedImages.length}개`;
|
||||
|
||||
// 배포 버튼 활성화/비활성화
|
||||
const deployButton = document.getElementById("deployButton");
|
||||
deployButton.disabled = channels.length === 0;
|
||||
}
|
||||
|
||||
// 배포 시작
|
||||
function startDeployment() {
|
||||
const channels = [];
|
||||
document
|
||||
.querySelectorAll('input[type="checkbox"]:checked')
|
||||
.forEach((checkbox) => {
|
||||
channels.push(checkbox.value);
|
||||
});
|
||||
|
||||
if (channels.length === 0) {
|
||||
UIManager.showToast("배포 채널을 선택해주세요.", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
const scheduleType = document.querySelector(
|
||||
'input[name="schedule"]:checked'
|
||||
).value;
|
||||
let scheduleInfo = "즉시 배포";
|
||||
|
||||
if (scheduleType === "reserved") {
|
||||
const date = document.getElementById("scheduleDate").value;
|
||||
const time = document.getElementById("scheduleTime").value;
|
||||
|
||||
if (!date) {
|
||||
UIManager.showToast("배포 날짜를 선택해주세요.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const dateObj = new Date(date);
|
||||
scheduleInfo = `${Utils.formatDate(dateObj)} ${time}`;
|
||||
}
|
||||
|
||||
UIManager.showModal({
|
||||
title: "이벤트 배포 확인",
|
||||
body: `
|
||||
<p class="body-m text-gray-900 mb-m">
|
||||
이벤트를 배포하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-primary-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<div class="flex flex-col gap-s body-m text-gray-900">
|
||||
<div><strong>채널:</strong> ${channels.length}개</div>
|
||||
<div><strong>일정:</strong> ${scheduleInfo}</div>
|
||||
<div><strong>이미지:</strong> ${
|
||||
(AppState.load("selectedImages") || [1]).length
|
||||
}개</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 mt-m">
|
||||
배포 후에는 실시간 대시보드에서 성과를 확인할 수 있습니다.
|
||||
</p>
|
||||
`,
|
||||
confirmText: "배포 시작",
|
||||
cancelText: "취소",
|
||||
onConfirm: () => {
|
||||
proceedDeployment(channels, scheduleType, scheduleInfo);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 배포 진행
|
||||
function proceedDeployment(channels, scheduleType, scheduleInfo) {
|
||||
UIManager.showLoading("이벤트를 배포하고 있습니다...");
|
||||
|
||||
// Progress 업데이트
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
progress += 20;
|
||||
UIManager.updateProgress(progress);
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(progressInterval);
|
||||
|
||||
// 배포 데이터 저장
|
||||
AppState.save("deployedEvent", {
|
||||
channels: channels,
|
||||
scheduleType: scheduleType,
|
||||
scheduleInfo: scheduleInfo,
|
||||
deployedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
UIManager.hideLoading();
|
||||
|
||||
UIManager.showModal({
|
||||
title: "배포 완료!",
|
||||
body: `
|
||||
<div class="text-center mb-m">
|
||||
<span class="material-icons text-success" style="font-size: 64px;">check_circle</span>
|
||||
</div>
|
||||
<p class="body-l text-center text-gray-900 mb-m">
|
||||
이벤트가 성공적으로 배포되었습니다!
|
||||
</p>
|
||||
<div class="card bg-success-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-m text-gray-700">
|
||||
실시간 대시보드에서 이벤트 성과를 확인하세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: "대시보드 보기",
|
||||
cancelText: "닫기",
|
||||
onConfirm: () => {
|
||||
NavManager.navigate("21", "실시간대시보드");
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav("event");
|
||||
|
||||
// 초기 요약 업데이트
|
||||
updateSummary();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,381 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 참여 신청 - 우진이네 고깃집</title>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
|
||||
<style>
|
||||
.event-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.event-image::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
animation: backgroundMove 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes backgroundMove {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(20px, 20px); }
|
||||
}
|
||||
|
||||
.event-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-m);
|
||||
padding: var(--space-m);
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-medium);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-primary" style="font-size: 24px;">store</span>
|
||||
<span class="h3">우진이네 고깃집</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container (No Bottom Nav) -->
|
||||
<main class="page" style="padding-bottom: 24px;">
|
||||
|
||||
<!-- Event Image -->
|
||||
<div class="event-image">
|
||||
<div class="event-content">
|
||||
<div style="font-size: 64px; margin-bottom: 16px;">🔥</div>
|
||||
<h1 class="h1" style="color: white; margin-bottom: 8px;">여름 특별 할인</h1>
|
||||
<p class="body-l" style="color: white; opacity: 0.95;">전 메뉴 20% 할인!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Event Title -->
|
||||
<section class="mb-l">
|
||||
<h2 class="h2 mb-s">우진이네 여름 특별 할인 이벤트</h2>
|
||||
<p class="body-l text-gray-700">
|
||||
시원한 여름 메뉴와 함께 특별한 혜택을 경험하세요!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Benefits -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="h3 mb-m">이벤트 혜택</h3>
|
||||
|
||||
<div class="flex flex-col gap-m">
|
||||
|
||||
<!-- Period -->
|
||||
<div class="benefit-item">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">calendar_today</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">이벤트 기간</div>
|
||||
<div class="body-l text-semibold">2025.06.01 ~ 2025.06.30</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discount -->
|
||||
<div class="benefit-item">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">local_offer</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">할인 혜택</div>
|
||||
<div class="body-l text-semibold">전 메뉴 20% 할인</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Benefit -->
|
||||
<div class="benefit-item">
|
||||
<div class="flex items-center justify-center" style="width: 40px; height: 40px; border-radius: 50%; background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">card_giftcard</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-gray-600">추가 혜택</div>
|
||||
<div class="body-l text-semibold">SNS 공유 시 10% 쿠폰 추가</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Participation Form -->
|
||||
<section class="mb-xl">
|
||||
<h3 class="h3 mb-m">참여 신청</h3>
|
||||
|
||||
<form id="participationForm" novalidate>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="input-group">
|
||||
<label for="customerName">이름 *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="customerName"
|
||||
class="input"
|
||||
placeholder="이름을 입력하세요"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">이름을 입력해주세요.</span>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="input-group">
|
||||
<label for="customerPhone">전화번호 *</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="customerPhone"
|
||||
class="input"
|
||||
placeholder="010-1234-5678"
|
||||
required
|
||||
/>
|
||||
<span class="input-error">전화번호를 입력해주세요.</span>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Agreement -->
|
||||
<div class="mb-l">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="agreePrivacy" required />
|
||||
<span class="body-m">개인정보 수집 및 이용에 동의합니다 (필수)</span>
|
||||
</label>
|
||||
<button type="button" class="btn-text body-s mt-xs" onclick="showPrivacyPolicy()">
|
||||
개인정보 처리방침 보기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="btn btn-primary btn-full">
|
||||
<span class="material-icons mr-xs">check_circle</span>
|
||||
신청하기
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Info -->
|
||||
<section class="mb-l">
|
||||
<div class="card bg-info-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<h4 class="body-m text-semibold mb-xs text-info">유의사항</h4>
|
||||
<ul class="body-s text-gray-700" style="padding-left: 20px; line-height: 1.8;">
|
||||
<li>이벤트 참여는 선착순으로 마감됩니다</li>
|
||||
<li>중복 신청은 불가능합니다</li>
|
||||
<li>신청 후 문자로 쿠폰을 받으실 수 있습니다</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Share Section -->
|
||||
<section class="text-center">
|
||||
<p class="body-m text-gray-700 mb-m">이 이벤트를 친구에게 공유하세요!</p>
|
||||
<div class="flex gap-m justify-center">
|
||||
<button class="btn btn-secondary" onclick="shareEvent('kakao')">
|
||||
<span class="body-m">카카오톡</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="shareEvent('link')">
|
||||
<span class="material-icons" style="font-size: 20px;">link</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
const form = document.getElementById('participationForm');
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Validation
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loading
|
||||
UIManager.showLoading('참여 신청 처리 중...');
|
||||
|
||||
// API 호출 시뮬레이션
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// 신청 데이터
|
||||
const participationData = {
|
||||
name: document.getElementById('customerName').value,
|
||||
phone: document.getElementById('customerPhone').value,
|
||||
eventName: '우진이네 여름 특별 할인 이벤트',
|
||||
appliedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 저장 (프로토타입용)
|
||||
const participants = AppState.load('participants') || [];
|
||||
participants.push(participationData);
|
||||
AppState.save('participants', participants);
|
||||
|
||||
UIManager.hideLoading();
|
||||
|
||||
// 성공 모달
|
||||
UIManager.showModal({
|
||||
title: '신청 완료!',
|
||||
body: `
|
||||
<div class="text-center mb-m">
|
||||
<span class="material-icons text-success" style="font-size: 64px;">check_circle</span>
|
||||
</div>
|
||||
<p class="body-l text-center text-gray-900 mb-m">
|
||||
이벤트 참여 신청이 완료되었습니다!
|
||||
</p>
|
||||
<div class="card bg-success-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
<strong>${participationData.name}</strong>님
|
||||
</p>
|
||||
<p class="body-s text-gray-700">
|
||||
입력하신 전화번호로 쿠폰이 발송되었습니다.<br/>
|
||||
우진이네 고깃집에서 사용하실 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 text-center mt-m">
|
||||
SNS에 공유하시면 추가 10% 쿠폰을 드립니다!
|
||||
</p>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: null,
|
||||
onConfirm: () => {
|
||||
// 폼 초기화
|
||||
form.reset();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
|
||||
// Clear errors
|
||||
document.querySelectorAll('.input-group').forEach(group => {
|
||||
group.classList.remove('error');
|
||||
});
|
||||
|
||||
// Name
|
||||
const name = document.getElementById('customerName');
|
||||
if (!FormValidator.validateRequired(name.value)) {
|
||||
FormValidator.showError(name, '이름을 입력해주세요.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Phone
|
||||
const phone = document.getElementById('customerPhone');
|
||||
if (!FormValidator.validateRequired(phone.value)) {
|
||||
FormValidator.showError(phone, '전화번호를 입력해주세요.');
|
||||
isValid = false;
|
||||
} else if (!FormValidator.validatePhone(phone.value)) {
|
||||
FormValidator.showError(phone, '올바른 전화번호 형식이 아닙니다. (예: 010-1234-5678)');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Privacy Agreement
|
||||
const agreePrivacy = document.getElementById('agreePrivacy');
|
||||
if (!agreePrivacy.checked) {
|
||||
UIManager.showToast('개인정보 수집 및 이용에 동의해주세요.', 'error');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// 개인정보 처리방침
|
||||
function showPrivacyPolicy() {
|
||||
UIManager.showModal({
|
||||
title: '개인정보 처리방침',
|
||||
body: `
|
||||
<div class="body-s text-gray-700" style="max-height: 300px; overflow-y: auto; line-height: 1.8;">
|
||||
<p class="mb-m"><strong>1. 개인정보의 수집 및 이용 목적</strong></p>
|
||||
<p class="mb-l">
|
||||
우진이네 고깃집은 이벤트 참여자에게 쿠폰을 발송하기 위해 개인정보를 수집합니다.
|
||||
</p>
|
||||
|
||||
<p class="mb-m"><strong>2. 수집하는 개인정보 항목</strong></p>
|
||||
<p class="mb-l">
|
||||
- 필수항목: 이름, 전화번호<br/>
|
||||
- 수집 방법: 이벤트 참여 신청 폼
|
||||
</p>
|
||||
|
||||
<p class="mb-m"><strong>3. 개인정보의 보유 및 이용 기간</strong></p>
|
||||
<p class="mb-l">
|
||||
이벤트 종료 후 3개월까지 보관하며, 이후 즉시 파기합니다.
|
||||
</p>
|
||||
|
||||
<p class="mb-m"><strong>4. 개인정보 제공 거부 권리</strong></p>
|
||||
<p>
|
||||
귀하는 개인정보 제공을 거부할 권리가 있으나, 거부 시 이벤트 참여가 제한됩니다.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// 이벤트 공유
|
||||
function shareEvent(platform) {
|
||||
const eventUrl = window.location.href;
|
||||
const eventTitle = '우진이네 여름 특별 할인 이벤트';
|
||||
const eventDescription = '전 메뉴 20% 할인! 지금 바로 참여하세요!';
|
||||
|
||||
if (platform === 'kakao') {
|
||||
UIManager.showToast('카카오톡 공유 기능은 프로토타입에서 지원하지 않습니다.', 'info');
|
||||
} else if (platform === 'link') {
|
||||
// 링크 복사 (시뮬레이션)
|
||||
UIManager.showToast('링크가 복사되었습니다!', 'success');
|
||||
|
||||
// 실제로는 clipboard API 사용
|
||||
// navigator.clipboard.writeText(eventUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 실시간 에러 제거
|
||||
document.querySelectorAll('input').forEach(input => {
|
||||
input.addEventListener('input', () => {
|
||||
FormValidator.clearError(input);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,538 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
|
||||
<style>
|
||||
.kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-m);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
padding: var(--space-l);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-s);
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-gray-900);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: var(--font-size-body-s);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.kpi-change {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: var(--font-size-body-s);
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
|
||||
.kpi-change.positive {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.kpi-change.negative {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.event-item {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
padding: var(--space-l);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: var(--space-m);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.event-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-m);
|
||||
}
|
||||
|
||||
.event-status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-small);
|
||||
font-size: var(--font-size-body-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.event-status-badge.active {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.event-status-badge.completed {
|
||||
background-color: var(--color-gray-200);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.event-stats {
|
||||
display: flex;
|
||||
gap: var(--space-l);
|
||||
margin-top: var(--space-m);
|
||||
padding-top: var(--space-m);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.event-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
font-size: var(--font-size-body-s);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
padding: var(--space-l);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: var(--radius-medium);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--font-size-body-l);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-placeholder::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgba(255, 255, 255, 0.1),
|
||||
rgba(255, 255, 255, 0.1) 10px,
|
||||
rgba(255, 255, 255, 0.05) 10px,
|
||||
rgba(255, 255, 255, 0.05) 20px
|
||||
);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.kpi-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<div style="width: 44px;"></div>
|
||||
<h1 class="top-bar-title">실시간 대시보드</h1>
|
||||
<button class="icon-button" aria-label="새로고침" onclick="refreshDashboard()">
|
||||
<span class="material-icons">refresh</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<section class="kpi-grid">
|
||||
|
||||
<!-- Total Events -->
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-icon" style="background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">event</span>
|
||||
</div>
|
||||
<div class="kpi-value" id="totalEvents">12</div>
|
||||
<div class="kpi-label">전체 이벤트</div>
|
||||
<div class="kpi-change positive">
|
||||
<span class="material-icons" style="font-size: 16px;">arrow_upward</span>
|
||||
<span>+2 이번 달</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Events -->
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-icon" style="background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">play_circle</span>
|
||||
</div>
|
||||
<div class="kpi-value" id="activeEvents">3</div>
|
||||
<div class="kpi-label">진행 중</div>
|
||||
<div class="kpi-change positive">
|
||||
<span class="material-icons" style="font-size: 16px;">trending_up</span>
|
||||
<span>진행 중</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Participants -->
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-icon" style="background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">group</span>
|
||||
</div>
|
||||
<div class="kpi-value" id="totalParticipants">1,248</div>
|
||||
<div class="kpi-label">총 참여자</div>
|
||||
<div class="kpi-change positive">
|
||||
<span class="material-icons" style="font-size: 16px;">arrow_upward</span>
|
||||
<span>+156 오늘</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sales Increase -->
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-icon" style="background-color: var(--color-warning-light);">
|
||||
<span class="material-icons text-warning" style="font-size: 20px;">trending_up</span>
|
||||
</div>
|
||||
<div class="kpi-value" id="salesIncrease">+23%</div>
|
||||
<div class="kpi-label">매출 증가율</div>
|
||||
<div class="kpi-change positive">
|
||||
<span class="material-icons" style="font-size: 16px;">arrow_upward</span>
|
||||
<span>전월 대비</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<section class="chart-container">
|
||||
<h3 class="h3 mb-m">참여자 추이</h3>
|
||||
<div class="chart-placeholder">
|
||||
<div style="position: relative; z-index: 1;">
|
||||
<span class="material-icons" style="font-size: 48px; margin-bottom: 8px;">show_chart</span>
|
||||
<div>참여자 증가 추이 그래프</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 mt-s">
|
||||
최근 7일간 일평균 178명의 고객이 이벤트에 참여했습니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Active Events List -->
|
||||
<section class="mb-xl">
|
||||
<div class="flex items-center justify-between mb-m">
|
||||
<h3 class="h3">진행 중인 이벤트</h3>
|
||||
<button class="btn-text body-s" onclick="viewAllEvents()">
|
||||
전체 보기
|
||||
<span class="material-icons" style="font-size: 16px;">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="eventsList">
|
||||
<!-- Events will be rendered here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AI Insights Section -->
|
||||
<section class="mb-xl">
|
||||
<div class="card bg-secondary-light" style="border: none;">
|
||||
<div class="card-body flex gap-m">
|
||||
<span class="material-icons text-secondary" style="font-size: 28px;">auto_awesome</span>
|
||||
<div>
|
||||
<h4 class="body-l text-semibold mb-xs text-secondary">AI 인사이트</h4>
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
현재 진행 중인 이벤트의 참여율이 예상보다 15% 높습니다.
|
||||
추가 할인 혜택을 제공하면 더 큰 효과를 얻을 수 있습니다.
|
||||
</p>
|
||||
<button class="btn btn-secondary btn-sm" onclick="viewAIRecommendations()">
|
||||
<span class="material-icons mr-xs" style="font-size: 18px;">lightbulb</span>
|
||||
AI 개선안 보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// Dashboard data (실제로는 API에서 가져옴)
|
||||
const dashboardData = {
|
||||
kpis: {
|
||||
totalEvents: 12,
|
||||
activeEvents: 3,
|
||||
totalParticipants: 1248,
|
||||
salesIncrease: 23
|
||||
},
|
||||
events: [
|
||||
{
|
||||
id: 1,
|
||||
name: '우진이네 여름 특별 할인 이벤트',
|
||||
status: 'active',
|
||||
startDate: '2025.06.01',
|
||||
endDate: '2025.06.30',
|
||||
participants: 456,
|
||||
views: 1234,
|
||||
clickRate: 37
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '신메뉴 출시 기념 이벤트',
|
||||
status: 'active',
|
||||
startDate: '2025.06.15',
|
||||
endDate: '2025.07.15',
|
||||
participants: 234,
|
||||
views: 890,
|
||||
clickRate: 26
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '재방문 고객 감사 이벤트',
|
||||
status: 'active',
|
||||
startDate: '2025.06.10',
|
||||
endDate: '2025.06.25',
|
||||
participants: 558,
|
||||
views: 2100,
|
||||
clickRate: 27
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '봄맞이 할인 이벤트',
|
||||
status: 'completed',
|
||||
startDate: '2025.03.01',
|
||||
endDate: '2025.03.31',
|
||||
participants: 892,
|
||||
views: 3200,
|
||||
clickRate: 28
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Render events list
|
||||
function renderEventsList() {
|
||||
const container = document.getElementById('eventsList');
|
||||
const activeEvents = dashboardData.events.filter(e => e.status === 'active');
|
||||
|
||||
if (activeEvents.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="card bg-gray-100" style="border: none;">
|
||||
<div class="card-body text-center">
|
||||
<span class="material-icons text-gray-400" style="font-size: 48px;">event_busy</span>
|
||||
<p class="body-m text-gray-600 mt-m">진행 중인 이벤트가 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = activeEvents.map(event => `
|
||||
<div class="event-item" onclick="viewEventDetail(${event.id})">
|
||||
<div class="event-header">
|
||||
<div>
|
||||
<h4 class="body-l text-semibold mb-xs">${event.name}</h4>
|
||||
<p class="body-s text-gray-600">${event.startDate} ~ ${event.endDate}</p>
|
||||
</div>
|
||||
<span class="event-status-badge ${event.status}">
|
||||
${event.status === 'active' ? '진행 중' : '완료'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="event-stats">
|
||||
<div class="event-stat">
|
||||
<span class="material-icons" style="font-size: 18px;">group</span>
|
||||
<span>${Utils.formatNumber(event.participants)}명 참여</span>
|
||||
</div>
|
||||
<div class="event-stat">
|
||||
<span class="material-icons" style="font-size: 18px;">visibility</span>
|
||||
<span>${Utils.formatNumber(event.views)}회 조회</span>
|
||||
</div>
|
||||
<div class="event-stat">
|
||||
<span class="material-icons" style="font-size: 18px;">ads_click</span>
|
||||
<span>${event.clickRate}% 클릭률</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Update KPIs
|
||||
function updateKPIs() {
|
||||
document.getElementById('totalEvents').textContent = dashboardData.kpis.totalEvents;
|
||||
document.getElementById('activeEvents').textContent = dashboardData.kpis.activeEvents;
|
||||
document.getElementById('totalParticipants').textContent =
|
||||
Utils.formatNumber(dashboardData.kpis.totalParticipants);
|
||||
document.getElementById('salesIncrease').textContent =
|
||||
`+${dashboardData.kpis.salesIncrease}%`;
|
||||
}
|
||||
|
||||
// Refresh dashboard
|
||||
function refreshDashboard() {
|
||||
UIManager.showLoading('데이터를 불러오는 중...');
|
||||
|
||||
// API 호출 시뮬레이션
|
||||
setTimeout(() => {
|
||||
// 데이터 업데이트 (실제로는 API 응답으로 업데이트)
|
||||
updateKPIs();
|
||||
renderEventsList();
|
||||
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('대시보드가 업데이트되었습니다.', 'success');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// View event detail
|
||||
function viewEventDetail(eventId) {
|
||||
const event = dashboardData.events.find(e => e.id === eventId);
|
||||
|
||||
UIManager.showModal({
|
||||
title: event.name,
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="flex items-center gap-s">
|
||||
<span class="material-icons text-gray-600">calendar_month</span>
|
||||
<span class="body-m text-gray-900">${event.startDate} ~ ${event.endDate}</span>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="flex flex-col gap-s">
|
||||
<div class="flex justify-between body-m">
|
||||
<span class="text-gray-600">참여자 수</span>
|
||||
<span class="text-semibold">${Utils.formatNumber(event.participants)}명</span>
|
||||
</div>
|
||||
<div class="flex justify-between body-m">
|
||||
<span class="text-gray-600">조회 수</span>
|
||||
<span class="text-semibold">${Utils.formatNumber(event.views)}회</span>
|
||||
</div>
|
||||
<div class="flex justify-between body-m">
|
||||
<span class="text-gray-600">클릭률</span>
|
||||
<span class="text-semibold">${event.clickRate}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<p class="body-s text-gray-600">
|
||||
이벤트 참여율이 목표 대비 ${Math.floor(Math.random() * 30 + 10)}% 높습니다.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// View all events
|
||||
function viewAllEvents() {
|
||||
UIManager.showBottomSheet({
|
||||
title: '전체 이벤트',
|
||||
body: `
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
${dashboardData.events.map(event => `
|
||||
<div class="event-item" style="margin-bottom: var(--space-m);" onclick="viewEventDetail(${event.id})">
|
||||
<div class="event-header">
|
||||
<div>
|
||||
<h4 class="body-l text-semibold mb-xs">${event.name}</h4>
|
||||
<p class="body-s text-gray-600">${event.startDate} ~ ${event.endDate}</p>
|
||||
</div>
|
||||
<span class="event-status-badge ${event.status}">
|
||||
${event.status === 'active' ? '진행 중' : '완료'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="event-stats">
|
||||
<div class="event-stat">
|
||||
<span class="material-icons" style="font-size: 18px;">group</span>
|
||||
<span>${Utils.formatNumber(event.participants)}명</span>
|
||||
</div>
|
||||
<div class="event-stat">
|
||||
<span class="material-icons" style="font-size: 18px;">visibility</span>
|
||||
<span>${Utils.formatNumber(event.views)}회</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`,
|
||||
confirmText: '닫기',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// View AI recommendations
|
||||
function viewAIRecommendations() {
|
||||
// Navigate to AI improvement suggestions page
|
||||
NavManager.navigate('25', 'AI개선안제안');
|
||||
}
|
||||
|
||||
// Page initialization
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('analytics');
|
||||
|
||||
// Dashboard 데이터 렌더링
|
||||
updateKPIs();
|
||||
renderEventsList();
|
||||
|
||||
// Auto-refresh every 30 seconds (프로토타입에서는 비활성화)
|
||||
// setInterval(refreshDashboard, 30000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,467 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
|
||||
<style>
|
||||
.suggestion-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
padding: var(--space-l);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: var(--space-m);
|
||||
border-left: 4px solid var(--color-secondary);
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-m);
|
||||
margin-bottom: var(--space-m);
|
||||
}
|
||||
|
||||
.suggestion-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-secondary-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.suggestion-type {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--radius-small);
|
||||
font-size: var(--font-size-body-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.suggestion-type.optimization {
|
||||
background-color: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.suggestion-type.targeting {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.suggestion-type.content {
|
||||
background-color: var(--color-warning-light);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.suggestion-impact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-s);
|
||||
margin-top: var(--space-m);
|
||||
padding: var(--space-m);
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-small);
|
||||
}
|
||||
|
||||
.impact-label {
|
||||
font-size: var(--font-size-body-s);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.impact-value {
|
||||
font-size: var(--font-size-body-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.suggestion-actions {
|
||||
display: flex;
|
||||
gap: var(--space-s);
|
||||
margin-top: var(--space-m);
|
||||
}
|
||||
|
||||
.suggestion-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-s) var(--space-m);
|
||||
border-radius: var(--radius-small);
|
||||
font-size: var(--font-size-body-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-top: var(--space-m);
|
||||
}
|
||||
|
||||
.suggestion-status.accepted {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.suggestion-status.rejected {
|
||||
background-color: var(--color-gray-200);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--space-2xl) var(--space-l);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 64px;
|
||||
color: var(--color-gray-300);
|
||||
margin-bottom: var(--space-m);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<button class="back-button" aria-label="뒤로가기">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="top-bar-title">AI 개선안 제안</h1>
|
||||
<div style="width: 44px;"></div>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Header Section -->
|
||||
<section class="mb-l">
|
||||
<div class="flex items-center gap-s mb-s">
|
||||
<span class="material-icons text-secondary" style="font-size: 28px;">auto_awesome</span>
|
||||
<h2 class="h2">AI가 분석한 개선안</h2>
|
||||
</div>
|
||||
<p class="body-m text-gray-700">
|
||||
현재 진행 중인 이벤트를 분석하여 성과를 높일 수 있는 개선안을 제안합니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Suggestions List -->
|
||||
<section id="suggestionsContainer" class="mb-xl">
|
||||
<!-- Suggestions will be rendered here -->
|
||||
</section>
|
||||
|
||||
<!-- Generate New Suggestions Button -->
|
||||
<section>
|
||||
<button class="btn btn-secondary btn-full" onclick="generateNewSuggestions()">
|
||||
<span class="material-icons mr-xs">refresh</span>
|
||||
새로운 개선안 생성
|
||||
</button>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// AI Suggestions data (실제로는 API에서 가져옴)
|
||||
let suggestions = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'optimization',
|
||||
typeLabel: '최적화',
|
||||
eventName: '우진이네 여름 특별 할인 이벤트',
|
||||
title: '할인율 조정 제안',
|
||||
description: '현재 참여율이 목표 대비 15% 높습니다. 할인율을 20%에서 25%로 상향 조정하면 참여자가 30% 이상 증가할 것으로 예상됩니다.',
|
||||
rationale: '경쟁 매장 분석 결과, 유사 이벤트의 평균 할인율은 22-28%입니다. 현재 할인율은 경쟁력이 다소 낮은 수준입니다.',
|
||||
expectedImpact: '+30% 참여자 증가',
|
||||
impactValue: '+30%',
|
||||
status: null
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'targeting',
|
||||
typeLabel: '타겟팅',
|
||||
eventName: '신메뉴 출시 기념 이벤트',
|
||||
title: '타겟 시간대 확대',
|
||||
description: '점심 시간대(11:00-14:00)에 집중된 현재 전략을 저녁 시간대(18:00-21:00)로 확대하면 참여자 기반을 넓힐 수 있습니다.',
|
||||
rationale: '데이터 분석 결과, 저녁 시간대 방문 고객 중 65%가 이벤트를 인지하지 못하고 있습니다.',
|
||||
expectedImpact: '+45% 도달률 증가',
|
||||
impactValue: '+45%',
|
||||
status: null
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'content',
|
||||
typeLabel: '콘텐츠',
|
||||
eventName: '재방문 고객 감사 이벤트',
|
||||
title: 'SNS 공유 인센티브 강화',
|
||||
description: 'SNS 공유 시 추가 할인율을 10%에서 15%로 상향하고, 리뷰 작성 시 추가 쿠폰을 제공하면 바이럴 효과가 증가합니다.',
|
||||
rationale: '현재 SNS 공유율은 12%로 업계 평균(18-25%)보다 낮습니다. 인센티브 강화로 공유율을 2배 이상 높일 수 있습니다.',
|
||||
expectedImpact: '+120% SNS 공유 증가',
|
||||
impactValue: '+120%',
|
||||
status: null
|
||||
}
|
||||
];
|
||||
|
||||
// Render suggestions
|
||||
function renderSuggestions() {
|
||||
const container = document.getElementById('suggestionsContainer');
|
||||
|
||||
if (suggestions.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<span class="material-icons empty-state-icon">lightbulb_outline</span>
|
||||
<h3 class="h3 mb-s">제안 가능한 개선안이 없습니다</h3>
|
||||
<p class="body-m text-gray-600 mb-l">
|
||||
진행 중인 이벤트가 최적화되어 있거나,<br/>
|
||||
충분한 데이터가 수집되지 않았습니다.
|
||||
</p>
|
||||
<button class="btn btn-primary" onclick="generateNewSuggestions()">
|
||||
새로운 개선안 생성
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = suggestions.map(suggestion => `
|
||||
<div class="suggestion-card">
|
||||
<div class="suggestion-header">
|
||||
<div class="suggestion-icon">
|
||||
<span class="material-icons text-secondary" style="font-size: 24px;">
|
||||
${suggestion.type === 'optimization' ? 'tune' :
|
||||
suggestion.type === 'targeting' ? 'my_location' : 'create'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="suggestion-content">
|
||||
<span class="suggestion-type ${suggestion.type}">${suggestion.typeLabel}</span>
|
||||
<h3 class="body-l text-semibold mb-xs">${suggestion.title}</h3>
|
||||
<p class="body-s text-gray-600">${suggestion.eventName}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-m">
|
||||
<h4 class="body-m text-semibold mb-xs">개선안</h4>
|
||||
<p class="body-m text-gray-900">${suggestion.description}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-m">
|
||||
<h4 class="body-m text-semibold mb-xs">분석 근거</h4>
|
||||
<p class="body-s text-gray-700">${suggestion.rationale}</p>
|
||||
</div>
|
||||
|
||||
<div class="suggestion-impact">
|
||||
<span class="material-icons text-success">trending_up</span>
|
||||
<div>
|
||||
<div class="impact-label">예상 효과</div>
|
||||
<div class="impact-value">${suggestion.impactValue}</div>
|
||||
</div>
|
||||
<div class="flex-1 text-right">
|
||||
<p class="body-s text-gray-700">${suggestion.expectedImpact}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${suggestion.status ? `
|
||||
<div class="suggestion-status ${suggestion.status}">
|
||||
<span class="material-icons" style="font-size: 18px;">
|
||||
${suggestion.status === 'accepted' ? 'check_circle' : 'cancel'}
|
||||
</span>
|
||||
<span>${suggestion.status === 'accepted' ? '적용됨' : '거절됨'}</span>
|
||||
</div>
|
||||
` : `
|
||||
<div class="suggestion-actions">
|
||||
<button class="btn btn-secondary flex-1" onclick="rejectSuggestion(${suggestion.id})">
|
||||
<span class="material-icons mr-xs" style="font-size: 18px;">close</span>
|
||||
거절
|
||||
</button>
|
||||
<button class="btn btn-primary flex-1" onclick="acceptSuggestion(${suggestion.id})">
|
||||
<span class="material-icons mr-xs" style="font-size: 18px;">check</span>
|
||||
적용
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Accept suggestion
|
||||
function acceptSuggestion(suggestionId) {
|
||||
const suggestion = suggestions.find(s => s.id === suggestionId);
|
||||
|
||||
UIManager.showModal({
|
||||
title: '개선안 적용',
|
||||
body: `
|
||||
<p class="body-m text-gray-900 mb-m">
|
||||
<strong>${suggestion.title}</strong>을(를) 적용하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-success-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-s text-gray-700">
|
||||
<strong>예상 효과:</strong> ${suggestion.expectedImpact}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="body-s text-gray-600 mt-m">
|
||||
이벤트 설정이 즉시 업데이트되며, 변경 사항은 실시간으로 반영됩니다.
|
||||
</p>
|
||||
`,
|
||||
confirmText: '적용',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
UIManager.showLoading('개선안을 적용하고 있습니다...');
|
||||
|
||||
// API 호출 시뮬레이션
|
||||
setTimeout(() => {
|
||||
suggestion.status = 'accepted';
|
||||
AppState.save('acceptedSuggestions',
|
||||
suggestions.filter(s => s.status === 'accepted').map(s => s.id));
|
||||
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('개선안이 적용되었습니다!', 'success');
|
||||
renderSuggestions();
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reject suggestion
|
||||
function rejectSuggestion(suggestionId) {
|
||||
const suggestion = suggestions.find(s => s.id === suggestionId);
|
||||
|
||||
UIManager.showModal({
|
||||
title: '개선안 거절',
|
||||
body: `
|
||||
<p class="body-m text-gray-900 mb-m">
|
||||
<strong>${suggestion.title}</strong>을(를) 거절하시겠습니까?
|
||||
</p>
|
||||
<p class="body-s text-gray-600">
|
||||
거절된 개선안은 목록에서 제거되며, 언제든지 새로운 개선안을 생성할 수 있습니다.
|
||||
</p>
|
||||
`,
|
||||
confirmText: '거절',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
suggestion.status = 'rejected';
|
||||
AppState.save('rejectedSuggestions',
|
||||
suggestions.filter(s => s.status === 'rejected').map(s => s.id));
|
||||
|
||||
UIManager.showToast('개선안이 거절되었습니다.', 'info');
|
||||
renderSuggestions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new suggestions
|
||||
function generateNewSuggestions() {
|
||||
UIManager.showModal({
|
||||
title: '새로운 개선안 생성',
|
||||
body: `
|
||||
<p class="body-m text-gray-700 mb-m">
|
||||
현재 이벤트 데이터를 분석하여 새로운 개선안을 생성하시겠습니까?
|
||||
</p>
|
||||
<div class="card bg-info-light" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-s text-gray-700">
|
||||
💡 AI가 최신 데이터를 분석하여 최적의 개선안을 제시합니다.
|
||||
이전에 거절한 개선안은 제외됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '생성',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
UIManager.showLoading('AI가 새로운 개선안을 분석하고 있습니다...');
|
||||
|
||||
// Progress 업데이트
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
progress += 15;
|
||||
UIManager.updateProgress(progress);
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(progressInterval);
|
||||
|
||||
setTimeout(() => {
|
||||
// 새로운 개선안 추가 (시뮬레이션)
|
||||
const newSuggestion = {
|
||||
id: suggestions.length + 1,
|
||||
type: 'targeting',
|
||||
typeLabel: '타겟팅',
|
||||
eventName: '우진이네 여름 특별 할인 이벤트',
|
||||
title: '주말 특별 프로모션 추가',
|
||||
description: '주말에 별도의 추가 할인(5%)을 제공하면 주말 방문객을 25% 증가시킬 수 있습니다.',
|
||||
rationale: '주말 방문 고객의 평균 구매액이 평일 대비 40% 높으나, 현재 주말 특화 전략이 없습니다.',
|
||||
expectedImpact: '+25% 주말 방문 증가',
|
||||
impactValue: '+25%',
|
||||
status: null
|
||||
};
|
||||
|
||||
suggestions.unshift(newSuggestion);
|
||||
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('새로운 개선안이 생성되었습니다!', 'success');
|
||||
renderSuggestions();
|
||||
}, 500);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Page initialization
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('analytics');
|
||||
|
||||
// 이전에 저장된 상태 복원
|
||||
const acceptedIds = AppState.load('acceptedSuggestions') || [];
|
||||
const rejectedIds = AppState.load('rejectedSuggestions') || [];
|
||||
|
||||
suggestions.forEach(suggestion => {
|
||||
if (acceptedIds.includes(suggestion.id)) {
|
||||
suggestion.status = 'accepted';
|
||||
} else if (rejectedIds.includes(suggestion.id)) {
|
||||
suggestion.status = 'rejected';
|
||||
}
|
||||
});
|
||||
|
||||
// Render suggestions
|
||||
renderSuggestions();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,646 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
|
||||
<!-- Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="common.css">
|
||||
|
||||
<style>
|
||||
.profile-section {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
padding: var(--space-l);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: var(--space-xl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto var(--space-m);
|
||||
color: white;
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: var(--font-size-body-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--space-m);
|
||||
padding: 0 var(--space-s);
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background: white;
|
||||
border-radius: var(--radius-medium);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--space-l);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-out;
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: var(--space-m);
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
font-size: var(--font-size-body-l);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.menu-description {
|
||||
font-size: var(--font-size-body-s);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
background-color: var(--color-gray-300);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.toggle-switch.active {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.toggle-switch::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
.toggle-switch.active::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
margin-top: var(--space-l);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<div style="width: 44px;"></div>
|
||||
<h1 class="top-bar-title">MY</h1>
|
||||
<button class="icon-button" aria-label="설정" onclick="openSettings()">
|
||||
<span class="material-icons">settings</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Page Container -->
|
||||
<main class="page">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- Profile Section -->
|
||||
<section class="profile-section">
|
||||
<div class="profile-avatar" id="profileAvatar">정</div>
|
||||
<h2 class="h2 mb-xs" id="userName">정우진</h2>
|
||||
<p class="body-m text-gray-600 mb-s" id="businessName">우진이네 고깃집</p>
|
||||
<p class="body-s text-gray-500 mb-l" id="userEmail">woojin@example.com</p>
|
||||
<button class="btn btn-secondary" onclick="editProfile()">
|
||||
<span class="material-icons mr-xs" style="font-size: 18px;">edit</span>
|
||||
프로필 수정
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Business Settings -->
|
||||
<section class="menu-section">
|
||||
<h3 class="menu-title">사업장 관리</h3>
|
||||
<div class="menu-list">
|
||||
<div class="menu-item" onclick="openBusinessInfo()">
|
||||
<div class="menu-icon" style="background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">store</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">사업장 정보</div>
|
||||
<div class="menu-description">업종, 주소, 연락처 관리</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openOperatingHours()">
|
||||
<div class="menu-icon" style="background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">schedule</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">운영 시간</div>
|
||||
<div class="menu-description">영업 시간 및 휴무일 설정</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event Settings -->
|
||||
<section class="menu-section">
|
||||
<h3 class="menu-title">이벤트 관리</h3>
|
||||
<div class="menu-list">
|
||||
<div class="menu-item" onclick="openEventHistory()">
|
||||
<div class="menu-icon" style="background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">history</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">이벤트 내역</div>
|
||||
<div class="menu-description">지난 이벤트 조회 및 관리</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openTemplates()">
|
||||
<div class="menu-icon" style="background-color: var(--color-warning-light);">
|
||||
<span class="material-icons text-warning" style="font-size: 20px;">folder_special</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">저장된 템플릿</div>
|
||||
<div class="menu-description">자주 사용하는 이벤트 템플릿</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Account Settings -->
|
||||
<section class="menu-section">
|
||||
<h3 class="menu-title">계정 설정</h3>
|
||||
<div class="menu-list">
|
||||
<div class="menu-item">
|
||||
<div class="menu-icon" style="background-color: var(--color-info-light);">
|
||||
<span class="material-icons text-info" style="font-size: 20px;">notifications</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">알림 설정</div>
|
||||
<div class="menu-description">이벤트 알림 수신</div>
|
||||
</div>
|
||||
<div class="toggle-switch active" id="notificationToggle" onclick="toggleNotification(event)"></div>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openLanguage()">
|
||||
<div class="menu-icon" style="background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">language</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">언어 설정</div>
|
||||
<div class="menu-description">한국어</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openSecurity()">
|
||||
<div class="menu-icon" style="background-color: var(--color-error-light);">
|
||||
<span class="material-icons text-error" style="font-size: 20px;">lock</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">보안 설정</div>
|
||||
<div class="menu-description">비밀번호 변경, 2단계 인증</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Help & Support -->
|
||||
<section class="menu-section">
|
||||
<h3 class="menu-title">고객 지원</h3>
|
||||
<div class="menu-list">
|
||||
<div class="menu-item" onclick="openGuide()">
|
||||
<div class="menu-icon" style="background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">help</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">사용 가이드</div>
|
||||
<div class="menu-description">서비스 이용 방법 안내</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openFAQ()">
|
||||
<div class="menu-icon" style="background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">question_answer</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">자주 묻는 질문</div>
|
||||
<div class="menu-description">FAQ 및 문제 해결</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openContact()">
|
||||
<div class="menu-icon" style="background-color: var(--color-secondary-light);">
|
||||
<span class="material-icons text-secondary" style="font-size: 20px;">support_agent</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">고객센터</div>
|
||||
<div class="menu-description">1:1 문의 및 상담</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item" onclick="openTerms()">
|
||||
<div class="menu-icon" style="background-color: var(--color-gray-200);">
|
||||
<span class="material-icons text-gray-700" style="font-size: 20px;">description</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">약관 및 정책</div>
|
||||
<div class="menu-description">이용약관, 개인정보처리방침</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- App Info -->
|
||||
<section class="menu-section">
|
||||
<div class="menu-list">
|
||||
<div class="menu-item" onclick="openVersionInfo()">
|
||||
<div class="menu-icon" style="background-color: var(--color-info-light);">
|
||||
<span class="material-icons text-info" style="font-size: 20px;">info</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">앱 정보</div>
|
||||
<div class="menu-description">버전 1.0.0</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<section class="logout-button">
|
||||
<button class="btn btn-text btn-full text-error" onclick="logout()">
|
||||
<span class="material-icons mr-xs">logout</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="#" class="nav-item" data-nav="home">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="event">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-nav="analytics">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item active" data-nav="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- Common JavaScript -->
|
||||
<script src="common.js"></script>
|
||||
|
||||
<!-- Page Specific JavaScript -->
|
||||
<script>
|
||||
// Load user profile
|
||||
function loadProfile() {
|
||||
const user = AppState.loadUser() || SampleData.user;
|
||||
|
||||
document.getElementById('userName').textContent = user.name;
|
||||
document.getElementById('businessName').textContent = user.businessName;
|
||||
document.getElementById('userEmail').textContent = user.email;
|
||||
document.getElementById('profileAvatar').textContent = user.name.charAt(0);
|
||||
}
|
||||
|
||||
// Edit profile
|
||||
function editProfile() {
|
||||
const user = AppState.loadUser() || SampleData.user;
|
||||
|
||||
UIManager.showModal({
|
||||
title: '프로필 수정',
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="input-group" style="margin-bottom: 0;">
|
||||
<label for="editName">이름</label>
|
||||
<input type="text" id="editName" class="input" value="${user.name}" />
|
||||
</div>
|
||||
<div class="input-group" style="margin-bottom: 0;">
|
||||
<label for="editBusinessName">사업장명</label>
|
||||
<input type="text" id="editBusinessName" class="input" value="${user.businessName}" />
|
||||
</div>
|
||||
<div class="input-group" style="margin-bottom: 0;">
|
||||
<label for="editEmail">이메일</label>
|
||||
<input type="email" id="editEmail" class="input" value="${user.email}" />
|
||||
</div>
|
||||
<div class="input-group" style="margin-bottom: 0;">
|
||||
<label for="editPhone">전화번호</label>
|
||||
<input type="tel" id="editPhone" class="input" value="${user.phone || ''}" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '저장',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: document.getElementById('editName').value,
|
||||
businessName: document.getElementById('editBusinessName').value,
|
||||
email: document.getElementById('editEmail').value,
|
||||
phone: document.getElementById('editPhone').value
|
||||
};
|
||||
|
||||
AppState.saveUser(updatedUser);
|
||||
UIManager.showToast('프로필이 수정되었습니다.', 'success');
|
||||
loadProfile();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle notification
|
||||
function toggleNotification(event) {
|
||||
event.stopPropagation();
|
||||
const toggle = document.getElementById('notificationToggle');
|
||||
const isActive = toggle.classList.contains('active');
|
||||
|
||||
toggle.classList.toggle('active');
|
||||
AppState.save('notificationEnabled', !isActive);
|
||||
|
||||
const message = !isActive ? '알림이 활성화되었습니다.' : '알림이 비활성화되었습니다.';
|
||||
UIManager.showToast(message, 'info');
|
||||
}
|
||||
|
||||
// Settings menu handlers
|
||||
function openSettings() {
|
||||
UIManager.showToast('설정 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openBusinessInfo() {
|
||||
UIManager.showToast('사업장 정보 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openOperatingHours() {
|
||||
UIManager.showToast('운영 시간 설정 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openEventHistory() {
|
||||
UIManager.showToast('이벤트 내역 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openTemplates() {
|
||||
UIManager.showToast('템플릿 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openLanguage() {
|
||||
UIManager.showBottomSheet({
|
||||
title: '언어 선택',
|
||||
body: `
|
||||
<div class="flex flex-col gap-s">
|
||||
<label class="radio card p-m" style="cursor: pointer;">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="radio" name="language" value="ko" checked />
|
||||
<div class="flex-1">
|
||||
<div class="body-l text-semibold">한국어</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio card p-m" style="cursor: pointer;">
|
||||
<div class="flex items-center gap-m">
|
||||
<input type="radio" name="language" value="en" />
|
||||
<div class="flex-1">
|
||||
<div class="body-l text-semibold">English</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
UIManager.showToast('언어가 변경되었습니다.', 'success');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openSecurity() {
|
||||
UIManager.showBottomSheet({
|
||||
title: '보안 설정',
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="menu-item" onclick="changePassword()" style="padding: var(--space-m); cursor: pointer;">
|
||||
<div class="menu-icon" style="background-color: var(--color-primary-light);">
|
||||
<span class="material-icons text-primary" style="font-size: 20px;">lock</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">비밀번호 변경</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
<div class="menu-item" onclick="setup2FA()" style="padding: var(--space-m); cursor: pointer;">
|
||||
<div class="menu-icon" style="background-color: var(--color-success-light);">
|
||||
<span class="material-icons text-success" style="font-size: 20px;">security</span>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">2단계 인증 설정</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '닫기',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
function changePassword() {
|
||||
UIManager.closeBottomSheet();
|
||||
UIManager.showToast('비밀번호 변경 기능 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function setup2FA() {
|
||||
UIManager.closeBottomSheet();
|
||||
UIManager.showToast('2단계 인증 설정 기능 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openGuide() {
|
||||
UIManager.showToast('사용 가이드 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openFAQ() {
|
||||
UIManager.showToast('FAQ 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openContact() {
|
||||
UIManager.showToast('고객센터 페이지 준비 중입니다.', 'info');
|
||||
}
|
||||
|
||||
function openTerms() {
|
||||
UIManager.showBottomSheet({
|
||||
title: '약관 및 정책',
|
||||
body: `
|
||||
<div class="flex flex-col gap-m">
|
||||
<div class="menu-item" style="padding: var(--space-m); cursor: pointer;">
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">이용약관</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
<div class="menu-item" style="padding: var(--space-m); cursor: pointer;">
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">개인정보 처리방침</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
<div class="menu-item" style="padding: var(--space-m); cursor: pointer;">
|
||||
<div class="menu-content">
|
||||
<div class="menu-label">마케팅 정보 수신 동의</div>
|
||||
</div>
|
||||
<span class="material-icons menu-arrow">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '닫기',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
function openVersionInfo() {
|
||||
UIManager.showModal({
|
||||
title: '앱 정보',
|
||||
body: `
|
||||
<div class="text-center mb-m">
|
||||
<div class="profile-avatar" style="width: 64px; height: 64px; margin: 0 auto var(--space-m);">
|
||||
<span class="material-icons" style="font-size: 32px;">storefront</span>
|
||||
</div>
|
||||
<h3 class="h3 mb-xs">KT AI 이벤트 자동 생성</h3>
|
||||
<p class="body-m text-gray-600 mb-m">버전 1.0.0</p>
|
||||
</div>
|
||||
<div class="card bg-gray-100" style="border: none;">
|
||||
<div class="card-body">
|
||||
<p class="body-s text-gray-700" style="line-height: 1.8;">
|
||||
© 2025 KT Corporation.<br/>
|
||||
All rights reserved.<br/><br/>
|
||||
AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '확인',
|
||||
cancelText: null
|
||||
});
|
||||
}
|
||||
|
||||
// Logout
|
||||
function logout() {
|
||||
UIManager.showModal({
|
||||
title: '로그아웃',
|
||||
body: `
|
||||
<p class="body-m text-gray-900 mb-m">
|
||||
정말 로그아웃하시겠습니까?
|
||||
</p>
|
||||
<p class="body-s text-gray-600">
|
||||
저장되지 않은 데이터는 유지되지 않을 수 있습니다.
|
||||
</p>
|
||||
`,
|
||||
confirmText: '로그아웃',
|
||||
cancelText: '취소',
|
||||
onConfirm: () => {
|
||||
UIManager.showLoading('로그아웃 중...');
|
||||
|
||||
setTimeout(() => {
|
||||
// Clear user data (프로토타입에서는 일부만 삭제)
|
||||
// AppState.clear(); // 전체 삭제는 프로토타입 테스트를 위해 주석 처리
|
||||
|
||||
UIManager.hideLoading();
|
||||
UIManager.showToast('로그아웃되었습니다.', 'success');
|
||||
|
||||
// Redirect to login (프로토타입에서는 메인으로)
|
||||
setTimeout(() => {
|
||||
NavManager.navigate('00', '메인대시보드');
|
||||
}, 500);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Page initialization
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Bottom Navigation 초기화
|
||||
NavManager.initBottomNav('my');
|
||||
|
||||
// Load profile
|
||||
loadProfile();
|
||||
|
||||
// Load notification setting
|
||||
const notificationEnabled = AppState.load('notificationEnabled');
|
||||
if (notificationEnabled === false) {
|
||||
document.getElementById('notificationToggle').classList.remove('active');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
774
design/uiux/prototype/common.js
vendored
774
design/uiux/prototype/common.js
vendored
@ -1,774 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* 공통 JavaScript
|
||||
*
|
||||
* 기능:
|
||||
* - 네비게이션 관리
|
||||
* - 상태 관리 (localStorage)
|
||||
* - UI 유틸리티 (Toast, Modal, Bottom Sheet)
|
||||
* - 폼 검증
|
||||
* - 애니메이션 헬퍼
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 1. Navigation Manager
|
||||
// ============================================
|
||||
const NavManager = {
|
||||
/**
|
||||
* 화면 이동
|
||||
* @param {string} pageNumber - 화면 번호 (00, 01, 03 등)
|
||||
* @param {string} pageName - 화면명 (한글)
|
||||
*/
|
||||
navigate: (pageNumber, pageName) => {
|
||||
window.location.href = `${pageNumber}-${pageName}.html`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 뒤로가기
|
||||
*/
|
||||
back: () => {
|
||||
window.history.back();
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom Navigation 초기화
|
||||
* @param {string} activeMenu - 활성 메뉴 ('home' | 'event' | 'analytics' | 'my')
|
||||
*/
|
||||
initBottomNav: (activeMenu = 'home') => {
|
||||
const navItems = document.querySelectorAll('.nav-item');
|
||||
|
||||
navItems.forEach(item => {
|
||||
const menu = item.getAttribute('data-nav');
|
||||
|
||||
// 활성 메뉴 표시
|
||||
if (menu === activeMenu) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
|
||||
// 클릭 이벤트
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
switch (menu) {
|
||||
case 'home':
|
||||
NavManager.navigate('00', '메인대시보드');
|
||||
break;
|
||||
case 'event':
|
||||
NavManager.navigate('03', '이벤트목적선택');
|
||||
break;
|
||||
case 'analytics':
|
||||
NavManager.navigate('21', '실시간대시보드');
|
||||
break;
|
||||
case 'my':
|
||||
NavManager.navigate('99', '마이페이지');
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 2. State Management
|
||||
// ============================================
|
||||
const AppState = {
|
||||
/**
|
||||
* 데이터 저장
|
||||
* @param {string} key - 저장 키
|
||||
* @param {*} data - 저장할 데이터
|
||||
*/
|
||||
save: (key, data) => {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save data:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 데이터 로드
|
||||
* @param {string} key - 로드 키
|
||||
* @returns {*} 저장된 데이터 또는 null
|
||||
*/
|
||||
load: (key) => {
|
||||
try {
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.error('Failed to load data:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 데이터 삭제
|
||||
* @param {string} key - 삭제 키
|
||||
*/
|
||||
remove: (key) => {
|
||||
localStorage.removeItem(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 전체 데이터 삭제
|
||||
*/
|
||||
clear: () => {
|
||||
localStorage.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* 사용자 정보 저장
|
||||
*/
|
||||
saveUser: (userData) => {
|
||||
AppState.save('user', userData);
|
||||
},
|
||||
|
||||
/**
|
||||
* 사용자 정보 로드
|
||||
*/
|
||||
loadUser: () => {
|
||||
return AppState.load('user');
|
||||
},
|
||||
|
||||
/**
|
||||
* 이벤트 데이터 저장
|
||||
*/
|
||||
saveEvent: (eventData) => {
|
||||
const events = AppState.load('events') || [];
|
||||
events.push(eventData);
|
||||
AppState.save('events', events);
|
||||
},
|
||||
|
||||
/**
|
||||
* 이벤트 목록 로드
|
||||
*/
|
||||
loadEvents: () => {
|
||||
return AppState.load('events') || [];
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 3. UI Manager
|
||||
// ============================================
|
||||
const UIManager = {
|
||||
/**
|
||||
* Toast 메시지 표시
|
||||
* @param {string} message - 메시지 내용
|
||||
* @param {string} type - 타입 ('success' | 'error' | 'warning' | 'info')
|
||||
* @param {number} duration - 표시 시간 (ms, 기본 3000)
|
||||
*/
|
||||
showToast: (message, type = 'info', duration = 3000) => {
|
||||
// 기존 Toast 제거
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// Toast 생성
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.textContent = message;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'polite');
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 애니메이션
|
||||
setTimeout(() => {
|
||||
toast.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// 자동 제거
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 300);
|
||||
}, duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* Modal 표시
|
||||
* @param {Object} options - Modal 옵션
|
||||
* @param {string} options.title - 제목
|
||||
* @param {string} options.body - 내용
|
||||
* @param {string} options.confirmText - 확인 버튼 텍스트
|
||||
* @param {string} options.cancelText - 취소 버튼 텍스트
|
||||
* @param {Function} options.onConfirm - 확인 콜백
|
||||
* @param {Function} options.onCancel - 취소 콜백
|
||||
*/
|
||||
showModal: (options) => {
|
||||
const {
|
||||
title = '알림',
|
||||
body = '',
|
||||
confirmText = '확인',
|
||||
cancelText = '취소',
|
||||
onConfirm = null,
|
||||
onCancel = null
|
||||
} = options;
|
||||
|
||||
// Modal HTML 생성
|
||||
const modalHTML = `
|
||||
<div class="modal-overlay" id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="modal-title">${title}</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${body}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${cancelText ? `<button class="btn btn-text" id="modal-cancel">${cancelText}</button>` : ''}
|
||||
<button class="btn btn-primary" id="modal-confirm">${confirmText}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// DOM에 추가
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.innerHTML = modalHTML;
|
||||
document.body.appendChild(modalContainer.firstElementChild);
|
||||
|
||||
const modalElement = document.getElementById('modal');
|
||||
|
||||
// 표시 애니메이션
|
||||
setTimeout(() => {
|
||||
modalElement.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// 확인 버튼
|
||||
const confirmBtn = document.getElementById('modal-confirm');
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
if (onConfirm) onConfirm();
|
||||
UIManager.closeModal();
|
||||
});
|
||||
|
||||
// 취소 버튼
|
||||
const cancelBtn = document.getElementById('modal-cancel');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
if (onCancel) onCancel();
|
||||
UIManager.closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// 배경 클릭으로 닫기
|
||||
modalElement.addEventListener('click', (e) => {
|
||||
if (e.target === modalElement) {
|
||||
if (onCancel) onCancel();
|
||||
UIManager.closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC 키로 닫기
|
||||
const handleEscape = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (onCancel) onCancel();
|
||||
UIManager.closeModal();
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
},
|
||||
|
||||
/**
|
||||
* Modal 닫기
|
||||
*/
|
||||
closeModal: () => {
|
||||
const modal = document.getElementById('modal');
|
||||
if (modal) {
|
||||
modal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
modal.remove();
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom Sheet 표시
|
||||
* @param {Object} options - Bottom Sheet 옵션
|
||||
* @param {string} options.content - HTML 내용
|
||||
* @param {Function} options.onClose - 닫기 콜백
|
||||
*/
|
||||
showBottomSheet: (options) => {
|
||||
const { content = '', onClose = null } = options;
|
||||
|
||||
// Bottom Sheet HTML 생성
|
||||
const sheetHTML = `
|
||||
<div class="bottom-sheet-overlay" id="bottom-sheet">
|
||||
<div class="bottom-sheet">
|
||||
<div class="bottom-sheet-handle"></div>
|
||||
<div class="bottom-sheet-content">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// DOM에 추가
|
||||
const sheetContainer = document.createElement('div');
|
||||
sheetContainer.innerHTML = sheetHTML;
|
||||
document.body.appendChild(sheetContainer.firstElementChild);
|
||||
|
||||
const sheetElement = document.getElementById('bottom-sheet');
|
||||
|
||||
// 표시 애니메이션
|
||||
setTimeout(() => {
|
||||
sheetElement.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// 배경 클릭으로 닫기
|
||||
sheetElement.addEventListener('click', (e) => {
|
||||
if (e.target === sheetElement) {
|
||||
if (onClose) onClose();
|
||||
UIManager.closeBottomSheet();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom Sheet 닫기
|
||||
*/
|
||||
closeBottomSheet: () => {
|
||||
const sheet = document.getElementById('bottom-sheet');
|
||||
if (sheet) {
|
||||
sheet.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
sheet.remove();
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loading 표시
|
||||
* @param {string} message - 로딩 메시지
|
||||
*/
|
||||
showLoading: (message = 'AI가 처리하고 있습니다...') => {
|
||||
const loadingHTML = `
|
||||
<div class="modal-overlay show" id="loading">
|
||||
<div class="ai-loading">
|
||||
<div class="spinner"></div>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const loadingContainer = document.createElement('div');
|
||||
loadingContainer.innerHTML = loadingHTML;
|
||||
document.body.appendChild(loadingContainer.firstElementChild);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loading 숨기기
|
||||
*/
|
||||
hideLoading: () => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.remove();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Progress Bar 업데이트
|
||||
* @param {number} percent - 진행률 (0-100)
|
||||
*/
|
||||
updateProgress: (percent) => {
|
||||
const progressFill = document.querySelector('.progress-fill');
|
||||
if (progressFill) {
|
||||
progressFill.style.width = `${percent}%`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 4. Form Validator
|
||||
// ============================================
|
||||
const FormValidator = {
|
||||
/**
|
||||
* 이메일 검증
|
||||
* @param {string} email - 이메일 주소
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateEmail: (email) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
},
|
||||
|
||||
/**
|
||||
* 비밀번호 검증 (8자 이상, 영문+숫자 조합)
|
||||
* @param {string} password - 비밀번호
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validatePassword: (password) => {
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
|
||||
return passwordRegex.test(password);
|
||||
},
|
||||
|
||||
/**
|
||||
* 전화번호 검증 (한국 형식)
|
||||
* @param {string} phone - 전화번호
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validatePhone: (phone) => {
|
||||
const phoneRegex = /^01[0-9]-?[0-9]{3,4}-?[0-9]{4}$/;
|
||||
return phoneRegex.test(phone);
|
||||
},
|
||||
|
||||
/**
|
||||
* 필수 입력 검증
|
||||
* @param {string} value - 입력값
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateRequired: (value) => {
|
||||
return value && value.trim().length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Input 요소에 에러 표시
|
||||
* @param {HTMLElement} inputElement - Input 요소
|
||||
* @param {string} errorMessage - 에러 메시지
|
||||
*/
|
||||
showError: (inputElement, errorMessage) => {
|
||||
const inputGroup = inputElement.closest('.input-group');
|
||||
if (inputGroup) {
|
||||
inputGroup.classList.add('error');
|
||||
const errorElement = inputGroup.querySelector('.input-error');
|
||||
if (errorElement) {
|
||||
errorElement.textContent = errorMessage;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Input 요소의 에러 제거
|
||||
* @param {HTMLElement} inputElement - Input 요소
|
||||
*/
|
||||
clearError: (inputElement) => {
|
||||
const inputGroup = inputElement.closest('.input-group');
|
||||
if (inputGroup) {
|
||||
inputGroup.classList.remove('error');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 폼 전체 검증
|
||||
* @param {HTMLFormElement} formElement - Form 요소
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateForm: (formElement) => {
|
||||
let isValid = true;
|
||||
const inputs = formElement.querySelectorAll('input[required], textarea[required]');
|
||||
|
||||
inputs.forEach(input => {
|
||||
FormValidator.clearError(input);
|
||||
|
||||
// 필수 입력 체크
|
||||
if (!FormValidator.validateRequired(input.value)) {
|
||||
FormValidator.showError(input, '필수 입력 항목입니다.');
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 타입별 검증
|
||||
if (input.type === 'email' && !FormValidator.validateEmail(input.value)) {
|
||||
FormValidator.showError(input, '올바른 이메일 형식이 아닙니다.');
|
||||
isValid = false;
|
||||
} else if (input.type === 'password' && !FormValidator.validatePassword(input.value)) {
|
||||
FormValidator.showError(input, '비밀번호는 8자 이상, 영문과 숫자 조합이어야 합니다.');
|
||||
isValid = false;
|
||||
} else if (input.type === 'tel' && !FormValidator.validatePhone(input.value)) {
|
||||
FormValidator.showError(input, '올바른 전화번호 형식이 아닙니다.');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 5. Animation Helpers
|
||||
// ============================================
|
||||
const AnimationHelper = {
|
||||
/**
|
||||
* Fade In
|
||||
* @param {HTMLElement} element - 대상 요소
|
||||
* @param {number} duration - 지속 시간 (ms)
|
||||
*/
|
||||
fadeIn: (element, duration = 300) => {
|
||||
element.style.opacity = '0';
|
||||
element.style.display = 'block';
|
||||
|
||||
let opacity = 0;
|
||||
const increment = 50 / duration;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
opacity += increment;
|
||||
element.style.opacity = opacity;
|
||||
|
||||
if (opacity >= 1) {
|
||||
clearInterval(timer);
|
||||
element.style.opacity = '1';
|
||||
}
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fade Out
|
||||
* @param {HTMLElement} element - 대상 요소
|
||||
* @param {number} duration - 지속 시간 (ms)
|
||||
*/
|
||||
fadeOut: (element, duration = 300) => {
|
||||
let opacity = 1;
|
||||
const decrement = 50 / duration;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
opacity -= decrement;
|
||||
element.style.opacity = opacity;
|
||||
|
||||
if (opacity <= 0) {
|
||||
clearInterval(timer);
|
||||
element.style.opacity = '0';
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/**
|
||||
* Slide Down
|
||||
* @param {HTMLElement} element - 대상 요소
|
||||
* @param {number} duration - 지속 시간 (ms)
|
||||
*/
|
||||
slideDown: (element, duration = 300) => {
|
||||
element.style.height = '0';
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.display = 'block';
|
||||
|
||||
const targetHeight = element.scrollHeight;
|
||||
let height = 0;
|
||||
const increment = (targetHeight * 50) / duration;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
height += increment;
|
||||
element.style.height = `${height}px`;
|
||||
|
||||
if (height >= targetHeight) {
|
||||
clearInterval(timer);
|
||||
element.style.height = 'auto';
|
||||
element.style.overflow = 'visible';
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 6. Utility Functions
|
||||
// ============================================
|
||||
const Utils = {
|
||||
/**
|
||||
* 날짜 포맷 (YYYY.MM.DD)
|
||||
* @param {Date} date - 날짜 객체
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDate: (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}.${month}.${day}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 날짜 포맷 (YYYY-MM-DD)
|
||||
* @param {Date} date - 날짜 객체
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDateISO: (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 숫자 포맷 (천 단위 콤마)
|
||||
* @param {number} number - 숫자
|
||||
* @returns {string}
|
||||
*/
|
||||
formatNumber: (number) => {
|
||||
return number.toLocaleString('ko-KR');
|
||||
},
|
||||
|
||||
/**
|
||||
* 퍼센트 포맷
|
||||
* @param {number} value - 값
|
||||
* @param {number} decimals - 소수점 자릿수
|
||||
* @returns {string}
|
||||
*/
|
||||
formatPercent: (value, decimals = 1) => {
|
||||
return `${value.toFixed(decimals)}%`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 디바운스 (연속 호출 방지)
|
||||
* @param {Function} func - 실행할 함수
|
||||
* @param {number} wait - 대기 시간 (ms)
|
||||
* @returns {Function}
|
||||
*/
|
||||
debounce: (func, wait = 300) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 쓰로틀 (일정 시간마다 한 번만 실행)
|
||||
* @param {Function} func - 실행할 함수
|
||||
* @param {number} limit - 제한 시간 (ms)
|
||||
* @returns {Function}
|
||||
*/
|
||||
throttle: (func, limit = 300) => {
|
||||
let inThrottle;
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 랜덤 ID 생성
|
||||
* @returns {string}
|
||||
*/
|
||||
generateId: () => {
|
||||
return `id-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 요소가 뷰포트에 보이는지 확인
|
||||
* @param {HTMLElement} element - 대상 요소
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isInViewport: (element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 7. 예제 데이터
|
||||
// ============================================
|
||||
const SampleData = {
|
||||
// 사용자 정보
|
||||
user: {
|
||||
name: '정우진',
|
||||
businessName: '우진이네 고깃집',
|
||||
businessType: '한식당',
|
||||
location: '서울 강남구',
|
||||
email: 'woojin@example.com',
|
||||
phone: '010-1234-5678'
|
||||
},
|
||||
|
||||
// 이벤트 예제
|
||||
event: {
|
||||
id: 'evt-001',
|
||||
name: '우진이네 여름 특별 할인 이벤트',
|
||||
purpose: '신규 고객 유치',
|
||||
startDate: '2025.06.01',
|
||||
endDate: '2025.06.30',
|
||||
discountRate: 20,
|
||||
target: '20-30대 직장인',
|
||||
status: 'in_progress'
|
||||
},
|
||||
|
||||
// 이벤트 목록
|
||||
events: [
|
||||
{
|
||||
id: 'evt-001',
|
||||
name: '우진이네 여름 특별 할인 이벤트',
|
||||
status: 'in_progress',
|
||||
startDate: '2025.06.01',
|
||||
participants: 127
|
||||
},
|
||||
{
|
||||
id: 'evt-002',
|
||||
name: '신메뉴 출시 기념 이벤트',
|
||||
status: 'in_progress',
|
||||
startDate: '2025.05.15',
|
||||
participants: 89
|
||||
},
|
||||
{
|
||||
id: 'evt-003',
|
||||
name: '재방문 고객 감사 이벤트',
|
||||
status: 'completed',
|
||||
startDate: '2025.04.01',
|
||||
participants: 254
|
||||
}
|
||||
],
|
||||
|
||||
// 통계 데이터
|
||||
analytics: {
|
||||
totalEvents: 5,
|
||||
activeEvents: 2,
|
||||
completedEvents: 3,
|
||||
totalParticipants: 127,
|
||||
salesIncrease: 23,
|
||||
conversionRate: 15.4
|
||||
},
|
||||
|
||||
// AI 생성 콘텐츠
|
||||
aiContent: {
|
||||
plan: '여름 시즌 신규 고객 유치를 위한 20% 할인 이벤트입니다. 점심 시간대 직장인을 타겟으로 하며, SNS 공유 시 추가 쿠폰을 제공합니다.',
|
||||
images: [
|
||||
{ id: 1, url: 'https://via.placeholder.com/400x300?text=여름+특별+이미지1', selected: true },
|
||||
{ id: 2, url: 'https://via.placeholder.com/400x300?text=여름+특별+이미지2', selected: false },
|
||||
{ id: 3, url: 'https://via.placeholder.com/400x300?text=여름+특별+이미지3', selected: false }
|
||||
],
|
||||
improvement: '참여율 향상을 위해 쿠폰 사용 기한을 1주일에서 2주일로 연장하고, SNS 공유 혜택을 10%에서 15%로 상향 조정하는 것을 제안합니다.'
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 8. 초기화
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Back 버튼 초기화
|
||||
const backButtons = document.querySelectorAll('.back-button');
|
||||
backButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
NavManager.back();
|
||||
});
|
||||
});
|
||||
|
||||
// 예제 데이터 로드 (개발용)
|
||||
if (!AppState.loadUser()) {
|
||||
AppState.saveUser(SampleData.user);
|
||||
}
|
||||
});
|
||||
|
||||
// Export for use in other files
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
NavManager,
|
||||
AppState,
|
||||
UIManager,
|
||||
FormValidator,
|
||||
AnimationHelper,
|
||||
Utils,
|
||||
SampleData
|
||||
};
|
||||
}
|
||||
3769
design/uiux/uiux.md
3769
design/uiux/uiux.md
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user