first commit 2

This commit is contained in:
hmw1001
2026-06-11 18:47:38 +09:00
parent c768729ce6
commit 6f534e33a6
11095 changed files with 1595758 additions and 0 deletions
@@ -0,0 +1,148 @@
<?php
if (!defined('_GNUBOARD_')) exit;
// 디버깅: config.php 로드됨
error_log('survey_form config.php loaded');
// 설문 목록 가져오기
$survey_options = array();
$survey_sql = "SELECT sv_id, sv_title, sv_status FROM survey_master ORDER BY sv_created_at DESC";
$survey_result = sql_query($survey_sql);
while ($survey = sql_fetch_array($survey_result)) {
$status_text = '';
switch($survey['sv_status']) {
case 'active': $status_text = ' (진행중)'; break;
case 'draft': $status_text = ' (임시저장)'; break;
case 'closed': $status_text = ' (종료)'; break;
}
$survey_options[$survey['sv_id']] = $survey['sv_title'] . $status_text;
}
// 현재 설정값 가져오기
$current_sv_id = '';
if (isset($row_mod['md_custom_survey_key']) && $row_mod['md_custom_survey_key']) {
$custom_options = json_decode($row_mod['md_custom_survey_key'], true);
if (isset($custom_options['sv_id'])) {
$current_sv_id = $custom_options['sv_id'];
}
}
?>
<div class="module-config-section">
<h4><i class="fa fa-clipboard"></i> 설문 폼 모듈 설정</h4>
<div class="form-group">
<label for="survey_select">표시할 설문 선택:</label>
<select name="custom_options[sv_id]" id="survey_select" class="form-control">
<option value="">-- 설문을 선택하세요 --</option>
<?php foreach ($survey_options as $sv_id => $sv_title): ?>
<option value="<?php echo $sv_id; ?>" <?php echo ($current_sv_id == $sv_id) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($sv_title); ?>
</option>
<?php endforeach; ?>
</select>
<small class="form-text text-muted">
설문을 선택하지 않으면 사용자가 직접 설문을 선택할 수 있는 인터페이스가 표시됩니다.
</small>
</div>
<?php if (empty($survey_options)): ?>
<div class="alert alert-info">
<i class="fa fa-info-circle"></i>
등록된 설문이 없습니다.
<a href="<?php echo G5_ADMIN_URL; ?>/survey_manage/survey_form.php" target="_blank">새 설문 만들기</a>
</div>
<?php endif; ?>
<div class="form-group">
<label>모듈 사용법:</label>
<ul class="usage-list">
<li>위에서 설문을 선택하면 해당 설문이 자동으로 표시됩니다.</li>
<li>설문을 선택하지 않으면 사용자가 설문을 선택할 수 있는 목록이 표시됩니다.</li>
<li>URL에 <code>?sv_id=설문번호</code>를 추가하면 특정 설문을 강제로 표시할 수 있습니다.</li>
</ul>
</div>
</div>
<style>
.module-config-section {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
margin-bottom: 20px;
}
.module-config-section h4 {
margin-top: 0;
color: #333;
border-bottom: 2px solid #AA20FF;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 5px;
color: #333;
}
.form-control {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-text {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.alert {
padding: 12px 15px;
border-radius: 4px;
margin-bottom: 15px;
}
.alert-info {
background-color: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
}
.usage-list {
margin: 0;
padding-left: 20px;
}
.usage-list li {
margin-bottom: 5px;
color: #666;
}
.usage-list code {
background: #f1f1f1;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const surveySelect = document.getElementById('survey_select');
if (surveySelect) {
surveySelect.addEventListener('change', function() {
// 설문 선택 시 미리보기나 추가 정보를 표시할 수 있습니다.
console.log('선택된 설문 ID:', this.value);
});
}
});
</script>
@@ -0,0 +1,636 @@
/* 설문 폼 모듈 스타일 */
.survey-form-module {
--survey-primary: #AA20FF;
--survey-primary-light: rgba(170, 32, 255, 0.1);
--survey-primary-dark: #8A1ACC;
}
.survey-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.survey-header {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: white;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
position: relative;
overflow: hidden;
}
.survey-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(90deg, var(--survey-primary) 0%, var(--survey-primary-dark) 100%);
}
.survey-title {
font-size: 2.5em;
font-weight: 700;
color: #333;
margin-bottom: 15px;
line-height: 1.2;
}
.survey-description {
font-size: 1.1em;
color: #666;
line-height: 1.6;
margin-bottom: 20px;
}
.survey-info {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
flex-wrap: wrap;
}
.survey-info-item {
display: flex;
align-items: center;
gap: 8px;
color: #888;
font-size: 0.9em;
}
.survey-info-item i {
color: var(--survey-primary);
}
.progress-container {
position: sticky;
top: 20px;
background: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
margin-bottom: 30px;
z-index: 100;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--survey-primary) 0%, var(--survey-primary-dark) 100%);
border-radius: 4px;
transition: width 0.5s ease;
width: 0%;
}
.progress-text {
text-align: center;
margin-top: 10px;
font-size: 0.9em;
color: #666;
}
.survey-form {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.question-container {
margin-bottom: 40px;
padding-bottom: 30px;
border-bottom: 1px solid #f0f0f0;
animation: fadeInUp 0.6s ease forwards;
opacity: 0;
}
.question-container:last-child {
border-bottom: none;
margin-bottom: 0;
}
.question-header {
margin-bottom: 20px;
}
.question-number {
display: inline-block;
width: 30px;
height: 30px;
background: var(--survey-primary);
color: white;
border-radius: 50%;
text-align: center;
line-height: 30px;
font-weight: bold;
font-size: 0.9em;
margin-right: 15px;
}
.question-title {
font-size: 1.3em;
font-weight: 600;
color: #333;
line-height: 1.4;
display: inline;
}
.question-required {
color: #e74c3c;
margin-left: 5px;
}
.question-description {
margin-top: 10px;
color: #666;
font-size: 0.95em;
line-height: 1.5;
margin-left: 45px;
}
.question-input {
margin-left: 45px;
}
/* 텍스트 입력 */
.form-input {
width: 100%;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 1em;
transition: all 0.3s ease;
background: #fafafa;
}
.form-input:focus {
outline: none;
border-color: var(--survey-primary);
background: white;
box-shadow: 0 0 0 3px var(--survey-primary-light);
}
.form-textarea {
min-height: 120px;
resize: vertical;
font-family: inherit;
}
/* 라디오 버튼 */
.radio-group,
.checkbox-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.radio-item,
.checkbox-item {
display: flex;
align-items: center;
padding: 15px 20px;
background: #fafafa;
border: 2px solid #e0e0e0;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.radio-item:hover,
.checkbox-item:hover {
background: var(--survey-primary-light);
border-color: var(--survey-primary);
}
.radio-item.selected,
.checkbox-item.selected {
background: var(--survey-primary-light);
border-color: var(--survey-primary);
color: var(--survey-primary-dark);
}
.radio-item input,
.checkbox-item input {
margin-right: 15px;
transform: scale(1.2);
accent-color: var(--survey-primary);
}
.radio-item label,
.checkbox-item label {
flex: 1;
cursor: pointer;
font-size: 1em;
line-height: 1.4;
}
/* 셀렉트 박스 */
.form-select {
width: 100%;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 1em;
background: #fafafa;
cursor: pointer;
transition: all 0.3s ease;
}
.form-select:focus {
outline: none;
border-color: var(--survey-primary);
background: white;
box-shadow: 0 0 0 3px var(--survey-primary-light);
}
/* 평점 */
.rating-container {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
.rating-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 15px;
border-radius: 10px;
transition: all 0.3s ease;
min-width: 60px;
background: #fafafa;
border: 2px solid #e0e0e0;
}
.rating-item:hover {
background: var(--survey-primary-light);
border-color: var(--survey-primary);
}
.rating-item.selected {
background: var(--survey-primary);
color: white;
border-color: var(--survey-primary);
}
.rating-number {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 5px;
}
.rating-label {
font-size: 0.8em;
text-align: center;
line-height: 1.2;
}
/* 날짜 입력 */
.form-date {
width: 100%;
max-width: 300px;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 1em;
background: #fafafa;
transition: all 0.3s ease;
}
.form-date:focus {
outline: none;
border-color: var(--survey-primary);
background: white;
box-shadow: 0 0 0 3px var(--survey-primary-light);
}
/* 제출 버튼 */
.submit-container {
text-align: center;
margin-top: 40px;
padding-top: 30px;
border-top: 1px solid #f0f0f0;
}
.submit-btn {
background: linear-gradient(135deg, var(--survey-primary) 0%, var(--survey-primary-dark) 100%);
color: white;
border: none;
padding: 18px 50px;
font-size: 1.1em;
font-weight: 600;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
gap: 10px;
}
.submit-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
.submit-btn:active {
transform: translateY(0);
}
.submit-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.submit-btn .loading-spinner {
display: none;
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.3);
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.submit-btn.loading .loading-spinner {
display: block;
}
/* 확인 모달 */
.confirm-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1050;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.confirm-modal.show {
display: block;
opacity: 1;
visibility: visible;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
animation: fadeIn 0.3s ease;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 500px;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
animation: slideUp 0.4s ease-out;
}
.modal-close {
position: absolute;
top: 20px;
right: 25px;
font-size: 2rem;
color: #aaa;
background: none;
border: none;
cursor: pointer;
line-height: 1;
z-index: 1;
transition: color 0.3s ease;
}
.modal-close:hover {
color: #333;
}
.modal-header {
padding: 30px 30px 20px;
border-bottom: 1px solid #f0f0f0;
}
.modal-header h3 {
font-size: 1.5em;
font-weight: 600;
color: #333;
margin: 0;
}
.modal-body {
padding: 30px;
}
.modal-body p {
color: #666;
line-height: 1.6;
margin-bottom: 15px;
}
.modal-warning {
color: #e74c3c;
font-size: 0.9em;
font-style: italic;
}
.modal-footer {
padding: 20px 30px 30px;
display: flex;
gap: 15px;
justify-content: flex-end;
}
.btn-cancel {
padding: 12px 24px;
background: #f8f9fa;
color: #666;
border: 1px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-cancel:hover {
background: #e9ecef;
color: #333;
}
.btn-confirm {
padding: 12px 24px;
background: var(--survey-primary);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-confirm:hover {
background: var(--survey-primary-dark);
}
/* 반응형 */
@media (max-width: 768px) {
.survey-container {
padding: 10px;
}
.survey-form {
padding: 20px;
border-radius: 15px;
}
.survey-title {
font-size: 2em;
}
.survey-info {
flex-direction: column;
gap: 15px;
}
.question-input {
margin-left: 0;
}
.question-description {
margin-left: 0;
}
.rating-container {
gap: 8px;
}
.rating-item {
min-width: 50px;
padding: 10px;
}
.modal-content {
width: 95%;
margin: 20px;
}
.modal-header,
.modal-body,
.modal-footer {
padding: 20px;
}
.modal-footer {
flex-direction: column;
}
.btn-cancel,
.btn-confirm {
width: 100%;
text-align: center;
}
}
/* 애니메이션 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translate(-50%, -40%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 질문 애니메이션 지연 */
.question-container:nth-child(1) { animation-delay: 0.1s; }
.question-container:nth-child(2) { animation-delay: 0.2s; }
.question-container:nth-child(3) { animation-delay: 0.3s; }
.question-container:nth-child(4) { animation-delay: 0.4s; }
.question-container:nth-child(5) { animation-delay: 0.5s; }
/* 스크롤 하이라이트 효과 */
.question-container.highlight {
transform: scale(1.02);
box-shadow: 0 5px 20px rgba(170, 32, 255, 0.1);
border-color: var(--survey-primary-light);
}
/* 유효성 검사 오류 스타일 */
.form-input.error,
.form-select.error {
border-color: #e74c3c;
background: #fdf2f2;
}
.form-input.error:focus,
.form-select.error:focus {
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.1);
}
.error-message {
color: #e74c3c;
font-size: 0.9em;
margin-top: 8px;
margin-left: 45px;
display: none;
}
.error-message.show {
display: block;
animation: fadeInUp 0.3s ease;
}
@@ -0,0 +1,507 @@
function initSurveyFormModule(moduleId) {
const moduleElement = document.getElementById(moduleId);
if (!moduleElement || moduleElement.classList.contains('initialized')) return;
// 설문 데이터 가져오기
const surveyData = JSON.parse(moduleElement.dataset.survey || '{}');
const { survey, questions, theme_color } = surveyData;
// DOM 요소들
const questionsContainer = moduleElement.querySelector('#questionsContainer');
const progressFill = moduleElement.querySelector('#progressFill');
const progressText = moduleElement.querySelector('#progressText');
const currentQuestionSpan = moduleElement.querySelector('#currentQuestion');
const submitBtn = moduleElement.querySelector('#submitBtn');
const surveyForm = moduleElement.querySelector('#surveyForm');
const confirmModal = moduleElement.querySelector('.confirm-modal');
const modalOverlay = confirmModal.querySelector('.modal-overlay');
const modalClose = confirmModal.querySelector('.modal-close');
const btnCancel = confirmModal.querySelector('.btn-cancel');
const btnConfirm = confirmModal.querySelector('.btn-confirm');
// CSS 변수 설정
if (theme_color) {
moduleElement.style.setProperty('--survey-primary', theme_color);
moduleElement.style.setProperty('--survey-primary-light', theme_color + '20');
moduleElement.style.setProperty('--survey-primary-dark', theme_color + 'CC');
}
// 질문 렌더링
function renderQuestions() {
if (!questions || !questions.length) return;
const questionsHTML = questions.map((question, index) => {
const fieldName = `answer_${question.sq_id}`;
const required = question.sq_required ? 'required' : '';
const requiredMark = question.sq_required ? '<span class="question-required">*</span>' : '';
let inputHTML = '';
switch (question.sq_type) {
case 'text':
inputHTML = `
<input type="text"
name="${fieldName}"
class="form-input"
placeholder="답변을 입력해주세요"
${required}>
`;
break;
case 'textarea':
inputHTML = `
<textarea name="${fieldName}"
class="form-input form-textarea"
placeholder="자세한 답변을 입력해주세요"
${required}></textarea>
`;
break;
case 'radio':
if (question.sq_options && Array.isArray(question.sq_options)) {
inputHTML = `
<div class="radio-group">
${question.sq_options.map((option, optionIndex) => `
<div class="radio-item">
<input type="radio"
name="${fieldName}"
value="${escapeHtml(option)}"
id="${fieldName}_${optionIndex}"
${required}>
<label for="${fieldName}_${optionIndex}">
${escapeHtml(option)}
</label>
</div>
`).join('')}
</div>
`;
}
break;
case 'checkbox':
if (question.sq_options && Array.isArray(question.sq_options)) {
inputHTML = `
<div class="checkbox-group">
${question.sq_options.map((option, optionIndex) => `
<div class="checkbox-item">
<input type="checkbox"
name="${fieldName}[]"
value="${escapeHtml(option)}"
id="${fieldName}_${optionIndex}">
<label for="${fieldName}_${optionIndex}">
${escapeHtml(option)}
</label>
</div>
`).join('')}
</div>
`;
}
break;
case 'select':
if (question.sq_options && Array.isArray(question.sq_options)) {
inputHTML = `
<select name="${fieldName}" class="form-select" ${required}>
<option value="">선택해주세요</option>
${question.sq_options.map(option => `
<option value="${escapeHtml(option)}">
${escapeHtml(option)}
</option>
`).join('')}
</select>
`;
}
break;
case 'rating':
const scale = question.sq_options?.scale || 5;
const labels = question.sq_options?.labels || [];
inputHTML = `
<div class="rating-container">
${Array.from({ length: scale }, (_, i) => i + 1).map(value => `
<div class="rating-item" data-value="${value}">
<input type="radio"
name="${fieldName}"
value="${value}"
id="${fieldName}_${value}"
style="display: none;"
${required}>
<div class="rating-number">${value}</div>
${labels[value - 1] ? `<div class="rating-label">${escapeHtml(labels[value - 1])}</div>` : ''}
</div>
`).join('')}
</div>
`;
break;
case 'date':
inputHTML = `
<input type="date"
name="${fieldName}"
class="form-date"
${required}>
`;
break;
}
return `
<div class="question-container" data-question="${index + 1}">
<div class="question-header">
<span class="question-number">${index + 1}</span>
<h3 class="question-title">
${escapeHtml(question.sq_title)}
${requiredMark}
</h3>
${question.sq_description ? `
<p class="question-description">${escapeHtml(question.sq_description)}</p>
` : ''}
</div>
<div class="question-input">
${inputHTML}
<div class="error-message" id="error_${question.sq_id}"></div>
</div>
</div>
`;
}).join('');
questionsContainer.innerHTML = questionsHTML;
}
// HTML 이스케이프 함수
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 진행률 업데이트
function updateProgress() {
let answeredQuestions = 0;
const totalQuestions = questions.length;
questions.forEach((question, index) => {
const questionContainer = questionsContainer.children[index];
if (!questionContainer) return;
const inputs = questionContainer.querySelectorAll('input, textarea, select');
let hasAnswer = false;
inputs.forEach(input => {
if (input.type === 'radio' || input.type === 'checkbox') {
if (input.checked) hasAnswer = true;
} else if (input.value.trim() !== '') {
hasAnswer = true;
}
});
if (hasAnswer) {
answeredQuestions++;
}
});
const progress = Math.round((answeredQuestions / totalQuestions) * 100);
progressFill.style.width = progress + '%';
progressText.textContent = progress + '%';
currentQuestionSpan.textContent = answeredQuestions;
// 모든 필수 질문이 답변되었는지 확인
const requiredQuestions = questionsContainer.querySelectorAll('input[required], textarea[required], select[required]');
let allRequiredAnswered = true;
requiredQuestions.forEach(input => {
if (input.type === 'radio') {
const radioGroup = questionsContainer.querySelectorAll(`input[name="${input.name}"]`);
let radioChecked = false;
radioGroup.forEach(radio => {
if (radio.checked) radioChecked = true;
});
if (!radioChecked) allRequiredAnswered = false;
} else if (input.type === 'checkbox') {
const checkboxGroup = questionsContainer.querySelectorAll(`input[name="${input.name}"]`);
let checkboxChecked = false;
checkboxGroup.forEach(checkbox => {
if (checkbox.checked) checkboxChecked = true;
});
if (!checkboxChecked) allRequiredAnswered = false;
} else if (input.value.trim() === '') {
allRequiredAnswered = false;
}
});
submitBtn.disabled = !allRequiredAnswered;
}
// 이벤트 리스너 바인딩
function bindEvents() {
// 라디오/체크박스 스타일링
questionsContainer.addEventListener('click', (e) => {
const radioItem = e.target.closest('.radio-item');
const checkboxItem = e.target.closest('.checkbox-item');
if (radioItem) {
const input = radioItem.querySelector('input');
const groupName = input.name;
// 라디오 버튼 그룹의 다른 선택 해제
questionsContainer.querySelectorAll(`input[name="${groupName}"]`).forEach(radio => {
radio.closest('.radio-item').classList.remove('selected');
});
input.checked = true;
radioItem.classList.add('selected');
updateProgress();
}
if (checkboxItem) {
const input = checkboxItem.querySelector('input');
input.checked = !input.checked;
checkboxItem.classList.toggle('selected', input.checked);
updateProgress();
}
});
// 평점 클릭 이벤트
questionsContainer.addEventListener('click', (e) => {
const ratingItem = e.target.closest('.rating-item');
if (ratingItem) {
const value = ratingItem.dataset.value;
const input = ratingItem.querySelector('input');
const container = ratingItem.closest('.rating-container');
// 같은 그룹의 다른 평점 해제
container.querySelectorAll('.rating-item').forEach(item => {
item.classList.remove('selected');
});
// 현재 평점 선택
ratingItem.classList.add('selected');
input.checked = true;
updateProgress();
}
});
// 일반 입력 필드 이벤트
questionsContainer.addEventListener('input', updateProgress);
questionsContainer.addEventListener('change', updateProgress);
// 폼 제출 이벤트
surveyForm.addEventListener('submit', (e) => {
e.preventDefault();
if (submitBtn.disabled) {
showNotification('모든 필수 항목을 입력해주세요.', 'error');
return;
}
// 확인 모달 표시
showConfirmModal();
});
// 모달 이벤트
modalClose.addEventListener('click', hideConfirmModal);
btnCancel.addEventListener('click', hideConfirmModal);
modalOverlay.addEventListener('click', hideConfirmModal);
btnConfirm.addEventListener('click', () => {
hideConfirmModal();
submitSurvey();
});
// ESC 키로 모달 닫기
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && confirmModal.classList.contains('show')) {
hideConfirmModal();
}
});
}
// 확인 모달 표시
function showConfirmModal() {
confirmModal.classList.add('show');
document.body.style.overflow = 'hidden';
}
// 확인 모달 숨기기
function hideConfirmModal() {
confirmModal.classList.remove('show');
document.body.style.overflow = '';
}
// 설문 제출
function submitSurvey() {
submitBtn.classList.add('loading');
submitBtn.innerHTML = '<div class="loading-spinner"></div> 제출 중...';
submitBtn.disabled = true;
const formData = new FormData(surveyForm);
formData.append('ajax', '1');
fetch(surveyForm.action, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('설문이 성공적으로 제출되었습니다!', 'success');
setTimeout(() => {
window.location.href = data.redirect_url;
}, 1500);
} else {
showNotification(data.errors ? data.errors.join('\n') : '제출 중 오류가 발생했습니다.', 'error');
submitBtn.classList.remove('loading');
submitBtn.innerHTML = '<i class="fa fa-paper-plane"></i> 설문 제출하기';
submitBtn.disabled = false;
}
})
.catch(error => {
console.error('Error:', error);
showNotification('제출 중 오류가 발생했습니다.', 'error');
submitBtn.classList.remove('loading');
submitBtn.innerHTML = '<i class="fa fa-paper-plane"></i> 설문 제출하기';
submitBtn.disabled = false;
});
}
// 알림 표시
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `survey-notification survey-notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<i class="fa fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
</div>
`;
// 스타일 추가
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : '#17a2b8'};
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
z-index: 1060;
animation: slideInRight 0.3s ease;
max-width: 300px;
word-wrap: break-word;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOutRight 0.3s ease';
setTimeout(() => {
notification.remove();
}, 300);
}, 5000);
}
// 스크롤 시 질문 하이라이트
function initScrollHighlight() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 모든 하이라이트 제거
questionsContainer.querySelectorAll('.question-container').forEach(q => {
q.classList.remove('highlight');
});
// 현재 질문 하이라이트
entry.target.classList.add('highlight');
}
});
}, {
threshold: 0.5,
rootMargin: '-20% 0px -20% 0px'
});
questionsContainer.querySelectorAll('.question-container').forEach(question => {
observer.observe(question);
});
}
// 질문 애니메이션 초기화
function initQuestionAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.animationPlayState = 'running';
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
questionsContainer.querySelectorAll('.question-container').forEach(question => {
question.style.opacity = '0';
question.style.animationPlayState = 'paused';
observer.observe(question);
});
}
// 초기화
function init() {
renderQuestions();
bindEvents();
updateProgress();
initScrollHighlight();
initQuestionAnimations();
moduleElement.classList.add('initialized');
}
// 모듈 초기화 실행
init();
}
// 추가 CSS 애니메이션 (JavaScript로 동적 추가)
const additionalStyles = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.survey-notification .notification-content {
display: flex;
align-items: center;
gap: 10px;
}
.survey-notification i {
font-size: 1.2em;
flex-shrink: 0;
}
`;
// 스타일 추가 (한 번만)
if (!document.getElementById('survey-form-module-styles')) {
const styleSheet = document.createElement('style');
styleSheet.id = 'survey-form-module-styles';
styleSheet.textContent = additionalStyles;
document.head.appendChild(styleSheet);
}
@@ -0,0 +1,274 @@
<?php
if (!defined('_GNUBOARD_')) exit;
// 설문 관리 라이브러리 로드
$survey_lib_path = G5_ADMIN_PATH.'/survey_manage/lib/survey.lib.php';
if (!file_exists($survey_lib_path)) {
echo '<div class="alert alert-danger">설문 라이브러리 파일이 없습니다.</div>';
return;
}
include_once($survey_lib_path);
// 모듈 설정에서 설문 ID 가져오기 (rb.custom 모듈 방식)
$sv_id = 0;
// 1. 모듈 설정에서 sv_id 확인 (rb_module 테이블의 설정값)
if (isset($row_mod['md_custom_survey_key']) && $row_mod['md_custom_survey_key']) {
$sv_id = (int)$row_mod['md_custom_survey_key'];
}
// 2. URL 파라미터에서 sv_id 확인 (우선순위 높음)
if (isset($_GET['sv_id']) && $_GET['sv_id']) {
$sv_id = (int)$_GET['sv_id'];
}
else if (isset($_POST['sv_id']) && $_POST['sv_id']) {
$sv_id = (int)$_POST['sv_id'];
}
// sv_id가 없으면 설문 선택 인터페이스 표시
if (!$sv_id && $sv_id == 0) {
echo '<div class="survey-form-module">';
echo '<div class="survey-container">';
echo '<div class="survey-header">';
echo '<div class="container">';
echo '<h3>설문을 선택해주세요</h3>';
echo '<p>모듈 설정에서 설문을 선택하거나, URL에 ?sv_id=설문번호를 추가하여 특정 설문을 표시할 수 있습니다.</p>';
// 모든 설문 목록 표시 (관리자용)
if ($is_admin) {
$all_surveys = sql_query("SELECT sv_id, sv_title, sv_status, sv_start_date, sv_end_date FROM survey_master ORDER BY sv_created_at DESC LIMIT 10");
if (sql_num_rows($all_surveys) > 0) {
echo '<div class="survey-list admin-survey-list">';
echo '<h4>설문 목록 (관리자 전용):</h4>';
echo '<div class="survey-grid">';
while ($survey_item = sql_fetch_array($all_surveys)) {
$status_class = '';
$status_text = '';
switch($survey_item['sv_status']) {
case 'active': $status_class = 'status-active'; $status_text = '진행중'; break;
case 'draft': $status_class = 'status-draft'; $status_text = '임시저장'; break;
case 'closed': $status_class = 'status-closed'; $status_text = '종료'; break;
default: $status_class = 'status-default'; $status_text = $survey_item['sv_status']; break;
}
echo '<div class="survey-card">';
echo '<div class="survey-card-header">';
echo '<h5><a href="?sv_id='.$survey_item['sv_id'].'">'.$survey_item['sv_title'].'</a></h5>';
echo '<span class="survey-status '.$status_class.'">'.$status_text.'</span>';
echo '</div>';
echo '<div class="survey-card-body">';
echo '<p>기간: '.date('Y-m-d', strtotime($survey_item['sv_start_date'])).' ~ '.date('Y-m-d', strtotime($survey_item['sv_end_date'])).'</p>';
echo '<a href="?sv_id='.$survey_item['sv_id'].'" class="btn-view-survey">설문 보기</a>';
echo '</div>';
echo '</div>';
}
echo '</div>';
echo '</div>';
}
} else {
// 일반 사용자용 - 활성화된 설문만 표시
$active_surveys = sql_query("SELECT sv_id, sv_title, sv_description FROM survey_master WHERE sv_status = 'active' AND sv_start_date <= NOW() AND sv_end_date >= NOW() ORDER BY sv_created_at DESC LIMIT 5");
if (sql_num_rows($active_surveys) > 0) {
echo '<div class="survey-list">';
echo '<h4>현재 진행 중인 설문:</h4>';
echo '<div class="survey-grid">';
while ($survey_item = sql_fetch_array($active_surveys)) {
echo '<div class="survey-card">';
echo '<div class="survey-card-header">';
echo '<h5><a href="?sv_id='.$survey_item['sv_id'].'">'.$survey_item['sv_title'].'</a></h5>';
echo '</div>';
if ($survey_item['sv_description']) {
echo '<div class="survey-card-body">';
echo '<p>'.htmlspecialchars(mb_substr($survey_item['sv_description'], 0, 100)).'...</p>';
echo '<a href="?sv_id='.$survey_item['sv_id'].'" class="btn-view-survey">참여하기</a>';
echo '</div>';
}
echo '</div>';
}
echo '</div>';
echo '</div>';
} else {
echo '<div class="no-surveys">';
echo '<i class="fa fa-clipboard" style="font-size: 3em; color: #ddd; margin-bottom: 15px;"></i>';
echo '<p>현재 진행 중인 설문이 없습니다.</p>';
echo '</div>';
}
}
echo '</div></div></div></div>';
// 기본 스타일 추가
echo '<style>
.survey-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; margin-top: 15px; }
.survey-card { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: white; }
.survey-card-header { margin-bottom: 10px; }
.survey-card-header h5 { margin: 0 0 5px 0; }
.survey-card-header a { color: #333; text-decoration: none; }
.survey-card-header a:hover { color: #AA20FF; }
.survey-status { padding: 3px 8px; border-radius: 12px; font-size: 0.8em; }
.status-active { background: #d4edda; color: #155724; }
.status-draft { background: #fff3cd; color: #856404; }
.status-closed { background: #f8d7da; color: #721c24; }
.btn-view-survey { display: inline-block; padding: 8px 15px; background: #AA20FF; color: white; text-decoration: none; border-radius: 4px; font-size: 0.9em; }
.btn-view-survey:hover { background: #8A1ACC; color: white; }
.no-surveys { text-align: center; padding: 40px; color: #666; }
</style>';
return;
}
// 함수 존재 확인
if (!function_exists('validate_survey_access')) {
echo '<div class="alert alert-danger">설문 검증 함수가 정의되지 않았습니다.</div>';
return;
}
// 설문 유효성 검사
$validation = validate_survey_access($sv_id, $member['mb_id'], $_SERVER['REMOTE_ADDR']);
if (!$validation['success']) {
echo '<div class="alert alert-warning">'.$validation['message'].'</div>';
return;
}
$survey = $validation['survey'];
$questions = get_survey_questions($sv_id);
if (empty($questions)) {
echo '<div class="alert alert-info">이 설문에는 아직 질문이 등록되지 않았습니다.</div>';
return;
}
// 설문 테마 색상
$theme_color = $survey['sv_theme_color'] ?: '#AA20FF';
// 설문 데이터를 JSON으로 준비
$survey_data = array(
'survey' => $survey,
'questions' => $questions,
'theme_color' => $theme_color,
'total_responses' => get_survey_response_count($sv_id, 'completed')
);
$survey_json = json_encode($survey_data, JSON_UNESCAPED_UNICODE);
// CSS와 JS 파일의 버전을 파일 수정 시간으로 자동 갱신
$module_css_path = G5_THEME_PATH.'/rb.custom/survey_form/module.css';
$module_js_path = G5_THEME_PATH.'/rb.custom/survey_form/module.js';
$module_css_ver = file_exists($module_css_path) ? filemtime($module_css_path) : G5_CSS_VER;
$module_js_ver = file_exists($module_js_path) ? filemtime($module_js_path) : G5_JS_VER;
// 이 모듈만의 고유 ID를 생성합니다.
$module_id = 'survey_form_module_'.uniqid();
?>
<!-- 모듈의 가장 바깥 요소에 고유 ID를 부여합니다. -->
<div id="<?php echo $module_id; ?>" class="survey-form-module" data-survey='<?php echo htmlspecialchars($survey_json, ENT_QUOTES, 'UTF-8'); ?>'>
<div class="survey-container">
<!-- 설문 헤더 -->
<div class="survey-header">
<div class="container">
<h1 class="survey-title"><?php echo htmlspecialchars($survey['sv_title']); ?></h1>
<?php if ($survey['sv_description']): ?>
<p class="survey-description"><?php echo nl2br(htmlspecialchars($survey['sv_description'])); ?></p>
<?php endif; ?>
<div class="survey-info">
<div class="survey-info-item">
<i class="fa fa-clock"></i>
<span>예상 소요시간: <span class="estimated-time"><?php echo count($questions); ?>분</span></span>
</div>
<div class="survey-info-item">
<i class="fa fa-question-circle"></i>
<span>총 <span class="total-questions"><?php echo count($questions); ?></span>개 질문</span>
</div>
<div class="survey-info-item">
<i class="fa fa-users"></i>
<span><span class="total-participants"><?php echo number_format(get_survey_response_count($sv_id, 'completed')); ?></span>명 참여</span>
</div>
</div>
</div>
</div>
<!-- 진행률 바 -->
<div class="progress-container">
<div class="container">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text">
<span id="progressText">0%</span> 완료 (<span id="currentQuestion">0</span> / <span class="total-questions"><?php echo count($questions); ?></span>)
</div>
</div>
</div>
<!-- 설문 폼 -->
<div class="survey-form-container">
<div class="container">
<form id="surveyForm" class="survey-form" method="post" action="<?php echo G5_THEME_URL; ?>/rb.custom/survey_form/survey_submit_page.php">
<input type="hidden" name="sv_id" value="<?php echo $sv_id; ?>">
<div class="questions-container" id="questionsContainer">
<!-- JavaScript로 질문들이 동적으로 생성됩니다 -->
</div>
<div class="submit-container">
<button type="submit" class="submit-btn" id="submitBtn" disabled>
<i class="fa fa-paper-plane"></i> 설문 제출하기
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 확인 모달 -->
<div class="confirm-modal">
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h3>설문 제출 확인</h3>
<button type="button" class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p>설문을 제출하시겠습니까?</p>
<p class="modal-warning">제출 후에는 수정할 수 없습니다.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn-cancel">취소</button>
<button type="button" class="btn-confirm">제출하기</button>
</div>
</div>
</div>
</div>
<!-- 이 모듈에 필요한 CSS 파일을 불러옵니다. -->
<link rel="stylesheet" href="<?php echo G5_THEME_URL; ?>/rb.custom/survey_form/module.css?ver=<?php echo $module_css_ver; ?>">
<script>
(function() {
const currentModuleId = '<?php echo $module_id; ?>';
const initFunctionName = 'initSurveyFormModule';
const scriptId = 'survey-form-module-script';
if (document.getElementById(scriptId)) {
if (typeof window[initFunctionName] === 'function') {
window[initFunctionName](currentModuleId);
}
return;
}
const script = document.createElement('script');
script.id = scriptId;
script.src = '<?php echo G5_THEME_URL; ?>/rb.custom/survey_form/module.js?ver=<?php echo $module_js_ver; ?>';
script.async = true;
script.onload = () => {
if (typeof window[initFunctionName] === 'function') {
window[initFunctionName](currentModuleId);
}
};
document.head.appendChild(script);
})();
</script>
@@ -0,0 +1,563 @@
<?php
include_once('../../_common.php');
// 설문 관리 라이브러리 로드
include_once(G5_ADMIN_PATH . '/survey_manage/lib/survey.lib.php');
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
$sr_id = isset($_GET['sr_id']) ? (int)$_GET['sr_id'] : 0;
if (!$sv_id || !$sr_id) {
alert('잘못된 접근입니다.', G5_URL);
}
$survey = get_survey($sv_id);
if (!$survey) {
alert('존재하지 않는 설문입니다.', G5_URL);
}
// 응답 확인
$response = sql_fetch("SELECT * FROM survey_responses WHERE sr_id = '$sr_id' AND sv_id = '$sv_id'");
if (!$response || $response['sr_status'] !== 'completed') {
alert('완료되지 않은 응답입니다.', G5_URL);
}
$g5['title'] = '설문 완료 - ' . $survey['sv_title'];
include_once(G5_THEME_PATH . '/head.sub.php');
// 설문 테마 색상
$theme_color = $survey['sv_theme_color'] ?: '#AA20FF';
// 응답 통계
$total_responses = get_survey_response_count($sv_id, 'completed');
?>
<style>
/* 페이지 전용 스타일 */
:root {
--survey-primary: <?php echo $theme_color; ?>;
--survey-primary-light: <?php echo $theme_color; ?>20;
--survey-primary-dark: <?php echo $theme_color; ?>CC;
}
body {
background: linear-gradient(135deg, var(--survey-primary-light) 0%, #ffffff 100%);
min-height: 100vh;
}
.complete-page-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 100px 20px 20px;
}
.complete-card {
background: white;
border-radius: 20px;
padding: 60px 40px;
text-align: center;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
animation: slideInUp 0.8s ease;
max-width: 600px;
width: 100%;
}
.complete-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(90deg, var(--survey-primary) 0%, var(--survey-primary-dark) 100%);
}
.success-icon {
width: 100px;
height: 100px;
background: var(--survey-primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 30px;
animation: bounceIn 1s ease 0.3s both;
}
.success-icon i {
font-size: 3em;
color: white;
}
.complete-title {
font-size: 2.5em;
font-weight: 700;
color: #333;
margin-bottom: 20px;
animation: fadeInUp 0.8s ease 0.5s both;
}
.complete-message {
font-size: 1.2em;
color: #666;
line-height: 1.6;
margin-bottom: 30px;
animation: fadeInUp 0.8s ease 0.7s both;
}
.thank-message {
background: var(--survey-primary-light);
padding: 20px;
border-radius: 10px;
margin: 30px 0;
font-style: italic;
color: var(--survey-primary-dark);
animation: fadeInUp 0.8s ease 0.9s both;
}
.stats-container {
display: flex;
justify-content: center;
gap: 30px;
margin: 40px 0;
animation: fadeInUp 0.8s ease 1.1s both;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2em;
font-weight: bold;
color: var(--survey-primary);
display: block;
}
.stat-label {
font-size: 0.9em;
color: #666;
margin-top: 5px;
}
.action-buttons {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 40px;
animation: fadeInUp 0.8s ease 1.3s both;
}
.btn {
padding: 15px 30px;
border-radius: 50px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
border: none;
cursor: pointer;
font-size: 1em;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--survey-primary);
color: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.btn-primary:hover {
background: var(--survey-primary-dark);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
color: white;
}
.btn-outline {
background: transparent;
color: var(--survey-primary);
border: 2px solid var(--survey-primary);
}
.btn-outline:hover {
background: var(--survey-primary);
color: white;
transform: translateY(-2px);
}
.social-share {
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid #f0f0f0;
animation: fadeInUp 0.8s ease 1.5s both;
}
.social-share h4 {
color: #666;
margin-bottom: 15px;
font-size: 1em;
}
.social-buttons {
display: flex;
justify-content: center;
gap: 10px;
}
.social-btn {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: white;
transition: all 0.3s ease;
}
.social-btn:hover {
transform: scale(1.1);
color: white;
}
.social-facebook {
background: #3b5998;
}
.social-twitter {
background: #1da1f2;
}
.social-kakao {
background: #fee500;
color: #3c1e1e;
}
.social-line {
background: #00c300;
}
/* 파티클 효과 */
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
.particle {
position: absolute;
background: var(--survey-primary);
border-radius: 50%;
opacity: 0.6;
animation: float 6s ease-in-out infinite;
}
.particle:nth-child(1) {
width: 4px;
height: 4px;
left: 10%;
animation-delay: 0s;
}
.particle:nth-child(2) {
width: 6px;
height: 6px;
left: 20%;
animation-delay: 1s;
}
.particle:nth-child(3) {
width: 3px;
height: 3px;
left: 30%;
animation-delay: 2s;
}
.particle:nth-child(4) {
width: 5px;
height: 5px;
left: 40%;
animation-delay: 3s;
}
.particle:nth-child(5) {
width: 4px;
height: 4px;
left: 50%;
animation-delay: 4s;
}
.particle:nth-child(6) {
width: 6px;
height: 6px;
left: 60%;
animation-delay: 5s;
}
.particle:nth-child(7) {
width: 3px;
height: 3px;
left: 70%;
animation-delay: 0.5s;
}
.particle:nth-child(8) {
width: 5px;
height: 5px;
left: 80%;
animation-delay: 1.5s;
}
.particle:nth-child(9) {
width: 4px;
height: 4px;
left: 90%;
animation-delay: 2.5s;
}
/* 반응형 */
@media (max-width: 768px) {
.complete-page-container {
padding: 80px 10px 20px;
}
.complete-card {
padding: 40px 20px;
}
.complete-title {
font-size: 2em;
}
.stats-container {
flex-direction: column;
gap: 20px;
}
.action-buttons {
flex-direction: column;
align-items: center;
}
.btn {
width: 100%;
max-width: 250px;
justify-content: center;
}
}
/* 애니메이션 */
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes float {
0%, 100% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10%, 90% {
opacity: 0.6;
}
50% {
transform: translateY(-10px) rotate(180deg);
opacity: 1;
}
}
</style>
<div class="complete-page-container">
<div class="complete-card">
<!-- 파티클 효과 -->
<div class="particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
<!-- 성공 아이콘 -->
<div class="success-icon">
<i class="fa fa-check"></i>
</div>
<!-- 완료 메시지 -->
<h1 class="complete-title">설문 완료!</h1>
<p class="complete-message">
<strong><?php echo htmlspecialchars($survey['sv_title']); ?></strong><br>
설문에 참여해 주셔서 감사합니다.
</p>
<!-- 감사 메시지 -->
<?php if ($survey['sv_thank_message']): ?>
<div class="thank-message">
<?php echo nl2br(htmlspecialchars($survey['sv_thank_message'])); ?>
</div>
<?php else: ?>
<div class="thank-message">
소중한 의견을 주셔서 감사합니다.<br>
더 나은 서비스를 위해 활용하겠습니다.
</div>
<?php endif; ?>
<!-- 통계 정보 -->
<div class="stats-container">
<div class="stat-item">
<span class="stat-number"><?php echo number_format($total_responses); ?></span>
<div class="stat-label">총 참여자 수</div>
</div>
<div class="stat-item">
<span class="stat-number"><?php echo $response['sr_id']; ?></span>
<div class="stat-label">응답 번호</div>
</div>
</div>
<!-- 액션 버튼 -->
<div class="action-buttons">
<a href="<?php echo G5_URL; ?>" class="btn btn-primary">
<i class="fa fa-home"></i> 홈으로 이동
</a>
<!-- <a href="survey_list_page.php" class="btn btn-outline">-->
<!-- <i class="fa fa-list"></i> 다른 설문 보기-->
<!-- </a>-->
</div>
<!-- 소셜 공유 -->
<div class="social-share">
<h4>설문을 공유해보세요</h4>
<div class="social-buttons">
<a href="#" class="social-btn social-facebook" onclick="shareToFacebook()" title="페이스북 공유">
<i class="fab fa-facebook-f"></i>
</a>
<a href="#" class="social-btn social-twitter" onclick="shareToTwitter()" title="트위터 공유">
<i class="fab fa-twitter"></i>
</a>
<a href="#" class="social-btn social-kakao" onclick="shareToKakao()" title="카카오톡 공유">
<i class="fab fa-comment"></i>
</a>
<a href="#" class="social-btn social-line" onclick="shareToLine()" title="라인 공유">
<i class="fab fa-line"></i>
</a>
</div>
</div>
</div>
</div>
<script>
// 소셜 공유 함수들
function shareToFacebook() {
const url = encodeURIComponent(window.location.origin + '/theme/rd.lwd/survey_page.php?sv_id=<?php echo $sv_id; ?>');
const title = encodeURIComponent('<?php echo addslashes($survey['sv_title']); ?>');
window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank', 'width=600,height=400');
}
function shareToTwitter() {
const url = encodeURIComponent(window.location.origin + '/theme/rd.lwd/survey_page.php?sv_id=<?php echo $sv_id; ?>');
const text = encodeURIComponent('<?php echo addslashes($survey['sv_title']); ?> 설문에 참여해보세요!');
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank', 'width=600,height=400');
}
function shareToKakao() {
// 카카오톡 공유 (실제 구현시 카카오 SDK 필요)
alert('카카오톡 공유 기능은 카카오 개발자 등록 후 사용 가능합니다.');
}
function shareToLine() {
const url = encodeURIComponent(window.location.origin + '/theme/rd.lwd/survey_page.php?sv_id=<?php echo $sv_id; ?>');
const text = encodeURIComponent('<?php echo addslashes($survey['sv_title']); ?>');
window.open(`https://social-plugins.line.me/lineit/share?url=${url}&text=${text}`, '_blank', 'width=600,height=400');
}
// 페이지 로드 시 축하 효과
document.addEventListener('DOMContentLoaded', function () {
// 3초 후 파티클 효과 시작
setTimeout(() => {
document.querySelector('.particles').style.display = 'block';
}, 1000);
// 성공 사운드 효과 (선택사항)
// playSuccessSound();
});
// 성공 사운드 재생 함수 (선택사항)
function playSuccessSound() {
// Web Audio API를 사용한 간단한 성공 사운드
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
} catch (e) {
// 오디오 컨텍스트 생성 실패시 무시
console.log('Audio context not supported');
}
}
</script>
<?php
include_once(G5_THEME_PATH . '/tail.sub.php');
?>
@@ -0,0 +1,175 @@
<?php
//if (!defined('_GNUBOARD_')) exit;
include_once('../../../../common.php');
// 설문 관리 라이브러리 로드
include_once(G5_ADMIN_PATH.'/survey_manage/lib/survey.lib.php');
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
if (!$sv_id) {
alert('잘못된 접근입니다.', G5_URL);
}
// 설문 유효성 검사
$validation = validate_survey_access($sv_id, $member['mb_id'], $_SERVER['REMOTE_ADDR']);
if (!$validation['success']) {
alert($validation['message']);
return;
}
$survey = $validation['survey'];
$questions = get_survey_questions($sv_id);
if (empty($questions)) {
alert('설문 질문이 없습니다.', G5_URL);
}
$g5['title'] = $survey['sv_title'];
include_once(G5_THEME_PATH.'/head.sub.php');
// 설문 테마 색상
$theme_color = $survey['sv_theme_color'] ?: '#AA20FF';
?>
<style>
/* 페이지 전용 스타일 */
:root {
--survey-primary: <?php echo $theme_color; ?>;
--survey-primary-light: <?php echo $theme_color; ?>20;
--survey-primary-dark: <?php echo $theme_color; ?>CC;
}
body {
background: linear-gradient(135deg, var(--survey-primary-light) 0%, #ffffff 100%);
min-height: 100vh;
}
.survey-page-container {
min-height: 100vh;
padding-top: 100px; /* 헤더 높이만큼 */
}
.survey-page-header {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: white;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
position: relative;
overflow: hidden;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.survey-page-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(90deg, var(--survey-primary) 0%, var(--survey-primary-dark) 100%);
}
.survey-page-title {
font-size: 2.5em;
font-weight: 700;
color: #333;
margin-bottom: 15px;
line-height: 1.2;
}
.survey-page-description {
font-size: 1.1em;
color: #666;
line-height: 1.6;
margin-bottom: 20px;
}
.survey-page-info {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
flex-wrap: wrap;
}
.survey-page-info-item {
display: flex;
align-items: center;
gap: 8px;
color: #888;
font-size: 0.9em;
}
.survey-page-info-item i {
color: var(--survey-primary);
}
.survey-form-section {
max-width: 800px;
margin: 0 auto;
padding: 0 20px 80px;
}
/* 반응형 */
@media (max-width: 768px) {
.survey-page-container {
padding-top: 80px;
}
.survey-page-title {
font-size: 2em;
}
.survey-page-info {
flex-direction: column;
gap: 15px;
}
.survey-form-section {
padding: 0 10px 60px;
}
}
</style>
<div class="survey-page-container">
<!-- 설문 헤더 -->
<!-- <div class="survey-page-header">-->
<!-- <h1 class="survey-page-title">--><?php //echo htmlspecialchars($survey['sv_title']); ?><!--</h1>-->
<!-- --><?php //if ($survey['sv_description']): ?>
<!-- <p class="survey-page-description">--><?php //echo nl2br(htmlspecialchars($survey['sv_description'])); ?><!--</p>-->
<!-- --><?php //endif; ?>
<!-- -->
<!-- <div class="survey-page-info">-->
<!-- <div class="survey-page-info-item">-->
<!-- <i class="fa fa-clock"></i>-->
<!-- <span>예상 소요시간: --><?php //echo count($questions); ?><!--분</span>-->
<!-- </div>-->
<!-- <div class="survey-page-info-item">-->
<!-- <i class="fa fa-question-circle"></i>-->
<!-- <span>총 --><?php //echo count($questions); ?><!--개 질문</span>-->
<!-- </div>-->
<!-- <div class="survey-page-info-item">-->
<!-- <i class="fa fa-users"></i>-->
<!-- <span>--><?php //echo number_format(get_survey_response_count($sv_id, 'completed')); ?><!--명 참여</span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- 설문 폼 섹션 (rb.custom 모듈 사용) -->
<div class="survey-form-section">
<?php
// 설문 폼 커스텀 모듈 포함
$_GET['sv_id'] = $sv_id; // 모듈에서 사용할 수 있도록 설정
include_once(G5_THEME_PATH.'/rb.custom/survey_form/module.php');
?>
</div>
</div>
<?php
include_once(G5_THEME_PATH.'/tail.sub.php');
?>
@@ -0,0 +1,132 @@
<?php
include_once('../../_common.php');
// 설문 관리 라이브러리 로드
include_once(G5_ADMIN_PATH.'/survey_manage/lib/survey.lib.php');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
alert('잘못된 접근입니다.', G5_URL);
}
$sv_id = isset($_POST['sv_id']) ? (int)$_POST['sv_id'] : 0;
if (!$sv_id) {
alert('잘못된 접근입니다.', G5_URL);
}
// 설문 유효성 검사
$validation = validate_survey_access($sv_id, $member['mb_id'], $_SERVER['REMOTE_ADDR']);
if (!$validation['success']) {
alert($validation['message'], G5_URL);
}
$survey = $validation['survey'];
$questions = get_survey_questions($sv_id);
if (empty($questions)) {
alert('설문 질문이 없습니다.', G5_URL);
}
// 응답 시작
$sr_id = start_survey_response(
$sv_id,
$member['mb_id'] ?: null,
$_SERVER['REMOTE_ADDR'],
$_SERVER['HTTP_USER_AGENT'] ?: '',
session_id()
);
if (!$sr_id) {
alert('응답 저장 중 오류가 발생했습니다.', G5_URL);
}
// 답변 저장
$errors = array();
foreach ($questions as $question) {
$field_name = 'answer_' . $question['sq_id'];
$value = isset($_POST[$field_name]) ? $_POST[$field_name] : '';
// 필수 질문 검증
if ($question['sq_required']) {
if (is_array($value)) {
if (empty($value) || (count($value) == 1 && empty($value[0]))) {
$errors[] = $question['sq_title'] . ' 항목은 필수입니다.';
continue;
}
} else {
if (empty(trim($value))) {
$errors[] = $question['sq_title'] . ' 항목은 필수입니다.';
continue;
}
}
}
// 유효성 검사
if ($question['sq_validation'] && !empty($value)) {
$validation_rules = $question['sq_validation'];
if (isset($validation_rules['minLength']) && strlen($value) < $validation_rules['minLength']) {
$errors[] = $question['sq_title'] . ' 항목은 최소 ' . $validation_rules['minLength'] . '자 이상 입력해주세요.';
continue;
}
if (isset($validation_rules['maxLength']) && strlen($value) > $validation_rules['maxLength']) {
$errors[] = $question['sq_title'] . ' 항목은 최대 ' . $validation_rules['maxLength'] . '자까지 입력 가능합니다.';
continue;
}
if (isset($validation_rules['pattern'])) {
$pattern = $validation_rules['pattern'];
if ($pattern === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[] = $question['sq_title'] . ' 항목에 올바른 이메일 주소를 입력해주세요.';
continue;
}
if ($pattern === 'phone' && !preg_match('/^[0-9-+\s()]+$/', $value)) {
$errors[] = $question['sq_title'] . ' 항목에 올바른 전화번호를 입력해주세요.';
continue;
}
}
}
// 답변 저장
if (!empty($value) || $question['sq_required']) {
save_survey_answer($sr_id, $question['sq_id'], $value);
}
}
// 오류가 있으면 응답 삭제하고 돌아가기
if (!empty($errors)) {
sql_query("DELETE FROM survey_responses WHERE sr_id = '$sr_id'");
// JSON 응답인지 확인
if (isset($_POST['ajax']) && $_POST['ajax'] == '1') {
header('Content-Type: application/json');
echo json_encode(array(
'success' => false,
'errors' => $errors
));
exit;
} else {
alert(implode('\n', $errors), 'survey_page.php?sv_id=' . $sv_id);
}
}
// 응답 완료 처리
complete_survey_response($sr_id);
// 통계 업데이트
update_survey_statistics($sv_id);
// JSON 응답인지 확인
if (isset($_POST['ajax']) && $_POST['ajax'] == '1') {
header('Content-Type: application/json');
echo json_encode(array(
'success' => true,
'redirect_url' => G5_THEME_PATH.'/rb.custom/survey_form/survey_complete_page.php?sv_id=' . $sv_id . '&sr_id=' . $sr_id
));
exit;
} else {
// 완료 페이지로 이동
goto_url(G5_THEME_PATH.'/rb.custom/survey_form/survey_complete_page.php?sv_id=' . $sv_id . '&sr_id=' . $sr_id);
}
?>
@@ -0,0 +1,45 @@
<?php
if (!defined('_GNUBOARD_')) exit;
echo '<div style="padding: 20px; border: 2px solid #AA20FF; margin: 20px; background: #f9f9f9;">';
echo '<h3>설문 폼 모듈 테스트</h3>';
echo '<p>현재 시간: ' . date('Y-m-d H:i:s') . '</p>';
// 파라미터 확인
echo '<h4>URL 파라미터:</h4>';
echo '<ul>';
foreach ($_GET as $key => $value) {
echo '<li>' . htmlspecialchars($key) . ' = ' . htmlspecialchars($value) . '</li>';
}
echo '</ul>';
// 라이브러리 파일 확인
$survey_lib_path = G5_ADMIN_PATH.'/survey_manage/lib/survey.lib.php';
echo '<h4>파일 확인:</h4>';
echo '<p>survey.lib.php: ' . (file_exists($survey_lib_path) ? '존재함' : '없음') . '</p>';
// 데이터베이스 테이블 확인
$tables = ['survey_master', 'survey_questions', 'survey_responses'];
echo '<h4>데이터베이스 테이블:</h4>';
echo '<ul>';
foreach ($tables as $table) {
$result = sql_query("SHOW TABLES LIKE '$table'", false);
echo '<li>' . $table . ': ' . ($result && sql_num_rows($result) > 0 ? '존재함' : '없음') . '</li>';
}
echo '</ul>';
// 설문 데이터 확인
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
if ($sv_id) {
echo '<h4>설문 데이터 (sv_id: ' . $sv_id . '):</h4>';
$survey = sql_fetch("SELECT * FROM survey_master WHERE sv_id = '$sv_id'");
if ($survey) {
echo '<p>제목: ' . htmlspecialchars($survey['sv_title']) . '</p>';
echo '<p>상태: ' . $survey['sv_status'] . '</p>';
} else {
echo '<p>설문을 찾을 수 없습니다.</p>';
}
}
echo '</div>';
?>