first commit 2
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
define('G5_IS_ADMIN', true);
|
||||
include_once('../../common.php');
|
||||
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
include_once(G5_ADMIN_PATH.'/admin.lib.php');
|
||||
|
||||
// 설문 관리 솔루션 버전
|
||||
define('SURVEY_VERSION', '1.0.0');
|
||||
|
||||
// 설문 관리 솔루션 경로
|
||||
define('SURVEY_ADMIN_PATH', G5_ADMIN_PATH.'/survey_manage');
|
||||
define('SURVEY_ADMIN_URL', G5_ADMIN_URL.'/survey_manage');
|
||||
define('SURVEY_DATA_PATH', G5_DATA_PATH.'/survey');
|
||||
define('SURVEY_DATA_URL', G5_DATA_URL.'/survey');
|
||||
|
||||
// 데이터 디렉토리 생성
|
||||
@mkdir(SURVEY_DATA_PATH, G5_DIR_PERMISSION, true);
|
||||
@chmod(SURVEY_DATA_PATH, G5_DIR_PERMISSION);
|
||||
// 설문 관리 라이브러리 함수들
|
||||
include_once(SURVEY_ADMIN_PATH.'/lib/survey.lib.php');
|
||||
|
||||
// 설문 상태 상수
|
||||
/*define('SURVEY_STATUS_DRAFT', 'draft');
|
||||
define('SURVEY_STATUS_ACTIVE', 'active');
|
||||
define('SURVEY_STATUS_CLOSED', 'closed');
|
||||
define('SURVEY_STATUS_DELETED', 'deleted');*/
|
||||
|
||||
// 질문 유형 상수
|
||||
define('QUESTION_TYPE_TEXT', 'text');
|
||||
define('QUESTION_TYPE_TEXTAREA', 'textarea');
|
||||
define('QUESTION_TYPE_RADIO', 'radio');
|
||||
define('QUESTION_TYPE_CHECKBOX', 'checkbox');
|
||||
define('QUESTION_TYPE_SELECT', 'select');
|
||||
define('QUESTION_TYPE_RATING', 'rating');
|
||||
define('QUESTION_TYPE_DATE', 'date');
|
||||
|
||||
/*// 응답 상태 상수
|
||||
define('RESPONSE_STATUS_STARTED', 'started');
|
||||
define('RESPONSE_STATUS_COMPLETED', 'completed');
|
||||
define('RESPONSE_STATUS_ABANDONED', 'abandoned');*/
|
||||
|
||||
// 공통 CSS 및 JS 파일 추가
|
||||
add_stylesheet('<link rel="stylesheet" href="'.SURVEY_ADMIN_URL.'/css/survey_admin.css?ver='.G5_SERVER_TIME.'">', 0);
|
||||
add_javascript('<script src="'.SURVEY_ADMIN_URL.'/js/survey_admin.js?ver='.G5_SERVER_TIME.'"></script>', 100);
|
||||
|
||||
// 페이지별 CSS/JS 파일 자동 로드
|
||||
$current_file = basename($_SERVER['PHP_SELF'], '.php');
|
||||
$page_css_map = [
|
||||
'survey_form' => 'survey_form.css',
|
||||
'template_form' => 'template_form.css',
|
||||
'template_list' => 'template_list.css',
|
||||
'statistics' => 'statistics.css'
|
||||
];
|
||||
|
||||
$page_js_map = [
|
||||
'survey_form' => 'survey_form.js',
|
||||
'template_form' => 'template_form.js',
|
||||
'statistics' => 'statistics.js'
|
||||
];
|
||||
|
||||
// 페이지별 CSS 로드
|
||||
if (isset($page_css_map[$current_file])) {
|
||||
$css_file = SURVEY_ADMIN_PATH.'/css/'.$page_css_map[$current_file];
|
||||
if (file_exists($css_file)) {
|
||||
add_stylesheet('<link rel="stylesheet" href="'.SURVEY_ADMIN_URL.'/css/'.$page_css_map[$current_file].'?ver='.filemtime($css_file).'">', 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지별 JS 로드
|
||||
if (isset($page_js_map[$current_file])) {
|
||||
$js_file = SURVEY_ADMIN_PATH.'/js/'.$page_js_map[$current_file];
|
||||
if (file_exists($js_file)) {
|
||||
add_javascript('<script src="'.SURVEY_ADMIN_URL.'/js/'.$page_js_map[$current_file].'?ver='.filemtime($js_file).'"></script>', 101);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 710번대 최상위 메뉴 '설문 관리'를 정의합니다.
|
||||
$menu['menu710'][] = array('710000', '설문 관리', G5_ADMIN_URL.'/survey_manage/survey_list.php', 'survey_manage', 'fa-poll');
|
||||
|
||||
// '설문 관리'의 하위 메뉴들을 정의합니다.
|
||||
$menu['menu710'][] = array('710100', '설문 목록', G5_ADMIN_URL.'/survey_manage/survey_list.php', 'survey_list');
|
||||
$menu['menu710'][] = array('710200', '설문 작성', G5_ADMIN_URL.'/survey_manage/survey_form.php', 'survey_create');
|
||||
$menu['menu710'][] = array('710300', '템플릿 관리', G5_ADMIN_URL.'/survey_manage/template_list.php', 'survey_template');
|
||||
$menu['menu710'][] = array('710400', '응답 관리', G5_ADMIN_URL.'/survey_manage/response_list.php', 'survey_response');
|
||||
$menu['menu710'][] = array('710500', '통계 분석', G5_ADMIN_URL.'/survey_manage/statistics_list.php', 'survey_statistics');
|
||||
$menu['menu710'][] = array('710600', '엑셀 내보내기', G5_ADMIN_URL.'/survey_manage/export.php', 'survey_export');
|
||||
$menu['menu710'][] = array('710700', '솔루션 설치', G5_ADMIN_URL.'/survey_manage/install.php', 'survey_solution_install');
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
include_once('./_common.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$st_id = isset($_GET['st_id']) ? (int)$_GET['st_id'] : 0;
|
||||
|
||||
if (!$st_id) {
|
||||
echo json_encode(['success' => false, 'message' => '템플릿 ID가 없습니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 템플릿 정보 가져오기
|
||||
$template = sql_fetch("SELECT * FROM survey_templates WHERE st_id = '$st_id'");
|
||||
if (!$template) {
|
||||
echo json_encode(['success' => false, 'message' => '존재하지 않는 템플릿입니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 템플릿 질문들 가져오기
|
||||
$questions = [];
|
||||
$question_sql = "SELECT * FROM survey_template_questions WHERE st_id = '$st_id' ORDER BY stq_order ASC";
|
||||
$question_result = sql_query($question_sql);
|
||||
|
||||
while ($question = sql_fetch_array($question_result)) {
|
||||
$question_data = [
|
||||
'stq_id' => $question['stq_id'],
|
||||
'stq_order' => $question['stq_order'],
|
||||
'stq_type' => $question['stq_type'],
|
||||
'stq_title' => $question['stq_title'],
|
||||
'stq_description' => $question['stq_description'],
|
||||
'stq_required' => $question['stq_required'],
|
||||
'stq_options' => $question['stq_options'] ? json_decode($question['stq_options'], true) : []
|
||||
];
|
||||
|
||||
$questions[] = $question_data;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'template' => $template,
|
||||
'questions' => $questions
|
||||
]);
|
||||
?>
|
||||
@@ -0,0 +1,391 @@
|
||||
/* 통계 페이지 스타일 */
|
||||
|
||||
.statistics-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stats-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
.stats-header p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5em;
|
||||
font-weight: 700;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stat-change.positive {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.stat-change.negative {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chart-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-btn.active,
|
||||
.chart-btn:hover {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.questions-stats {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.question-stat {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.question-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #AA20FF;
|
||||
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.2em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
border-radius: 12px;
|
||||
font-size: 0.7em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.question-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.answer-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.answer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.answer-item:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.answer-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.answer-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.answer-label {
|
||||
min-width: 150px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.answer-count {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.answer-percentage {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
font-size: 0.9em;
|
||||
color: #AA20FF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.question-chart {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.text-responses {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.text-response {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.text-response:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.text-response-meta {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.text-response-content {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.export-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(170, 32, 255, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.export-btn.secondary {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.export-btn.secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.statistics-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.question-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.answer-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.answer-label,
|
||||
.answer-count,
|
||||
.answer-percentage {
|
||||
min-width: auto;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* 애니메이션 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card,
|
||||
.chart-section,
|
||||
.question-stat {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.stat-card:nth-child(even) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.question-stat:nth-child(even) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/* 설문 관리 관리자 스타일 */
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-draft {
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status-deleted {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
/* 공통 버튼 스타일 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
background: #138496;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 알림 스타일 */
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
/* 테이블 스타일 */
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 폼 스타일 */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #AA20FF;
|
||||
box-shadow: 0 0 0 2px rgba(170, 32, 255, 0.25);
|
||||
}
|
||||
|
||||
/* 셀렉트 박스 스타일 수정 */
|
||||
select {
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
border: 1px solid #d5d5d5 !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 1em !important;
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.form-select,
|
||||
.question-type-select {
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
border: 1px solid #d5d5d5 !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 1em !important;
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
/* 설문 폼 내의 모든 셀렉트 박스 */
|
||||
.survey-form-container select,
|
||||
.template-form-container select {
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
border: 1px solid #d5d5d5 !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 1em !important;
|
||||
background-color: #fff !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/* 설문 작성/수정 폼 스타일 */
|
||||
|
||||
.survey-form-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
.form-tabs {
|
||||
display: flex;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-tab {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-tab.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-tab:not(.active):hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.form-label.required::after {
|
||||
content: ' *';
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: all 0.3s ease;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #AA20FF;
|
||||
background: white;
|
||||
box-shadow: 0 0 0 3px rgba(170, 32, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-col {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
accent-color: #AA20FF;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 템플릿 선택 섹션 */
|
||||
.template-section {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
border-color: #AA20FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.template-card.selected {
|
||||
border-color: #AA20FF;
|
||||
background: rgba(170, 32, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 질문 관리 섹션 */
|
||||
.questions-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
background: #fff;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.question-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-sm:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.question-type-select {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.option-input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.add-option-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.add-question-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.add-question-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 폼 액션 버튼 */
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 셀렉트 박스 높이 수정 */
|
||||
select,
|
||||
.form-select,
|
||||
.question-type-select {
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
padding: 8px 12px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.survey-form-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/* 템플릿 작성/수정 폼 스타일 */
|
||||
|
||||
.template-form-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.form-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
margin-right: 10px;
|
||||
color: #AA20FF;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-label.required::after {
|
||||
content: ' *';
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #AA20FF;
|
||||
box-shadow: 0 0 0 3px rgba(170, 32, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 12px center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px 12px;
|
||||
padding-right: 40px;
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
}
|
||||
|
||||
.questions-container {
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-right: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85em;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.option-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-add-option {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn-add-option:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.btn-add-question {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-add-question:hover {
|
||||
background: #8A1ACC;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #8A1ACC;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #6c757d;
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.empty-questions {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-questions i {
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.template-form-container {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/* 템플릿 목록 페이지 스타일 */
|
||||
|
||||
.template-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.template-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.category-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.category-btn:hover,
|
||||
.category-btn.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.template-card-header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.template-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.template-category {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.template-description {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9em;
|
||||
color: #888;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.template-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-template {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-use {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-use:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-preview {
|
||||
background: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-preview:hover {
|
||||
background: #e9ecef;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form select,
|
||||
.search-form input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
font-size: 1.5em;
|
||||
box-shadow: 0 4px 12px rgba(170, 32, 255, 0.3);
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.create-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: scale(1.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.template-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.template-stats {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
$sub_menu = '710600';
|
||||
include_once('./_common.php');
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
// 엑셀 다운로드 처리
|
||||
if (isset($_GET['sv_id']) && isset($_GET['format'])) {
|
||||
$sv_id = (int)$_GET['sv_id'];
|
||||
$format = $_GET['format'];
|
||||
|
||||
if ($format === 'excel' || $format === 'csv') {
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.');
|
||||
}
|
||||
|
||||
$export_data = get_survey_export_data($sv_id);
|
||||
if (empty($export_data) || count($export_data) <= 1) { // 헤더만 있는 경우 포함
|
||||
alert('내보낼 응답 데이터가 없습니다.');
|
||||
}
|
||||
|
||||
if ($format === 'excel') {
|
||||
// 💡 [수정] ZipArchive 클래스가 없는 치명적 오류를 방지하기 위해, 클래스 존재 여부를 먼저 확인합니다.
|
||||
// if (!class_exists('ZipArchive')) {
|
||||
// die('엑셀 파일 생성에 필요한 ZipArchive 클래스를 찾을 수 없습니다. 서버의 PHP 설정에서 "zip" 확장을 활성화해주세요.');
|
||||
// }
|
||||
|
||||
include_once(G5_LIB_PATH . '/PHPExcel.php');
|
||||
|
||||
try {
|
||||
// 💡 대용량 데이터 처리 시 발생할 수 있는 메모리 부족 및 시간 초과 문제를 방지합니다.
|
||||
|
||||
$objPHPExcel = new PHPExcel();
|
||||
$objPHPExcel->setActiveSheetIndex(0)
|
||||
->fromArray($export_data, null, 'A1');
|
||||
|
||||
// 컬럼 너비 자동 조정
|
||||
$sheet = $objPHPExcel->getActiveSheet();
|
||||
foreach (range('A', $sheet->getHighestDataColumn()) as $col) {
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
}
|
||||
|
||||
// 💡 기존에 생성된 모든 출력 버퍼를 강제로 비워서, 순수한 파일 데이터만 전송되도록 보장합니다.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment; filename="survey_' . $sv_id . '_' . date('Y-m-d') . '.xlsx"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
|
||||
$objWriter->save('php://output');
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// 💡 엑셀 생성 중 오류 발생 시, 깨진 파일을 보내는 대신 명확한 오류 메시지를 출력합니다.
|
||||
die('엑셀 파일 생성 중 오류가 발생했습니다. 오류: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
} else { // CSV
|
||||
// CSV 다운로드
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="survey_' . $sv_id . '_' . date('Y-m-d') . '.csv"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// UTF-8 BOM 추가 (엑셀에서 한글 깨짐 방지)
|
||||
fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
foreach ($export_data as $row) {
|
||||
fputcsv($output, $row);
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = '엑셀 내보내기';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
// 설문 목록 가져오기
|
||||
$survey_list = array();
|
||||
$survey_sql = "SELECT sv_id, sv_title, sv_created_at,
|
||||
(SELECT COUNT(*) FROM survey_responses WHERE sv_id = sm.sv_id AND sr_status = 'completed') as response_count
|
||||
FROM survey_master sm
|
||||
ORDER BY sv_created_at DESC";
|
||||
$survey_result = sql_query($survey_sql);
|
||||
while ($survey_row = sql_fetch_array($survey_result)) {
|
||||
$survey_list[] = $survey_row;
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.export-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.export-guide {
|
||||
background: #e7f3ff;
|
||||
border: 1px solid #b3d9ff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.export-guide h3 {
|
||||
color: #0066cc;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.export-guide ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.export-guide li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.survey-export-list {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.survey-export-list table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.survey-export-list th {
|
||||
background: #fff;
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.survey-export-list td {
|
||||
padding: 15px 10px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.survey-export-list tr:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.survey-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.survey-meta {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.response-count {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #AA20FF;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-excel {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-excel:hover {
|
||||
background: #218838;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-csv {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-csv:hover {
|
||||
background: #138496;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-export:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 4em;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.format-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.format-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.format-card h4 {
|
||||
color: #AA20FF;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.format-card p {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.format-info {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.survey-export-list {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.survey-export-list th,
|
||||
.survey-export-list td {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="export-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-download"></i> 엑셀 내보내기</h1>
|
||||
<p>설문 응답 데이터를 엑셀 또는 CSV 형식으로 다운로드할 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="export-guide">
|
||||
<h3><i class="fa fa-info-circle"></i> 내보내기 안내</h3>
|
||||
<ul>
|
||||
<li><strong>엑셀 형식 (.xls)</strong>: Microsoft Excel에서 바로 열 수 있으며, 한글이 깨지지 않습니다.</li>
|
||||
<li><strong>CSV 형식 (.csv)</strong>: 다양한 프로그램에서 호환되며, 데이터 분석 도구에서 사용하기 좋습니다.</li>
|
||||
<li>완료된 응답만 내보내기 됩니다. (진행중이거나 중단된 응답은 제외)</li>
|
||||
<li>모든 질문과 답변이 포함되며, 응답자 정보도 함께 제공됩니다.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="format-info">
|
||||
<div class="format-card">
|
||||
<h4><i class="fa fa-file-excel"></i> 엑셀 형식 (XLS)</h4>
|
||||
<p>Microsoft Excel에서 바로 열 수 있는 형식입니다. 한글 깨짐 없이 데이터를 확인할 수 있으며, 차트나 피벗 테이블 생성에 적합합니다.</p>
|
||||
</div>
|
||||
<div class="format-card">
|
||||
<h4><i class="fa fa-file-csv"></i> CSV 형식</h4>
|
||||
<p>범용적인 데이터 형식으로 Google Sheets, R, Python 등 다양한 분석 도구에서 사용할 수 있습니다. 대용량 데이터 처리에 적합합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($survey_list)): ?>
|
||||
<div class="survey-export-list">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th>설문 제목</th>
|
||||
<th width="100">응답 수</th>
|
||||
<th width="120">생성일</th>
|
||||
<th width="200">내보내기</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($survey_list as $survey): ?>
|
||||
<tr>
|
||||
<td><?php echo $survey['sv_id']; ?></td>
|
||||
<td>
|
||||
<div class="survey-title"><?php echo htmlspecialchars($survey['sv_title']); ?></div>
|
||||
<div class="survey-meta">ID: <?php echo $survey['sv_id']; ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="response-count"><?php echo number_format($survey['response_count']); ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<small><?php echo date('Y-m-d', strtotime($survey['sv_created_at'])); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="export-buttons">
|
||||
<?php if ($survey['response_count'] > 0): ?>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=excel"
|
||||
class="btn-export btn-excel">
|
||||
<i class="fa fa-file-excel"></i> 엑셀
|
||||
</a>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=csv" class="btn-export btn-csv">
|
||||
<i class="fa fa-file-csv"></i> CSV
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<button class="btn-export" disabled>
|
||||
<i class="fa fa-ban"></i> 응답 없음
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa fa-poll"></i>
|
||||
<h3>등록된 설문이 없습니다</h3>
|
||||
<p>설문을 먼저 생성해주세요.</p>
|
||||
<a href="survey_form.php" style="color: #AA20FF; margin-top: 15px; display: inline-block;">
|
||||
<i class="fa fa-plus"></i> 새 설문 만들기
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
// 다운로드 진행 상태 표시
|
||||
document.querySelectorAll('.btn-export:not([disabled])').forEach(btn => {
|
||||
btn.addEventListener('click', function (e) {
|
||||
const originalText = this.innerHTML;
|
||||
this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 준비중...';
|
||||
this.style.pointerEvents = 'none';
|
||||
|
||||
// 3초 후 원래 상태로 복원
|
||||
setTimeout(() => {
|
||||
this.innerHTML = originalText;
|
||||
this.style.pointerEvents = 'auto';
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
$sub_menu = '700306';
|
||||
include_once('./_common.php');
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
// 엑셀 다운로드 처리
|
||||
if (isset($_GET['sv_id']) && isset($_GET['format']) && isset($_GET['method'])) {
|
||||
$sv_id = (int)$_GET['sv_id'];
|
||||
$format = $_GET['format'];
|
||||
$method = $_GET['method'];
|
||||
|
||||
if ($format === 'excel') {
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.');
|
||||
}
|
||||
|
||||
$export_data = get_survey_export_data($sv_id);
|
||||
if (empty($export_data) || count($export_data) <= 1) {
|
||||
alert('내보낼 응답 데이터가 없습니다.');
|
||||
}
|
||||
|
||||
// 방법 1: 간단한 HTML 테이블 방식 (현재 수정된 방식)
|
||||
if ($method === 'html') {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
try {
|
||||
$filename = 'survey_' . $sv_id . '_' . date('Y-m-d') . '_html.xls';
|
||||
|
||||
header('Content-Type: application/vnd.ms-excel');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
header('Pragma: public');
|
||||
|
||||
echo '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">';
|
||||
echo '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>';
|
||||
echo '<body><table border="1">';
|
||||
|
||||
foreach ($export_data as $row) {
|
||||
echo '<tr>';
|
||||
foreach ($row as $cell) {
|
||||
echo '<td>' . htmlspecialchars($cell) . '</td>';
|
||||
}
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo '</table></body></html>';
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
die('엑셀 파일 생성 중 오류가 발생했습니다. 오류: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 방법 2: CSV를 XLS로 저장하는 방식
|
||||
elseif ($method === 'csv_as_xls') {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
try {
|
||||
$filename = 'survey_' . $sv_id . '_' . date('Y-m-d') . '_csv.xls';
|
||||
|
||||
header('Content-Type: application/vnd.ms-excel');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
foreach ($export_data as $row) {
|
||||
echo implode("\t", $row) . "\n";
|
||||
}
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
die('엑셀 파일 생성 중 오류가 발생했습니다. 오류: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 방법 3: 기존 PHPExcel 방식 (문제가 있는 방식)
|
||||
elseif ($method === 'phpexcel') {
|
||||
if (!file_exists(G5_LIB_PATH . '/PHPExcel.php')) {
|
||||
die('PHPExcel 라이브러리가 없습니다.');
|
||||
}
|
||||
|
||||
include_once(G5_LIB_PATH . '/PHPExcel.php');
|
||||
|
||||
try {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
@ini_set('memory_limit', '256M');
|
||||
@set_time_limit(300);
|
||||
|
||||
$objPHPExcel = new PHPExcel();
|
||||
$objPHPExcel->setActiveSheetIndex(0)->fromArray($export_data, null, 'A1');
|
||||
|
||||
$sheet = $objPHPExcel->getActiveSheet();
|
||||
foreach (range('A', $sheet->getHighestDataColumn()) as $col) {
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
}
|
||||
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment; filename="survey_' . $sv_id . '_' . date('Y-m-d') . '_phpexcel.xlsx"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
|
||||
$objWriter->save('php://output');
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
die('PHPExcel 오류: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 방법 4: 순수 CSV 다운로드
|
||||
elseif ($method === 'csv') {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="survey_' . $sv_id . '_' . date('Y-m-d') . '.csv"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); // UTF-8 BOM
|
||||
|
||||
foreach ($export_data as $row) {
|
||||
fputcsv($output, $row);
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = '엑셀 내보내기 테스트';
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
// 설문 목록 가져오기
|
||||
$survey_list = array();
|
||||
$survey_sql = "SELECT sv_id, sv_title, sv_created_at,
|
||||
(SELECT COUNT(*) FROM survey_responses WHERE sv_id = sm.sv_id AND sr_status = 'completed') as response_count
|
||||
FROM survey_master sm
|
||||
ORDER BY sv_created_at DESC";
|
||||
$survey_result = sql_query($survey_sql);
|
||||
while ($survey_row = sql_fetch_array($survey_result)) {
|
||||
$survey_list[] = $survey_row;
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.test-header {
|
||||
background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.test-methods {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.method-card {
|
||||
background: white;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.method-card:hover {
|
||||
border-color: #FF6B35;
|
||||
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.15);
|
||||
}
|
||||
|
||||
.method-title {
|
||||
font-weight: bold;
|
||||
color: #FF6B35;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.method-desc {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-test {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-html { background: #28a745; color: white; }
|
||||
.btn-html:hover { background: #218838; color: white; }
|
||||
|
||||
.btn-csv-xls { background: #17a2b8; color: white; }
|
||||
.btn-csv-xls:hover { background: #138496; color: white; }
|
||||
|
||||
.btn-phpexcel { background: #6f42c1; color: white; }
|
||||
.btn-phpexcel:hover { background: #5a32a3; color: white; }
|
||||
|
||||
.btn-csv { background: #fd7e14; color: white; }
|
||||
.btn-csv:hover { background: #e8690b; color: white; }
|
||||
|
||||
.survey-list {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.survey-list table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.survey-list th {
|
||||
background: #fff;
|
||||
padding: 12px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.survey-list td {
|
||||
padding: 12px 10px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.survey-list tr:hover {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="test-header">
|
||||
<h1><i class="fa fa-flask"></i> 엑셀 내보내기 테스트</h1>
|
||||
<p>다양한 방법으로 엑셀 파일을 생성해서 어떤 방법이 문제없이 작동하는지 테스트합니다</p>
|
||||
</div>
|
||||
|
||||
<div class="test-methods">
|
||||
<div class="method-card">
|
||||
<div class="method-title">방법 1: HTML 테이블</div>
|
||||
<div class="method-desc">HTML 테이블을 엑셀 형식으로 출력하는 방식 (현재 수정된 방식)</div>
|
||||
</div>
|
||||
|
||||
<div class="method-card">
|
||||
<div class="method-title">방법 2: CSV를 XLS로</div>
|
||||
<div class="method-desc">CSV 데이터를 XLS 확장자로 저장하는 방식</div>
|
||||
</div>
|
||||
|
||||
<div class="method-card">
|
||||
<div class="method-title">방법 3: PHPExcel</div>
|
||||
<div class="method-desc">기존 PHPExcel 라이브러리를 사용하는 방식 (문제가 있는 방식)</div>
|
||||
</div>
|
||||
|
||||
<div class="method-card">
|
||||
<div class="method-title">방법 4: 순수 CSV</div>
|
||||
<div class="method-desc">표준 CSV 형식으로 다운로드</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($survey_list)): ?>
|
||||
<div class="survey-list">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th>설문 제목</th>
|
||||
<th width="80">응답 수</th>
|
||||
<th width="100">생성일</th>
|
||||
<th width="300">테스트 다운로드</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($survey_list as $survey): ?>
|
||||
<tr>
|
||||
<td><?php echo $survey['sv_id']; ?></td>
|
||||
<td>
|
||||
<div style="font-weight: 600;"><?php echo htmlspecialchars($survey['sv_title']); ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<span style="font-weight: bold; color: #FF6B35;"><?php echo number_format($survey['response_count']); ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<small><?php echo date('Y-m-d', strtotime($survey['sv_created_at'])); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="test-buttons">
|
||||
<?php if ($survey['response_count'] > 0): ?>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=excel&method=html"
|
||||
class="btn-test btn-html">HTML</a>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=excel&method=csv_as_xls"
|
||||
class="btn-test btn-csv-xls">CSV→XLS</a>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=excel&method=phpexcel"
|
||||
class="btn-test btn-phpexcel">PHPExcel</a>
|
||||
<a href="?sv_id=<?php echo $survey['sv_id']; ?>&format=excel&method=csv"
|
||||
class="btn-test btn-csv">CSV</a>
|
||||
<?php else: ?>
|
||||
<span style="color: #999;">응답 없음</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div style="text-align: center; padding: 60px; color: #666;">
|
||||
<i class="fa fa-poll" style="font-size: 3em; margin-bottom: 15px; opacity: 0.3;"></i>
|
||||
<h3>등록된 설문이 없습니다</h3>
|
||||
<p>설문을 먼저 생성해주세요.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #fff; border-radius: 8px;">
|
||||
<h4><i class="fa fa-info-circle"></i> 테스트 방법 설명</h4>
|
||||
<ul>
|
||||
<li><strong>HTML</strong>: HTML 테이블을 엑셀로 인식시키는 방식 - 가장 안정적</li>
|
||||
<li><strong>CSV→XLS</strong>: CSV 데이터를 XLS 확장자로 저장 - 호환성 좋음</li>
|
||||
<li><strong>PHPExcel</strong>: 기존 라이브러리 방식 - 문제가 있을 수 있음</li>
|
||||
<li><strong>CSV</strong>: 표준 CSV 형식 - 가장 확실함</li>
|
||||
</ul>
|
||||
<p><strong>권장:</strong> 각 방법을 테스트해보고 정상적으로 다운로드되고 열리는 방법을 확인하세요.</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
$sub_menu = '710700';
|
||||
include_once('./_common.php');
|
||||
include_once(__DIR__ . '/lib/SchemaManager.class.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$g5['title'] = '설문 관리 솔루션 설치';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
function get_tables_from_sql_file($filepath) {
|
||||
$tables = [];
|
||||
if (!file_exists($filepath)) return $tables;
|
||||
$lines = file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/CREATE TABLE(?: IF NOT EXISTS)? `([^`]+)`/i', $line, $matches)) {
|
||||
$tables[] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
function create_admin_menu_file() {
|
||||
$source_file = __DIR__ . '/admin.menu710.survey.php';
|
||||
$target_file = G5_ADMIN_PATH . '/admin.menu710.survey.php';
|
||||
if (!file_exists($source_file)) return "실패 (메뉴 원본 파일 없음)";
|
||||
if (file_exists($target_file)) return "성공 (이미 존재함)";
|
||||
if (@copy($source_file, $target_file)) return "성공";
|
||||
return "실패 (파일 복사 오류)";
|
||||
}
|
||||
|
||||
$install_result = null;
|
||||
$delete_result = null;
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
$tables_to_check = get_tables_from_sql_file(__DIR__ . '/install.sql');
|
||||
|
||||
if ($action === 'install') {
|
||||
|
||||
check_admin_token();
|
||||
$sql_file = __DIR__ . '/install.sql';
|
||||
$db_results = [];
|
||||
try {
|
||||
$schemaManager = new SchemaManager($sql_file);
|
||||
$schemaManager->execute();
|
||||
$db_results = $schemaManager->get_results();
|
||||
} catch (Exception $e) { $db_results['errors'][] = $e->getMessage(); }
|
||||
|
||||
$menu_msg = create_admin_menu_file();
|
||||
$install_result = ['db' => $db_results, 'menu' => $menu_msg];
|
||||
} else if ($action === 'delete') {
|
||||
check_admin_token();
|
||||
$delete_result = ['tables' => [], 'menu' => ''];
|
||||
$tables_to_delete = $tables_to_check;
|
||||
foreach ($tables_to_delete as $table) {
|
||||
sql_query("DROP TABLE IF EXISTS `{$table}`", false);
|
||||
$delete_result['tables'][] = $table;
|
||||
}
|
||||
$menu_file = G5_ADMIN_PATH . '/admin.menu710.survey.php';
|
||||
if (file_exists($menu_file)) {
|
||||
if (@unlink($menu_file)) {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 성공';
|
||||
} else {
|
||||
$delete_result['menu'] = '메뉴 파일 삭제 실패 (권한 확인 필요)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$existing_tables = [];
|
||||
foreach ($tables_to_check as $table) {
|
||||
if (sql_query("SHOW TABLES LIKE '$table'", false) && sql_num_rows(sql_query("SHOW TABLES LIKE '$table'", false)) > 0) {
|
||||
$existing_tables[] = $table;
|
||||
}
|
||||
}
|
||||
$is_installed = count($existing_tables) > 0 && count($existing_tables) == count($tables_to_check);
|
||||
?>
|
||||
|
||||
<style>
|
||||
.install-container { max-width: 800px; margin: 20px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.install-header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #AA20FF; }
|
||||
.install-header h1 { color: #AA20FF; margin-bottom: 10px; }
|
||||
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; }
|
||||
.feature-card { padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px; text-align: center; }
|
||||
.feature-card i { font-size: 2em; color: #AA20FF; margin-bottom: 10px; }
|
||||
.status-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
.status-table th, .status-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
.status-table th { background-color: #f8f9fa; font-weight: bold; }
|
||||
.status-ok { color: #28a745; font-weight: bold; }
|
||||
.status-missing { color: #dc3545; font-weight: bold; }
|
||||
.install-btn { display: block; width: 200px; margin: 30px auto; padding: 15px 30px; background: #AA20FF; color: white; text-align: center; text-decoration: none; border-radius: 5px; font-size: 16px; font-weight: bold; border: none; cursor: pointer; transition: background-color 0.3s; }
|
||||
.install-btn:hover { background: #8A1ACC; color: white; }
|
||||
.install-btn:disabled { background: #ccc; cursor: not-allowed; }
|
||||
.alert { padding: 15px; margin: 20px 0; border-radius: 5px; }
|
||||
.alert-success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
|
||||
.alert-info { background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
|
||||
.alert-danger { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
|
||||
.btn-secondary { background: #6c757d; color: white; border-color: #6c757d; padding: 5px 10px; border-radius: 4px; text-decoration: none; }
|
||||
.btn-secondary:hover { background: #5a6268; }
|
||||
.btn-danger { background: #dc3545; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
.button-group { display: flex; justify-content: center; align-items: center; gap: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="install-container">
|
||||
<div class="install-header">
|
||||
<h1><i class="fa fa-poll"></i> 설문 관리 솔루션</h1>
|
||||
<p>그누보드5 기반의 전문적인 설문조사 시스템</p>
|
||||
</div>
|
||||
|
||||
<?php if ($install_result): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 작업 완료</h4><p>데이터베이스 및 메뉴 설정이 완료되었습니다.</p><p><a href="<?php echo SURVEY_ADMIN_URL; ?>/survey_list.php" class="btn btn-primary">설문 관리로 이동</a></p></div>
|
||||
<?php elseif ($delete_result): ?>
|
||||
<div class="alert alert-danger"><h4><i class="fa fa-trash"></i> 삭제 작업 완료</h4><p>솔루션 관련 데이터와 파일이 삭제되었습니다.</p><ul><?php foreach($delete_result['tables'] as $tbl) echo "<li>{$tbl} 테이블 삭제됨</li>"; ?><li><?php echo $delete_result['menu']; ?></li></ul></div>
|
||||
<?php elseif ($is_installed): ?>
|
||||
<div class="alert alert-success"><h4><i class="fa fa-check-circle"></i> 설치 완료</h4><p>설문 관리 솔루션이 이미 설치되어 있습니다.</p><p><a href="<?php echo SURVEY_ADMIN_URL; ?>/survey_list.php" class="btn btn-primary">설문 관리로 이동</a></p></div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info"><h4><i class="fa fa-info-circle"></i> 설치 필요</h4><p>설문 관리 솔루션을 사용하기 위해 데이터베이스 설치가 필요합니다.</p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><i class="fa fa-star"></i> 주요 기능</h3>
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-edit"></i>
|
||||
<h4>설문 작성</h4>
|
||||
<p>다양한 질문 유형으로<br>전문적인 설문 작성</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-users"></i>
|
||||
<h4>응답 관리</h4>
|
||||
<p>실시간 응답 현황<br>모니터링</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-chart-bar"></i>
|
||||
<h4>통계 분석</h4>
|
||||
<p>시각적 차트와<br>상세 통계 제공</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-file-excel"></i>
|
||||
<h4>엑셀 내보내기</h4>
|
||||
<p>응답 데이터를<br>엑셀로 다운로드</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-mobile-alt"></i>
|
||||
<h4>반응형 디자인</h4>
|
||||
<p>PC/모바일<br>최적화 지원</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fa fa-palette"></i>
|
||||
<h4>테마 커스터마이징</h4>
|
||||
<p>rd.lwd 테마 기반<br>세련된 디자인</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3><i class="fa fa-database"></i> 설치 상태</h3>
|
||||
<table class="status-table">
|
||||
<thead><tr><th>테이블명</th><th>상태</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($tables_to_check as $table): ?>
|
||||
<tr>
|
||||
<td><code><?php echo $table; ?></code></td>
|
||||
<td>
|
||||
<?php if (in_array($table, $existing_tables)): ?>
|
||||
<span class="status-ok"><i class="fa fa-check"></i> 설치됨</span>
|
||||
<?php else: ?>
|
||||
<span class="status-missing"><i class="fa fa-times"></i> 미설치</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if (!$is_installed): ?>
|
||||
<form method="post" onsubmit="return confirm('솔루션을 설치하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="install-btn"><i class="fa fa-download"></i> 솔루션 설치하기</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_installed && !$install_result && !$delete_result): ?>
|
||||
<div class="button-group" style="text-align: center; margin-top: 20px;">
|
||||
<form method="post" onsubmit="return confirm('기존 데이터는 유지되며, 변경된 DB 구조만 업데이트 됩니다. 진행하시겠습니까?');">
|
||||
<input type="hidden" name="action" value="install">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn btn-secondary"><i class="fa fa-sync"></i> 재설치 (업데이트)</button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('정말로 솔루션을 삭제하시겠습니까? 모든 관련 데이터와 파일이 영구적으로 삭제됩니다.');">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
<button type="submit" class="btn-danger"><i class="fa fa-trash"></i> 솔루션 삭제하기</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,155 @@
|
||||
-- 설문 관리 솔루션 데이터베이스 스키마
|
||||
-- Survey Management Solution Database Schema
|
||||
|
||||
-- 설문지 마스터 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_master` (
|
||||
`sv_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '설문지 ID',
|
||||
`sv_title` varchar(255) NOT NULL COMMENT '설문지 제목',
|
||||
`sv_description` text COMMENT '설문지 설명',
|
||||
`sv_start_date` datetime NOT NULL COMMENT '설문 시작일',
|
||||
`sv_end_date` datetime NOT NULL COMMENT '설문 종료일',
|
||||
`sv_status` enum('draft','active','closed','deleted') NOT NULL DEFAULT 'draft' COMMENT '설문 상태',
|
||||
`sv_allow_anonymous` tinyint(1) NOT NULL DEFAULT 1 COMMENT '익명 참여 허용',
|
||||
`sv_allow_multiple` tinyint(1) NOT NULL DEFAULT 0 COMMENT '중복 참여 허용',
|
||||
`sv_max_responses` int(11) DEFAULT NULL COMMENT '최대 응답 수',
|
||||
`sv_theme_color` varchar(7) DEFAULT '#AA20FF' COMMENT '테마 색상',
|
||||
`sv_thank_message` text COMMENT '완료 메시지',
|
||||
`sv_created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`sv_created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시',
|
||||
`sv_updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시',
|
||||
PRIMARY KEY (`sv_id`),
|
||||
KEY `idx_status` (`sv_status`),
|
||||
KEY `idx_dates` (`sv_start_date`, `sv_end_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문지 마스터';
|
||||
|
||||
-- 설문 질문 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_questions` (
|
||||
`sq_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '질문 ID',
|
||||
`sv_id` int(11) NOT NULL COMMENT '설문지 ID',
|
||||
`sq_order` int(11) NOT NULL DEFAULT 1 COMMENT '질문 순서',
|
||||
`sq_type` enum('text','textarea','radio','checkbox','select','rating','date','email','number') NOT NULL COMMENT '질문 유형',
|
||||
`sq_title` varchar(500) NOT NULL COMMENT '질문 제목',
|
||||
`sq_description` text COMMENT '질문 설명',
|
||||
`sq_required` tinyint(1) NOT NULL DEFAULT 0 COMMENT '필수 여부',
|
||||
`sq_options` text COMMENT '선택지 옵션 (JSON)',
|
||||
`sq_validation` text COMMENT '유효성 검사 규칙 (JSON)',
|
||||
`sq_created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시',
|
||||
PRIMARY KEY (`sq_id`),
|
||||
KEY `fk_survey_questions_master` (`sv_id`),
|
||||
KEY `idx_order` (`sv_id`, `sq_order`),
|
||||
CONSTRAINT `fk_survey_questions_master` FOREIGN KEY (`sv_id`) REFERENCES `survey_master` (`sv_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 질문';
|
||||
|
||||
-- 설문 응답자 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_responses` (
|
||||
`sr_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '응답 ID',
|
||||
`sv_id` int(11) NOT NULL COMMENT '설문지 ID',
|
||||
`sr_mb_id` varchar(20) DEFAULT NULL COMMENT '회원 ID (익명시 NULL)',
|
||||
`sr_ip` varchar(45) NOT NULL COMMENT '응답자 IP',
|
||||
`sr_user_agent` text COMMENT '사용자 에이전트',
|
||||
`sr_session_id` varchar(128) COMMENT '세션 ID',
|
||||
`sr_started_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '시작 시간',
|
||||
`sr_completed_at` datetime DEFAULT NULL COMMENT '완료 시간',
|
||||
`sr_status` enum('started','completed','abandoned') NOT NULL DEFAULT 'started' COMMENT '응답 상태',
|
||||
PRIMARY KEY (`sr_id`),
|
||||
KEY `fk_survey_responses_master` (`sv_id`),
|
||||
KEY `idx_member` (`sr_mb_id`),
|
||||
KEY `idx_ip` (`sr_ip`),
|
||||
KEY `idx_status` (`sr_status`),
|
||||
CONSTRAINT `fk_survey_responses_master` FOREIGN KEY (`sv_id`) REFERENCES `survey_master` (`sv_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 응답자';
|
||||
|
||||
-- 설문 답변 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_answers` (
|
||||
`sa_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '답변 ID',
|
||||
`sr_id` int(11) NOT NULL COMMENT '응답 ID',
|
||||
`sq_id` int(11) NOT NULL COMMENT '질문 ID',
|
||||
`sa_value` text COMMENT '답변 값',
|
||||
`sa_text` text COMMENT '기타 텍스트 답변',
|
||||
`sa_created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '답변 시간',
|
||||
PRIMARY KEY (`sa_id`),
|
||||
KEY `fk_survey_answers_response` (`sr_id`),
|
||||
KEY `fk_survey_answers_question` (`sq_id`),
|
||||
KEY `idx_response_question` (`sr_id`, `sq_id`),
|
||||
CONSTRAINT `fk_survey_answers_response` FOREIGN KEY (`sr_id`) REFERENCES `survey_responses` (`sr_id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_survey_answers_question` FOREIGN KEY (`sq_id`) REFERENCES `survey_questions` (`sq_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 답변';
|
||||
|
||||
-- 설문 통계 캐시 테이블 (성능 최적화용)
|
||||
CREATE TABLE IF NOT EXISTS `survey_statistics` (
|
||||
`ss_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '통계 ID',
|
||||
`sv_id` int(11) NOT NULL COMMENT '설문지 ID',
|
||||
`sq_id` int(11) NOT NULL COMMENT '질문 ID',
|
||||
`ss_option_value` varchar(500) COMMENT '선택지 값',
|
||||
`ss_count` int(11) NOT NULL DEFAULT 0 COMMENT '응답 수',
|
||||
`ss_percentage` decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT '비율',
|
||||
`ss_updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간',
|
||||
PRIMARY KEY (`ss_id`),
|
||||
UNIQUE KEY `uk_survey_stats` (`sv_id`, `sq_id`, `ss_option_value`),
|
||||
KEY `fk_survey_statistics_master` (`sv_id`),
|
||||
KEY `fk_survey_statistics_question` (`sq_id`),
|
||||
CONSTRAINT `fk_survey_statistics_master` FOREIGN KEY (`sv_id`) REFERENCES `survey_master` (`sv_id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_survey_statistics_question` FOREIGN KEY (`sq_id`) REFERENCES `survey_questions` (`sq_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 통계 캐시';
|
||||
|
||||
-- 설문 템플릿 마스터 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_templates` (
|
||||
`st_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '템플릿 ID',
|
||||
`st_name` varchar(255) NOT NULL COMMENT '템플릿 이름',
|
||||
`st_description` text COMMENT '템플릿 설명',
|
||||
`st_category` varchar(50) NOT NULL DEFAULT '기타' COMMENT '템플릿 카테고리',
|
||||
`st_is_public` tinyint(1) NOT NULL DEFAULT 1 COMMENT '공개 여부',
|
||||
`st_created_by` varchar(20) NOT NULL COMMENT '생성자',
|
||||
`st_created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시',
|
||||
`st_updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시',
|
||||
PRIMARY KEY (`st_id`),
|
||||
KEY `idx_category` (`st_category`),
|
||||
KEY `idx_public` (`st_is_public`),
|
||||
KEY `idx_created_by` (`st_created_by`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 템플릿 마스터';
|
||||
|
||||
-- 설문 템플릿 질문 테이블
|
||||
CREATE TABLE IF NOT EXISTS `survey_template_questions` (
|
||||
`stq_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '템플릿 질문 ID',
|
||||
`st_id` int(11) NOT NULL COMMENT '템플릿 ID',
|
||||
`stq_order` int(11) NOT NULL DEFAULT 1 COMMENT '질문 순서',
|
||||
`stq_type` enum('text','textarea','radio','checkbox','select','rating','date','email','number') NOT NULL COMMENT '질문 유형',
|
||||
`stq_title` varchar(500) NOT NULL COMMENT '질문 제목',
|
||||
`stq_description` text COMMENT '질문 설명',
|
||||
`stq_required` tinyint(1) NOT NULL DEFAULT 0 COMMENT '필수 여부',
|
||||
`stq_options` text COMMENT '선택지 옵션 (JSON)',
|
||||
`stq_created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시',
|
||||
PRIMARY KEY (`stq_id`),
|
||||
KEY `fk_survey_template_questions_master` (`st_id`),
|
||||
KEY `idx_order` (`st_id`, `stq_order`),
|
||||
CONSTRAINT `fk_survey_template_questions_master` FOREIGN KEY (`st_id`) REFERENCES `survey_templates` (`st_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='설문 템플릿 질문';
|
||||
|
||||
-- 기본 템플릿 데이터 삽입
|
||||
INSERT INTO `survey_templates` (`st_name`, `st_description`, `st_category`, `st_is_public`, `st_created_by`) VALUES
|
||||
('고객 만족도 조사', '제품이나 서비스에 대한 고객 만족도를 측정하는 기본 템플릿입니다.', '고객서비스', 1, 'admin'),
|
||||
('제품 피드백 설문', '새로운 제품에 대한 사용자 피드백을 수집하는 템플릿입니다.', '제품개발', 1, 'admin'),
|
||||
('마케팅 캠페인 효과 측정', '마케팅 캠페인의 효과를 측정하고 개선점을 찾는 템플릿입니다.', '마케팅', 1, 'admin');
|
||||
|
||||
-- 고객 만족도 조사 템플릿 질문들
|
||||
INSERT INTO `survey_template_questions` (`st_id`, `stq_order`, `stq_type`, `stq_title`, `stq_description`, `stq_required`, `stq_options`) VALUES
|
||||
(1, 1, 'rating', '전반적인 서비스 만족도를 평가해주세요.', '1점(매우 불만족)부터 5점(매우 만족)까지 평가해주세요.', 1, NULL),
|
||||
(1, 2, 'radio', '우리 서비스를 다른 사람에게 추천하시겠습니까?', '', 1, '["매우 추천", "추천", "보통", "추천하지 않음", "절대 추천하지 않음"]'),
|
||||
(1, 3, 'checkbox', '어떤 부분이 가장 만족스러우셨나요? (복수선택 가능)', '', 0, '["제품 품질", "고객 서비스", "배송 속도", "가격", "사용 편의성", "기타"]'),
|
||||
(1, 4, 'textarea', '개선이 필요한 부분이나 추가 의견이 있으시면 자유롭게 작성해주세요.', '', 0, NULL);
|
||||
|
||||
-- 제품 피드백 설문 템플릿 질문들
|
||||
INSERT INTO `survey_template_questions` (`st_id`, `stq_order`, `stq_type`, `stq_title`, `stq_description`, `stq_required`, `stq_options`) VALUES
|
||||
(2, 1, 'radio', '이 제품을 어떻게 알게 되셨나요?', '', 1, '["검색엔진", "소셜미디어", "친구/지인 추천", "광고", "기타"]'),
|
||||
(2, 2, 'rating', '제품의 사용 편의성은 어떠셨나요?', '1점(매우 어려움)부터 5점(매우 쉬움)까지 평가해주세요.', 1, NULL),
|
||||
(2, 3, 'radio', '제품의 가격은 적정하다고 생각하시나요?', '', 1, '["매우 비쌈", "비쌈", "적정함", "저렴함", "매우 저렴함"]'),
|
||||
(2, 4, 'checkbox', '어떤 기능을 가장 자주 사용하시나요?', '', 0, '["기본 기능", "고급 기능", "설정 기능", "공유 기능", "분석 기능"]'),
|
||||
(2, 5, 'textarea', '추가하고 싶은 기능이나 개선사항이 있다면 알려주세요.', '', 0, NULL);
|
||||
|
||||
-- 마케팅 캠페인 효과 측정 템플릿 질문들
|
||||
INSERT INTO `survey_template_questions` (`st_id`, `stq_order`, `stq_type`, `stq_title`, `stq_description`, `stq_required`, `stq_options`) VALUES
|
||||
(3, 1, 'radio', '최근 우리 브랜드의 광고를 본 적이 있나요?', '', 1, '["예, 여러 번 봤습니다", "예, 한두 번 봤습니다", "아니오, 본 적 없습니다"]'),
|
||||
(3, 2, 'checkbox', '어떤 채널에서 우리 광고를 보셨나요? (복수선택 가능)', '', 0, '["TV", "온라인 광고", "소셜미디어", "옥외광고", "라디오", "기타"]'),
|
||||
(3, 3, 'rating', '광고가 제품에 대한 관심을 높이는데 도움이 되었나요?', '1점(전혀 도움 안됨)부터 5점(매우 도움됨)까지 평가해주세요.', 1, NULL),
|
||||
(3, 4, 'radio', '광고를 본 후 실제로 제품을 구매하셨나요?', '', 1, '["예, 구매했습니다", "아니오, 하지만 구매를 고려중입니다", "아니오, 구매할 계획이 없습니다"]'),
|
||||
(3, 5, 'textarea', '광고에 대한 전반적인 의견이나 개선사항을 알려주세요.', '', 0, NULL);
|
||||
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
$sub_menu = '710700';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$g5['title'] = '템플릿 데이터 설치';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
if (isset($_POST['install_templates']) && $_POST['install_templates'] == '1') {
|
||||
check_admin_token();
|
||||
|
||||
// 기존 템플릿 데이터 삭제 (선택사항)
|
||||
if (isset($_POST['clear_existing'])) {
|
||||
sql_query("DELETE FROM survey_template_questions");
|
||||
sql_query("DELETE FROM survey_templates");
|
||||
sql_query("ALTER TABLE survey_templates AUTO_INCREMENT = 1");
|
||||
sql_query("ALTER TABLE survey_template_questions AUTO_INCREMENT = 1");
|
||||
}
|
||||
|
||||
$success_count = 0;
|
||||
$error_count = 0;
|
||||
$errors = [];
|
||||
|
||||
// 기본 템플릿 데이터 삽입
|
||||
$templates = [
|
||||
[
|
||||
'name' => '고객 만족도 조사',
|
||||
'description' => '제품이나 서비스에 대한 고객 만족도를 측정하는 기본 템플릿입니다.',
|
||||
'category' => '고객서비스',
|
||||
'questions' => [
|
||||
[
|
||||
'order' => 1,
|
||||
'type' => 'rating',
|
||||
'title' => '전반적인 서비스 만족도를 평가해주세요.',
|
||||
'description' => '1점(매우 불만족)부터 5점(매우 만족)까지 평가해주세요.',
|
||||
'required' => 1,
|
||||
'options' => null
|
||||
],
|
||||
[
|
||||
'order' => 2,
|
||||
'type' => 'radio',
|
||||
'title' => '우리 서비스를 다른 사람에게 추천하시겠습니까?',
|
||||
'description' => '',
|
||||
'required' => 1,
|
||||
'options' => '["매우 추천", "추천", "보통", "추천하지 않음", "절대 추천하지 않음"]'
|
||||
],
|
||||
[
|
||||
'order' => 3,
|
||||
'type' => 'checkbox',
|
||||
'title' => '어떤 부분이 가장 만족스러우셨나요? (복수선택 가능)',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => '["제품 품질", "고객 서비스", "배송 속도", "가격", "사용 편의성", "기타"]'
|
||||
],
|
||||
[
|
||||
'order' => 4,
|
||||
'type' => 'textarea',
|
||||
'title' => '개선이 필요한 부분이나 추가 의견이 있으시면 자유롭게 작성해주세요.',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => null
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '제품 피드백 설문',
|
||||
'description' => '새로운 제품에 대한 사용자 피드백을 수집하는 템플릿입니다.',
|
||||
'category' => '제품개발',
|
||||
'questions' => [
|
||||
[
|
||||
'order' => 1,
|
||||
'type' => 'radio',
|
||||
'title' => '이 제품을 어떻게 알게 되셨나요?',
|
||||
'description' => '',
|
||||
'required' => 1,
|
||||
'options' => '["검색엔진", "소셜미디어", "친구/지인 추천", "광고", "기타"]'
|
||||
],
|
||||
[
|
||||
'order' => 2,
|
||||
'type' => 'rating',
|
||||
'title' => '제품의 사용 편의성은 어떠셨나요?',
|
||||
'description' => '1점(매우 어려움)부터 5점(매우 쉬움)까지 평가해주세요.',
|
||||
'required' => 1,
|
||||
'options' => null
|
||||
],
|
||||
[
|
||||
'order' => 3,
|
||||
'type' => 'radio',
|
||||
'title' => '제품의 가격은 적정하다고 생각하시나요?',
|
||||
'description' => '',
|
||||
'required' => 1,
|
||||
'options' => '["매우 비쌈", "비쌈", "적정함", "저렴함", "매우 저렴함"]'
|
||||
],
|
||||
[
|
||||
'order' => 4,
|
||||
'type' => 'checkbox',
|
||||
'title' => '어떤 기능을 가장 자주 사용하시나요?',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => '["기본 기능", "고급 기능", "설정 기능", "공유 기능", "분석 기능"]'
|
||||
],
|
||||
[
|
||||
'order' => 5,
|
||||
'type' => 'textarea',
|
||||
'title' => '추가하고 싶은 기능이나 개선사항이 있다면 알려주세요.',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => null
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '마케팅 캠페인 효과 측정',
|
||||
'description' => '마케팅 캠페인의 효과를 측정하고 개선점을 찾는 템플릿입니다.',
|
||||
'category' => '마케팅',
|
||||
'questions' => [
|
||||
[
|
||||
'order' => 1,
|
||||
'type' => 'radio',
|
||||
'title' => '최근 우리 브랜드의 광고를 본 적이 있나요?',
|
||||
'description' => '',
|
||||
'required' => 1,
|
||||
'options' => '["예, 여러 번 봤습니다", "예, 한두 번 봤습니다", "아니오, 본 적 없습니다"]'
|
||||
],
|
||||
[
|
||||
'order' => 2,
|
||||
'type' => 'checkbox',
|
||||
'title' => '어떤 채널에서 우리 광고를 보셨나요? (복수선택 가능)',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => '["TV", "온라인 광고", "소셜미디어", "옥외광고", "라디오", "기타"]'
|
||||
],
|
||||
[
|
||||
'order' => 3,
|
||||
'type' => 'rating',
|
||||
'title' => '광고가 제품에 대한 관심을 높이는데 도움이 되었나요?',
|
||||
'description' => '1점(전혀 도움 안됨)부터 5점(매우 도움됨)까지 평가해주세요.',
|
||||
'required' => 1,
|
||||
'options' => null
|
||||
],
|
||||
[
|
||||
'order' => 4,
|
||||
'type' => 'radio',
|
||||
'title' => '광고를 본 후 실제로 제품을 구매하셨나요?',
|
||||
'description' => '',
|
||||
'required' => 1,
|
||||
'options' => '["예, 구매했습니다", "아니오, 하지만 구매를 고려중입니다", "아니오, 구매할 계획이 없습니다"]'
|
||||
],
|
||||
[
|
||||
'order' => 5,
|
||||
'type' => 'textarea',
|
||||
'title' => '광고에 대한 전반적인 의견이나 개선사항을 알려주세요.',
|
||||
'description' => '',
|
||||
'required' => 0,
|
||||
'options' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($templates as $template_data) {
|
||||
// 템플릿 삽입
|
||||
$sql = "INSERT INTO survey_templates
|
||||
(st_name, st_description, st_category, st_is_public, st_created_by, st_created_at)
|
||||
VALUES
|
||||
('".sql_real_escape_string($template_data['name'])."',
|
||||
'".sql_real_escape_string($template_data['description'])."',
|
||||
'".sql_real_escape_string($template_data['category'])."',
|
||||
1,
|
||||
'admin',
|
||||
NOW())";
|
||||
|
||||
if (sql_query($sql)) {
|
||||
$st_id = sql_insert_id();
|
||||
$success_count++;
|
||||
|
||||
// 템플릿 질문들 삽입
|
||||
foreach ($template_data['questions'] as $question) {
|
||||
$options_value = $question['options'] ? "'".sql_real_escape_string($question['options'])."'" : 'NULL';
|
||||
|
||||
$question_sql = "INSERT INTO survey_template_questions
|
||||
(st_id, stq_order, stq_type, stq_title, stq_description, stq_required, stq_options, stq_created_at)
|
||||
VALUES
|
||||
('$st_id',
|
||||
'{$question['order']}',
|
||||
'".sql_real_escape_string($question['type'])."',
|
||||
'".sql_real_escape_string($question['title'])."',
|
||||
'".sql_real_escape_string($question['description'])."',
|
||||
'{$question['required']}',
|
||||
$options_value,
|
||||
NOW())";
|
||||
|
||||
if (sql_query($question_sql)) {
|
||||
$success_count++;
|
||||
} else {
|
||||
$error_count++;
|
||||
$errors[] = "질문 삽입 실패: " . $question['title'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error_count++;
|
||||
$errors[] = "템플릿 삽입 실패: " . $template_data['name'];
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="alert alert-success">';
|
||||
echo '<h4>템플릿 데이터 설치 완료!</h4>';
|
||||
echo '<p>성공: '.$success_count.'개, 실패: '.$error_count.'개</p>';
|
||||
if (!empty($errors)) {
|
||||
echo '<details><summary>오류 상세</summary>';
|
||||
foreach ($errors as $error) {
|
||||
echo '<p style="color: red;">'.$error.'</p>';
|
||||
}
|
||||
echo '</details>';
|
||||
}
|
||||
echo '<p><a href="template_list.php" class="btn btn-primary">템플릿 목록 확인하기</a></p>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
// 현재 템플릿 상태 확인
|
||||
$template_count = sql_fetch("SELECT COUNT(*) as cnt FROM survey_templates")['cnt'];
|
||||
$question_count = sql_fetch("SELECT COUNT(*) as cnt FROM survey_template_questions")['cnt'];
|
||||
?>
|
||||
|
||||
<style>
|
||||
.install-container {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #17a2b8;
|
||||
}
|
||||
|
||||
.install-form {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="install-container">
|
||||
<h1><i class="fa fa-magic"></i> 템플릿 데이터 설치</h1>
|
||||
|
||||
<div class="status-info">
|
||||
<h3>현재 상태</h3>
|
||||
<p><strong>템플릿 수:</strong> <?php echo number_format($template_count); ?>개</p>
|
||||
<p><strong>템플릿 질문 수:</strong> <?php echo number_format($question_count); ?>개</p>
|
||||
|
||||
<?php if ($template_count == 0 || $question_count == 0): ?>
|
||||
<div class="alert alert-warning">
|
||||
<strong>주의:</strong> 템플릿 데이터가 부족합니다. 아래 버튼을 클릭하여 기본 템플릿을 설치하세요.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="install-form">
|
||||
<h3>기본 템플릿 설치</h3>
|
||||
<p>다음 3개의 기본 템플릿과 질문들이 설치됩니다:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>고객 만족도 조사</strong> - 4개 질문 (평점, 객관식, 체크박스, 주관식)</li>
|
||||
<li><strong>제품 피드백 설문</strong> - 5개 질문 (객관식, 평점, 체크박스, 주관식)</li>
|
||||
<li><strong>마케팅 캠페인 효과 측정</strong> - 5개 질문 (객관식, 체크박스, 평점, 주관식)</li>
|
||||
</ul>
|
||||
|
||||
<form method="post" onsubmit="return confirm('템플릿 데이터를 설치하시겠습니까?');">
|
||||
<input type="hidden" name="install_templates" value="1">
|
||||
<input type="hidden" name="token" value="<?php echo get_token(); ?>">
|
||||
|
||||
<?php if ($template_count > 0): ?>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="clear_existing" id="clearExisting">
|
||||
<label for="clearExisting">기존 템플릿 데이터를 모두 삭제하고 새로 설치</label>
|
||||
<small style="display: block; color: #666; margin-top: 5px;">
|
||||
체크하지 않으면 기존 데이터에 추가로 설치됩니다.
|
||||
</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa fa-download"></i> 기본 템플릿 설치하기
|
||||
</button>
|
||||
|
||||
<a href="template_list.php" class="btn btn-warning">
|
||||
<i class="fa fa-list"></i> 템플릿 목록 보기
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
}
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 통계 페이지 JavaScript
|
||||
*/
|
||||
|
||||
// 전역 변수
|
||||
let responseChart;
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 데이터가 로드될 때까지 대기
|
||||
if (typeof window.statisticsData !== 'undefined') {
|
||||
initStatistics();
|
||||
} else {
|
||||
// 데이터 로드 대기
|
||||
setTimeout(() => {
|
||||
if (typeof window.statisticsData !== 'undefined') {
|
||||
initStatistics();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
function initStatistics() {
|
||||
// 차트 초기화
|
||||
initResponseChart();
|
||||
initQuestionCharts();
|
||||
|
||||
// 차트 타입 변경 이벤트
|
||||
initChartControls();
|
||||
|
||||
// 숫자 애니메이션 실행
|
||||
setTimeout(animateNumbers, 500);
|
||||
}
|
||||
|
||||
// 응답 현황 차트 초기화
|
||||
function initResponseChart() {
|
||||
const ctx = document.getElementById('responseChart');
|
||||
if (!ctx || !window.statisticsData) return;
|
||||
|
||||
const dailyData = window.statisticsData.chartData.daily;
|
||||
|
||||
// 일별 데이터 준비 (최근 30일)
|
||||
const dailyLabels = [];
|
||||
const dailyValues = [];
|
||||
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
const label = date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
||||
|
||||
dailyLabels.push(label);
|
||||
dailyValues.push(dailyData[dateStr] || 0);
|
||||
}
|
||||
|
||||
responseChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dailyLabels,
|
||||
datasets: [{
|
||||
label: '일별 응답 수',
|
||||
data: dailyValues,
|
||||
borderColor: '#AA20FF',
|
||||
backgroundColor: 'rgba(170, 32, 255, 0.1)',
|
||||
borderWidth: 3,
|
||||
fill: true,
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 응답 차트 업데이트
|
||||
function updateResponseChart(type) {
|
||||
if (!responseChart || !window.statisticsData) return;
|
||||
|
||||
const dailyData = window.statisticsData.chartData.daily;
|
||||
const hourlyData = window.statisticsData.chartData.hourly;
|
||||
|
||||
if (type === 'hourly') {
|
||||
// 시간대별 데이터 준비
|
||||
const hourlyLabels = [];
|
||||
const hourlyValues = [];
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
hourlyLabels.push(i + '시');
|
||||
hourlyValues.push(hourlyData[i] || 0);
|
||||
}
|
||||
|
||||
responseChart.data.labels = hourlyLabels;
|
||||
responseChart.data.datasets[0].data = hourlyValues;
|
||||
responseChart.data.datasets[0].label = '시간대별 응답 수';
|
||||
responseChart.options.scales.x.title = { display: true, text: '시간' };
|
||||
} else {
|
||||
// 일별 데이터로 복원
|
||||
const dailyLabels = [];
|
||||
const dailyValues = [];
|
||||
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
const label = date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
||||
|
||||
dailyLabels.push(label);
|
||||
dailyValues.push(dailyData[dateStr] || 0);
|
||||
}
|
||||
|
||||
responseChart.data.labels = dailyLabels;
|
||||
responseChart.data.datasets[0].data = dailyValues;
|
||||
responseChart.data.datasets[0].label = '일별 응답 수';
|
||||
responseChart.options.scales.x.title = { display: true, text: '날짜' };
|
||||
}
|
||||
|
||||
responseChart.update();
|
||||
}
|
||||
|
||||
// 질문별 차트 초기화
|
||||
function initQuestionCharts() {
|
||||
// PHP에서 전달된 질문 데이터를 기반으로 차트 생성
|
||||
// 이 부분은 PHP에서 JavaScript로 데이터를 전달받아 처리
|
||||
}
|
||||
|
||||
// 차트 컨트롤 이벤트
|
||||
function initChartControls() {
|
||||
document.querySelectorAll('.chart-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.chart-btn').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
const chartType = this.dataset.chart;
|
||||
updateResponseChart(chartType);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 숫자 애니메이션
|
||||
function animateNumbers() {
|
||||
document.querySelectorAll('.stat-number').forEach(element => {
|
||||
const target = parseInt(element.textContent.replace(/,/g, ''));
|
||||
let current = 0;
|
||||
const increment = target / 50;
|
||||
const timer = setInterval(() => {
|
||||
current += increment;
|
||||
if (current >= target) {
|
||||
current = target;
|
||||
clearInterval(timer);
|
||||
}
|
||||
element.textContent = Math.floor(current).toLocaleString('ko-KR');
|
||||
}, 20);
|
||||
});
|
||||
}
|
||||
|
||||
// 인쇄 기능
|
||||
function printStatistics() {
|
||||
if (!window.statisticsData) return;
|
||||
|
||||
const printWindow = window.open('', '_blank');
|
||||
const data = window.statisticsData;
|
||||
|
||||
const printContent = `
|
||||
<html>
|
||||
<head>
|
||||
<title>설문 통계 - ${data.survey.title}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
|
||||
.stat-item { text-align: center; padding: 15px; border: 1px solid #ddd; }
|
||||
.stat-number { font-size: 2em; font-weight: bold; color: #AA20FF; }
|
||||
.question { margin-bottom: 30px; page-break-inside: avoid; }
|
||||
.question-title { font-size: 1.2em; font-weight: bold; margin-bottom: 15px; }
|
||||
.answer-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; }
|
||||
@media print { .no-print { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>설문 통계</h1>
|
||||
<h2>${data.survey.title}</h2>
|
||||
<p>생성일: ${new Date().toLocaleDateString('ko-KR')}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${data.stats.totalResponses}</div>
|
||||
<div>완료된 응답</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${data.stats.completionRate}%</div>
|
||||
<div>완료율</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${data.stats.avgResponseTime}분</div>
|
||||
<div>평균 응답시간</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${data.stats.startedResponses}</div>
|
||||
<div>진행중인 응답</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${document.querySelector('.questions-stats') ? document.querySelector('.questions-stats').innerHTML : ''}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
printWindow.document.write(printContent);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
}
|
||||
|
||||
// 차트 생성 헬퍼 함수
|
||||
function createQuestionChart(canvasId, chartData, chartType = 'doughnut') {
|
||||
const ctx = document.getElementById(canvasId);
|
||||
if (!ctx) return;
|
||||
|
||||
const colorPalette = ['#AA20FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8'];
|
||||
|
||||
new Chart(ctx, {
|
||||
type: chartType,
|
||||
data: {
|
||||
labels: chartData.labels,
|
||||
datasets: [{
|
||||
data: chartData.data,
|
||||
backgroundColor: colorPalette.slice(0, chartData.data.length),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
padding: 15,
|
||||
usePointStyle: true
|
||||
}
|
||||
}
|
||||
},
|
||||
...(chartType === 'bar' ? {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 데이터 내보내기 함수들
|
||||
function exportToExcel(surveyId) {
|
||||
window.location.href = `export.php?sv_id=${surveyId}&format=excel`;
|
||||
}
|
||||
|
||||
function exportToCSV(surveyId) {
|
||||
window.location.href = `export.php?sv_id=${surveyId}&format=csv`;
|
||||
}
|
||||
|
||||
// 유틸리티 함수들
|
||||
const StatisticsUtils = {
|
||||
formatNumber: function(num) {
|
||||
return new Intl.NumberFormat('ko-KR').format(num);
|
||||
},
|
||||
|
||||
formatPercentage: function(num) {
|
||||
return num.toFixed(1) + '%';
|
||||
},
|
||||
|
||||
calculatePercentage: function(value, total) {
|
||||
return total > 0 ? (value / total) * 100 : 0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
// 설문 관리 관리자 JavaScript
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 확인 대화상자
|
||||
const confirmButtons = document.querySelectorAll('[onclick*="confirm"]');
|
||||
confirmButtons.forEach(button => {
|
||||
button.addEventListener('click', function(e) {
|
||||
const message = this.getAttribute('onclick').match(/confirm\('([^']+)'\)/);
|
||||
if (message && !confirm(message[1])) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 툴팁 초기화
|
||||
const tooltips = document.querySelectorAll('[title]');
|
||||
tooltips.forEach(element => {
|
||||
element.addEventListener('mouseenter', function() {
|
||||
const title = this.getAttribute('title');
|
||||
if (title) {
|
||||
this.setAttribute('data-original-title', title);
|
||||
this.removeAttribute('title');
|
||||
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'tooltip';
|
||||
tooltip.textContent = title;
|
||||
tooltip.style.cssText = `
|
||||
position: absolute;
|
||||
background: #333;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
|
||||
tooltip.style.top = rect.top - tooltip.offsetHeight - 5 + 'px';
|
||||
|
||||
this._tooltip = tooltip;
|
||||
}
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', function() {
|
||||
if (this._tooltip) {
|
||||
document.body.removeChild(this._tooltip);
|
||||
this._tooltip = null;
|
||||
}
|
||||
|
||||
const originalTitle = this.getAttribute('data-original-title');
|
||||
if (originalTitle) {
|
||||
this.setAttribute('title', originalTitle);
|
||||
this.removeAttribute('data-original-title');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 자동 저장 기능 (폼이 있는 경우)
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('change', function() {
|
||||
// 자동 저장 로직 (필요시 구현)
|
||||
console.log('Form data changed:', this.name, this.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 유틸리티 함수들
|
||||
window.SurveyAdmin = window.SurveyAdmin || {
|
||||
// 숫자 포맷팅
|
||||
formatNumber: function(num) {
|
||||
return new Intl.NumberFormat('ko-KR').format(num);
|
||||
},
|
||||
|
||||
// 날짜 포맷팅
|
||||
formatDate: function(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('ko-KR');
|
||||
},
|
||||
|
||||
// 시간 포맷팅
|
||||
formatDateTime: function(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('ko-KR');
|
||||
},
|
||||
|
||||
// AJAX 요청
|
||||
ajax: function(url, data, callback) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', url, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
callback(null, response);
|
||||
} catch (e) {
|
||||
callback(null, xhr.responseText);
|
||||
}
|
||||
} else {
|
||||
callback(new Error('Request failed: ' + xhr.status));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const params = new URLSearchParams(data).toString();
|
||||
xhr.send(params);
|
||||
},
|
||||
|
||||
// 알림 표시
|
||||
showAlert: function(message, type = 'info') {
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type}`;
|
||||
alert.textContent = message;
|
||||
alert.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
min-width: 300px;
|
||||
animation: slideInRight 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(alert);
|
||||
|
||||
setTimeout(() => {
|
||||
alert.style.animation = 'slideOutRight 0.3s ease';
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode) {
|
||||
alert.parentNode.removeChild(alert);
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
// 로딩 표시
|
||||
showLoading: function(show = true) {
|
||||
let loading = document.getElementById('loading-overlay');
|
||||
|
||||
if (show) {
|
||||
if (!loading) {
|
||||
loading = document.createElement('div');
|
||||
loading.id = 'loading-overlay';
|
||||
loading.innerHTML = `
|
||||
<div style="
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
">
|
||||
<div style="
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
">
|
||||
<div style="
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #AA20FF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
"></div>
|
||||
<div>처리 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(loading);
|
||||
}
|
||||
} else {
|
||||
if (loading) {
|
||||
loading.parentNode.removeChild(loading);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// CSS 애니메이션 추가
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@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; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 설문 작성/수정 폼 JavaScript
|
||||
*/
|
||||
|
||||
let questionCount = 0;
|
||||
const questionTypes = {
|
||||
'text': '단답형',
|
||||
'textarea': '장문형',
|
||||
'radio': '단일선택',
|
||||
'checkbox': '다중선택',
|
||||
'select': '드롭다운',
|
||||
'rating': '평점',
|
||||
'date': '날짜'
|
||||
};
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initSurveyForm();
|
||||
});
|
||||
|
||||
function initSurveyForm() {
|
||||
// 탭 전환 이벤트
|
||||
initTabs();
|
||||
|
||||
// 템플릿 선택 이벤트
|
||||
initTemplateSelection();
|
||||
|
||||
// 폼 검증 이벤트
|
||||
initFormValidation();
|
||||
|
||||
// 기존 질문들의 타입에 따라 옵션 표시
|
||||
document.querySelectorAll('.question-type-select').forEach(select => {
|
||||
updateQuestionType(select);
|
||||
});
|
||||
|
||||
// URL에서 template_id가 있으면 해당 템플릿 자동 선택
|
||||
const templateId = document.getElementById('templateId')?.value;
|
||||
if (templateId > 0) {
|
||||
const templateCard = document.querySelector(`[data-template="${templateId}"]`);
|
||||
if (templateCard) {
|
||||
templateCard.click();
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 개수 초기화
|
||||
questionCount = document.querySelectorAll('.question-item').length;
|
||||
}
|
||||
|
||||
// 탭 전환 기능
|
||||
function initTabs() {
|
||||
document.querySelectorAll('.form-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const targetTab = this.dataset.tab;
|
||||
|
||||
// 탭 활성화
|
||||
document.querySelectorAll('.form-tab').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 콘텐츠 표시
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(targetTab).classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 템플릿 선택 기능
|
||||
function initTemplateSelection() {
|
||||
document.querySelectorAll('.template-card').forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
document.querySelectorAll('.template-card').forEach(c => c.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
const templateId = this.dataset.template;
|
||||
document.getElementById('templateId').value = templateId;
|
||||
|
||||
// 템플릿 질문 로드
|
||||
if (templateId > 0) {
|
||||
loadTemplateQuestions(templateId);
|
||||
} else {
|
||||
// 직접 작성 선택 시 기존 질문들 초기화
|
||||
document.getElementById('questionsList').innerHTML = '';
|
||||
questionCount = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 템플릿 질문 로드
|
||||
function loadTemplateQuestions(templateId) {
|
||||
fetch(`ajax_get_template.php?st_id=${templateId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// 기존 질문들 초기화
|
||||
document.getElementById('questionsList').innerHTML = '';
|
||||
questionCount = 0;
|
||||
|
||||
// 템플릿 질문들 추가
|
||||
data.questions.forEach((question, index) => {
|
||||
addTemplateQuestion(question, index + 1);
|
||||
});
|
||||
|
||||
// 기본 정보도 템플릿에서 가져오기
|
||||
if (data.template.st_name) {
|
||||
document.querySelector('input[name="sv_title"]').value = data.template.st_name;
|
||||
}
|
||||
if (data.template.st_description) {
|
||||
document.querySelector('textarea[name="sv_description"]').value = data.template.st_description;
|
||||
}
|
||||
|
||||
alert('템플릿이 적용되었습니다. 질문 설정 탭에서 확인해보세요.');
|
||||
} else {
|
||||
alert('템플릿 로드 실패: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('템플릿 로드 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
// 템플릿 질문 추가
|
||||
function addTemplateQuestion(questionData, questionNumber) {
|
||||
questionCount = questionNumber;
|
||||
|
||||
const optionsHtml = ['radio', 'checkbox', 'select'].includes(questionData.stq_type) && questionData.stq_options.length > 0
|
||||
? `<div class="options-container" style="display: block;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
${questionData.stq_options.map((option, index) => `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" value="${option}" placeholder="선택지 ${index + 1}">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>`
|
||||
: `<div class="options-container" style="display: none;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" placeholder="선택지 1">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" placeholder="선택지 2">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>`;
|
||||
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-question-index="${questionNumber}">
|
||||
<div class="question-header">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div class="question-number">${questionNumber}</div>
|
||||
<div style="flex: 1;">
|
||||
<select name="questions[${questionNumber}][type]" class="question-type-select form-select" onchange="updateQuestionType(this)">
|
||||
<option value="text" ${questionData.stq_type === 'text' ? 'selected' : ''}>단답형</option>
|
||||
<option value="textarea" ${questionData.stq_type === 'textarea' ? 'selected' : ''}>장문형</option>
|
||||
<option value="radio" ${questionData.stq_type === 'radio' ? 'selected' : ''}>단일선택</option>
|
||||
<option value="checkbox" ${questionData.stq_type === 'checkbox' ? 'selected' : ''}>다중선택</option>
|
||||
<option value="select" ${questionData.stq_type === 'select' ? 'selected' : ''}>드롭다운</option>
|
||||
<option value="rating" ${questionData.stq_type === 'rating' ? 'selected' : ''}>평점</option>
|
||||
<option value="date" ${questionData.stq_type === 'date' ? 'selected' : ''}>날짜</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-controls">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'up')">↑</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'down')">↓</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionNumber}][title]" class="form-input"
|
||||
value="${questionData.stq_title}" placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionNumber}][description]" class="form-textarea"
|
||||
placeholder="질문에 대한 추가 설명">${questionData.stq_description || ''}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="questions[${questionNumber}][required]" value="1" ${questionData.stq_required ? 'checked' : ''}>
|
||||
<label>필수 질문</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${optionsHtml}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionsList').insertAdjacentHTML('beforeend', questionHtml);
|
||||
}
|
||||
|
||||
// 질문 추가
|
||||
function addQuestion() {
|
||||
questionCount++;
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-question-index="${questionCount}">
|
||||
<div class="question-header">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div class="question-number">${questionCount}</div>
|
||||
<div style="flex: 1;">
|
||||
<select name="questions[${questionCount}][type]" class="question-type-select form-select" onchange="updateQuestionType(this)">
|
||||
<option value="text">단답형</option>
|
||||
<option value="textarea">장문형</option>
|
||||
<option value="radio">단일선택</option>
|
||||
<option value="checkbox">다중선택</option>
|
||||
<option value="select">드롭다운</option>
|
||||
<option value="rating">평점</option>
|
||||
<option value="date">날짜</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-controls">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'up')">↑</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'down')">↓</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionCount}][title]" class="form-input" placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionCount}][description]" class="form-textarea" placeholder="질문에 대한 추가 설명"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="questions[${questionCount}][required]" value="1">
|
||||
<label>필수 질문</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-container" style="display: none;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionCount}][options][]" class="option-input" placeholder="선택지 1">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionCount}][options][]" class="option-input" placeholder="선택지 2">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionsList').insertAdjacentHTML('beforeend', questionHtml);
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
// 질문 타입 변경
|
||||
function updateQuestionType(select) {
|
||||
const questionItem = select.closest('.question-item');
|
||||
const optionsContainer = questionItem.querySelector('.options-container');
|
||||
const questionType = select.value;
|
||||
|
||||
if (['radio', 'checkbox', 'select'].includes(questionType)) {
|
||||
optionsContainer.style.display = 'block';
|
||||
} else {
|
||||
optionsContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 삭제
|
||||
function removeQuestion(button) {
|
||||
if (confirm('이 질문을 삭제하시겠습니까?')) {
|
||||
button.closest('.question-item').remove();
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 순서 변경
|
||||
function moveQuestion(button, direction) {
|
||||
const questionItem = button.closest('.question-item');
|
||||
const sibling = direction === 'up' ? questionItem.previousElementSibling : questionItem.nextElementSibling;
|
||||
|
||||
if (sibling) {
|
||||
if (direction === 'up') {
|
||||
questionItem.parentNode.insertBefore(questionItem, sibling);
|
||||
} else {
|
||||
questionItem.parentNode.insertBefore(sibling, questionItem);
|
||||
}
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
// 선택지 추가
|
||||
function addOption(button) {
|
||||
const optionsList = button.previousElementSibling;
|
||||
const questionIndex = button.closest('.question-item').dataset.questionIndex;
|
||||
const optionCount = optionsList.children.length + 1;
|
||||
|
||||
const optionHtml = `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionIndex}][options][]" class="option-input" placeholder="선택지 ${optionCount}">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
optionsList.insertAdjacentHTML('beforeend', optionHtml);
|
||||
}
|
||||
|
||||
// 선택지 삭제
|
||||
function removeOption(button) {
|
||||
const optionsList = button.closest('.options-list');
|
||||
if (optionsList.children.length > 2) {
|
||||
button.closest('.option-item').remove();
|
||||
} else {
|
||||
alert('최소 2개의 선택지가 필요합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 번호 업데이트
|
||||
function updateQuestionNumbers() {
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
questions.forEach((question, index) => {
|
||||
const number = index + 1;
|
||||
question.querySelector('.question-number').textContent = number;
|
||||
question.dataset.questionIndex = number;
|
||||
|
||||
// input name 속성 업데이트
|
||||
const inputs = question.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.name && input.name.includes('questions[')) {
|
||||
input.name = input.name.replace(/questions\[\d+\]/, `questions[${number}]`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
questionCount = questions.length;
|
||||
}
|
||||
|
||||
// 폼 검증
|
||||
function initFormValidation() {
|
||||
document.getElementById('surveyForm').addEventListener('submit', function(e) {
|
||||
const title = document.querySelector('input[name="sv_title"]').value.trim();
|
||||
const startDate = new Date(document.querySelector('input[name="sv_start_date"]').value);
|
||||
const endDate = new Date(document.querySelector('input[name="sv_end_date"]').value);
|
||||
|
||||
if (!title) {
|
||||
alert('설문 제목을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
alert('종료일시는 시작일시보다 늦어야 합니다.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
if (questions.length === 0) {
|
||||
alert('최소 1개의 질문을 추가해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 질문 제목 검증
|
||||
let hasEmptyQuestion = false;
|
||||
questions.forEach(question => {
|
||||
const titleInput = question.querySelector('input[name*="[title]"]');
|
||||
if (!titleInput.value.trim()) {
|
||||
hasEmptyQuestion = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasEmptyQuestion) {
|
||||
alert('모든 질문의 제목을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 템플릿 작성/수정 폼 JavaScript
|
||||
*/
|
||||
|
||||
let questionIndex = 0;
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initTemplateForm();
|
||||
});
|
||||
|
||||
function initTemplateForm() {
|
||||
// 초기 데이터 설정
|
||||
if (window.templateFormData) {
|
||||
questionIndex = window.templateFormData.questionIndex;
|
||||
}
|
||||
|
||||
// 폼 검증 이벤트
|
||||
initFormValidation();
|
||||
|
||||
// 기존 질문들의 타입에 따라 옵션 표시
|
||||
document.querySelectorAll('.question-type-select').forEach(select => {
|
||||
toggleOptions(select.closest('.question-item').dataset.index);
|
||||
});
|
||||
}
|
||||
|
||||
function addQuestion() {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
const emptyState = document.getElementById('emptyQuestions');
|
||||
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-index="${questionIndex}">
|
||||
<div class="question-header">
|
||||
<div class="question-number">${questionIndex + 1}</div>
|
||||
<div class="question-title-text">질문 ${questionIndex + 1}</div>
|
||||
<div class="question-actions">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(${questionIndex}, 'up')">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(${questionIndex}, 'down')">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(${questionIndex})">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionIndex}][stq_title]" class="form-control"
|
||||
placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group question-type-group">
|
||||
<label class="form-label">질문 유형</label>
|
||||
<select name="questions[${questionIndex}][stq_type]" class="form-control form-select question-type-select"
|
||||
onchange="toggleOptions(${questionIndex})">
|
||||
<option value="text">단답형</option>
|
||||
<option value="textarea">장문형</option>
|
||||
<option value="radio">객관식(단일)</option>
|
||||
<option value="checkbox">객관식(다중)</option>
|
||||
<option value="select">드롭다운</option>
|
||||
<option value="rating">평점</option>
|
||||
<option value="date">날짜</option>
|
||||
<option value="email">이메일</option>
|
||||
<option value="number">숫자</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionIndex}][stq_description]" class="form-control" rows="2"
|
||||
placeholder="질문에 대한 추가 설명"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="questions[${questionIndex}][stq_required]" value="1">
|
||||
필수 질문
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', questionHtml);
|
||||
questionIndex++;
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
function removeQuestion(index) {
|
||||
if (confirm('이 질문을 삭제하시겠습니까?')) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
questionItem.remove();
|
||||
|
||||
const remainingQuestions = document.querySelectorAll('.question-item');
|
||||
if (remainingQuestions.length === 0) {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
container.innerHTML = `
|
||||
<div class="empty-questions" id="emptyQuestions">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
<h3>질문을 추가해주세요</h3>
|
||||
<p>아래 버튼을 클릭하여 첫 번째 질문을 만들어보세요.</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveQuestion(index, direction) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
const container = document.getElementById('questionsContainer');
|
||||
|
||||
if (direction === 'up' && questionItem.previousElementSibling) {
|
||||
container.insertBefore(questionItem, questionItem.previousElementSibling);
|
||||
} else if (direction === 'down' && questionItem.nextElementSibling) {
|
||||
container.insertBefore(questionItem.nextElementSibling, questionItem);
|
||||
}
|
||||
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
function updateQuestionNumbers() {
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
questions.forEach((question, index) => {
|
||||
const numberElement = question.querySelector('.question-number');
|
||||
const titleElement = question.querySelector('.question-title-text');
|
||||
|
||||
numberElement.textContent = index + 1;
|
||||
titleElement.textContent = `질문 ${index + 1}`;
|
||||
|
||||
// data-index 업데이트
|
||||
question.dataset.index = index;
|
||||
|
||||
// input name 속성 업데이트
|
||||
const inputs = question.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.name && input.name.includes('questions[')) {
|
||||
input.name = input.name.replace(/questions\[\d+\]/, `questions[${index}]`);
|
||||
}
|
||||
});
|
||||
|
||||
// 옵션 컨테이너 ID 업데이트
|
||||
const optionsContainer = question.querySelector('.options-container');
|
||||
if (optionsContainer) {
|
||||
optionsContainer.id = `optionsContainer${index}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleOptions(index) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
const typeSelect = questionItem.querySelector('.question-type-select');
|
||||
const selectedType = typeSelect.value;
|
||||
|
||||
// 기존 옵션 컨테이너 제거
|
||||
const existingOptions = questionItem.querySelector('.options-container');
|
||||
if (existingOptions) {
|
||||
existingOptions.remove();
|
||||
}
|
||||
|
||||
// 객관식 질문인 경우 옵션 컨테이너 추가
|
||||
if (['radio', 'checkbox', 'select'].includes(selectedType)) {
|
||||
const optionsHtml = `
|
||||
<div class="options-container" id="optionsContainer${index}">
|
||||
<label class="form-label">선택 옵션</label>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${index}][options][]"
|
||||
class="form-control option-input"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn-add-option" onclick="addOption(${index})">
|
||||
<i class="fa fa-plus"></i> 옵션 추가
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
questionItem.insertAdjacentHTML('beforeend', optionsHtml);
|
||||
}
|
||||
}
|
||||
|
||||
function addOption(questionIndex) {
|
||||
const container = document.getElementById(`optionsContainer${questionIndex}`);
|
||||
const addButton = container.querySelector('.btn-add-option');
|
||||
|
||||
const optionHtml = `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionIndex}][options][]"
|
||||
class="form-control option-input"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
addButton.insertAdjacentHTML('beforebegin', optionHtml);
|
||||
}
|
||||
|
||||
function removeOption(button) {
|
||||
const optionsContainer = button.closest('.options-container');
|
||||
const optionItems = optionsContainer.querySelectorAll('.option-item');
|
||||
|
||||
if (optionItems.length > 1) {
|
||||
button.parentElement.remove();
|
||||
} else {
|
||||
alert('최소 1개의 옵션이 필요합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 폼 검증
|
||||
function initFormValidation() {
|
||||
document.querySelector('form[name="templateForm"]').addEventListener('submit', function(e) {
|
||||
const templateName = document.querySelector('input[name="st_name"]').value.trim();
|
||||
if (!templateName) {
|
||||
alert('템플릿 이름을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
if (questions.length === 0) {
|
||||
alert('최소 1개 이상의 질문을 추가해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 각 질문의 제목 검증
|
||||
let hasEmptyTitle = false;
|
||||
questions.forEach((question, index) => {
|
||||
const titleInput = question.querySelector('input[name*="[stq_title]"]');
|
||||
if (!titleInput.value.trim()) {
|
||||
alert(`질문 ${index + 1}의 제목을 입력해주세요.`);
|
||||
hasEmptyTitle = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasEmptyTitle) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 객관식 질문의 옵션 검증
|
||||
let hasEmptyOptions = false;
|
||||
questions.forEach((question, index) => {
|
||||
const typeSelect = question.querySelector('.question-type-select');
|
||||
const selectedType = typeSelect.value;
|
||||
|
||||
if (['radio', 'checkbox', 'select'].includes(selectedType)) {
|
||||
const optionInputs = question.querySelectorAll('.option-input');
|
||||
const filledOptions = Array.from(optionInputs).filter(input => input.value.trim());
|
||||
|
||||
if (filledOptions.length < 2) {
|
||||
alert(`질문 ${index + 1}은 최소 2개의 옵션이 필요합니다.`);
|
||||
hasEmptyOptions = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasEmptyOptions) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
private $sql_file_path;
|
||||
private $results;
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
* @param string $sql_file_path install.sql 파일의 절대 경로
|
||||
*/
|
||||
public function __construct($sql_file_path)
|
||||
{
|
||||
if (!file_exists($sql_file_path)) {
|
||||
throw new Exception($sql_file_path . ' 파일을 찾을 수 없습니다.');
|
||||
}
|
||||
$this->sql_file_path = $sql_file_path;
|
||||
$this->results = [
|
||||
'created' => [],
|
||||
'existing' => [],
|
||||
'updated' => [],
|
||||
'failed' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 스키마 설치/업데이트를 실행합니다.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$sql_statements = $this->parse_sql_file();
|
||||
|
||||
foreach ($sql_statements as $stmt) {
|
||||
// CREATE TABLE 문인지 확인
|
||||
if (preg_match('/^CREATE\s+TABLE/i', $stmt)) {
|
||||
$schema = $this->parse_create_table_sql($stmt);
|
||||
if ($schema && !empty($schema['name'])) {
|
||||
$this->process_table_schema($stmt, $schema);
|
||||
}
|
||||
} else {
|
||||
// CREATE TABLE 문이 아닌 다른 SQL 문 (e.g. INSERT, UPDATE)
|
||||
sql_query($stmt, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 결과를 반환합니다.
|
||||
* @return array
|
||||
*/
|
||||
public function get_results()
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 스키마를 처리합니다. (생성 또는 업데이트)
|
||||
* @param string $create_sql 전체 CREATE TABLE 구문
|
||||
* @param array $schema 파싱된 스키마 정보
|
||||
*/
|
||||
private function process_table_schema($create_sql, $schema)
|
||||
{
|
||||
$table_name = $schema['name'];
|
||||
|
||||
if ($this->table_exists($table_name)) {
|
||||
// 테이블이 존재하면, 컬럼 비교 및 추가
|
||||
$this->results['existing'][] = $table_name;
|
||||
$this->update_table_columns($table_name, $schema['columns']);
|
||||
} else {
|
||||
// 테이블이 존재하지 않으면, 새로 생성
|
||||
if (sql_query($create_sql, false)) {
|
||||
$this->results['created'][] = $table_name;
|
||||
} else {
|
||||
$this->results['failed'][] = $table_name;
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . sql_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 컬럼 구조를 업데이트합니다.
|
||||
* @param string $table_name
|
||||
* @param array $target_columns .sql 파일에 정의된 컬럼 목록
|
||||
*/
|
||||
private function update_table_columns($table_name, $target_columns)
|
||||
{
|
||||
$current_columns = $this->get_current_columns($table_name);
|
||||
$added_columns_in_table = [];
|
||||
|
||||
foreach ($target_columns as $col_name => $col_definition) {
|
||||
// 현재 테이블에 해당 컬럼이 없으면 추가
|
||||
if (!isset($current_columns[$col_name])) {
|
||||
$alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `{$col_name}` {$col_definition}";
|
||||
if (sql_query($alter_sql, false)) {
|
||||
$added_columns_in_table[] = $col_name;
|
||||
} else {
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . sql_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($added_columns_in_table)) {
|
||||
$this->results['updated'][$table_name] = $added_columns_in_table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL 파일을 읽고 각 구문으로 분리합니다.
|
||||
* @return array
|
||||
*/
|
||||
private function parse_sql_file()
|
||||
{
|
||||
$sql = file_get_contents($this->sql_file_path);
|
||||
// 주석 제거 (SQL 주석 '--' 와 C-style '/* ... */' 주석)
|
||||
$sql = preg_replace('/--.*/', '', $sql);
|
||||
$sql = preg_replace('!/\*.*?\*/!s', '', $sql);
|
||||
$sql = trim($sql);
|
||||
|
||||
// 세미콜론(;)을 기준으로 쿼리 분리
|
||||
return array_filter(array_map('trim', explode(';', $sql)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 존재 여부를 확인합니다.
|
||||
* @param string $table_name
|
||||
* @return bool
|
||||
*/
|
||||
private function table_exists($table_name)
|
||||
{
|
||||
$res = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
|
||||
return sql_num_rows($res) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
|
||||
* @param string $table_name
|
||||
* @return array
|
||||
*/
|
||||
private function get_current_columns($table_name)
|
||||
{
|
||||
$res = sql_query("SHOW COLUMNS FROM `{$table_name}`", false);
|
||||
$columns = [];
|
||||
while ($row = sql_fetch_array($res)) {
|
||||
$columns[$row['Field']] = true;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE SQL 구문에서 테이블명과 컬럼 정의를 파싱합니다.
|
||||
* @param string $query CREATE TABLE 구문
|
||||
* @return array|null
|
||||
*/
|
||||
private function parse_create_table_sql($query)
|
||||
{
|
||||
$table_name = '';
|
||||
if (preg_match('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?(\w+)`?/i', $query, $matches)) {
|
||||
$table_name = $matches[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 괄호 안의 내용만 추출
|
||||
$start = strpos($query, '(');
|
||||
$end = strrpos($query, ')');
|
||||
if ($start === false || $end === false) {
|
||||
return ['name' => $table_name, 'columns' => []];
|
||||
}
|
||||
$content = substr($query, $start + 1, $end - $start - 1);
|
||||
|
||||
// 줄 단위로 분리
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$columns = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line, " \t\n\r\0\x0B,"); // 양쪽 공백과 마지막 쉼표 제거
|
||||
|
||||
// 컬럼 정의 라인인지 확인 (첫 단어가 `column_name` 형태)
|
||||
if (preg_match('/^`(\w+)`\s+(.*)/i', $line, $match)) {
|
||||
$col_name = $match[1];
|
||||
$col_definition = $match[2];
|
||||
$columns[$col_name] = $col_definition;
|
||||
}
|
||||
}
|
||||
|
||||
return ['name' => $table_name, 'columns' => $columns];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
|
||||
// 💡 [핵심 수정] 라이브러리가 어디서든 독립적으로 실행될 수 있도록 필요한 상수를 정의합니다.
|
||||
// AJAX 등 다른 경로로 라이브러리만 직접 호출될 때, 상수가 정의되지 않는 문제를 해결합니다.
|
||||
// if(!defined(...))로 감싸서 _common.php 등에서 이미 정의된 경우 충돌을 방지합니다.
|
||||
if (!defined('SURVEY_STATUS_DRAFT')) define('SURVEY_STATUS_DRAFT', 'draft');
|
||||
if (!defined('SURVEY_STATUS_ACTIVE')) define('SURVEY_STATUS_ACTIVE', 'active');
|
||||
if (!defined('SURVEY_STATUS_CLOSED')) define('SURVEY_STATUS_CLOSED', 'closed');
|
||||
if (!defined('SURVEY_STATUS_DELETED')) define('SURVEY_STATUS_DELETED', 'deleted');
|
||||
|
||||
if (!defined('RESPONSE_STATUS_STARTED')) define('RESPONSE_STATUS_STARTED', 'started');
|
||||
if (!defined('RESPONSE_STATUS_COMPLETED')) define('RESPONSE_STATUS_COMPLETED', 'completed');
|
||||
if (!defined('RESPONSE_STATUS_ABANDONED')) define('RESPONSE_STATUS_ABANDONED', 'abandoned');
|
||||
|
||||
/**
|
||||
* 설문 관리 라이브러리 함수들
|
||||
*/
|
||||
/**
|
||||
* 설문지 정보 가져오기
|
||||
*/
|
||||
function get_survey($sv_id) {
|
||||
global $g5;
|
||||
|
||||
$sql = "SELECT * FROM survey_master WHERE sv_id = '$sv_id'";
|
||||
return sql_fetch($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 질문 목록 가져오기
|
||||
*/
|
||||
function get_survey_questions($sv_id) {
|
||||
global $g5;
|
||||
|
||||
$sql = "SELECT * FROM survey_questions WHERE sv_id = '$sv_id' ORDER BY sq_order ASC";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$questions = array();
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
if ($row['sq_options']) {
|
||||
$row['sq_options'] = json_decode($row['sq_options'], true);
|
||||
}
|
||||
if ($row['sq_validation']) {
|
||||
$row['sq_validation'] = json_decode($row['sq_validation'], true);
|
||||
}
|
||||
$questions[] = $row;
|
||||
}
|
||||
|
||||
return $questions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 응답 수 가져오기
|
||||
*/
|
||||
function get_survey_response_count($sv_id, $status = 'completed') {
|
||||
global $g5;
|
||||
|
||||
$where = "sv_id = '$sv_id'";
|
||||
if ($status) {
|
||||
$where .= " AND sr_status = '$status'";
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) as cnt FROM survey_responses WHERE $where";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
return $row['cnt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 상태 업데이트
|
||||
*/
|
||||
function update_survey_status($sv_id, $status) {
|
||||
global $g5;
|
||||
|
||||
$sql = "UPDATE survey_master SET sv_status = '$status', sv_updated_at = NOW() WHERE sv_id = '$sv_id'";
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 응답 시작
|
||||
*/
|
||||
function start_survey_response($sv_id, $mb_id = null, $ip = '', $user_agent = '', $session_id = '') {
|
||||
global $g5;
|
||||
|
||||
$sql = "INSERT INTO survey_responses
|
||||
(sv_id, sr_mb_id, sr_ip, sr_user_agent, sr_session_id, sr_started_at, sr_status)
|
||||
VALUES
|
||||
('$sv_id', ".($mb_id ? "'$mb_id'" : 'NULL').", '$ip', '$user_agent', '$session_id', NOW(), '".RESPONSE_STATUS_STARTED."')";
|
||||
|
||||
sql_query($sql);
|
||||
return sql_insert_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 응답 완료
|
||||
*/
|
||||
function complete_survey_response($sr_id) {
|
||||
global $g5;
|
||||
|
||||
$sql = "UPDATE survey_responses
|
||||
SET sr_status = '".RESPONSE_STATUS_COMPLETED."', sr_completed_at = NOW()
|
||||
WHERE sr_id = '$sr_id'";
|
||||
|
||||
return sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 답변 저장
|
||||
*/
|
||||
function save_survey_answer($sr_id, $sq_id, $value, $text = '') {
|
||||
global $g5;
|
||||
|
||||
// 기존 답변 삭제
|
||||
$sql = "DELETE FROM survey_answers WHERE sr_id = '$sr_id' AND sq_id = '$sq_id'";
|
||||
sql_query($sql);
|
||||
|
||||
// 새 답변 저장
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
$sql = "INSERT INTO survey_answers (sr_id, sq_id, sa_value, sa_text, sa_created_at)
|
||||
VALUES ('$sr_id', '$sq_id', '$v', '$text', NOW())";
|
||||
sql_query($sql);
|
||||
}
|
||||
} else {
|
||||
$sql = "INSERT INTO survey_answers (sr_id, sq_id, sa_value, sa_text, sa_created_at)
|
||||
VALUES ('$sr_id', '$sq_id', '$value', '$text', NOW())";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 통계 업데이트
|
||||
*/
|
||||
function update_survey_statistics($sv_id) {
|
||||
global $g5;
|
||||
|
||||
// 기존 통계 삭제
|
||||
$sql = "DELETE FROM survey_statistics WHERE sv_id = '$sv_id'";
|
||||
sql_query($sql);
|
||||
|
||||
// 질문별 통계 생성
|
||||
$questions = get_survey_questions($sv_id);
|
||||
$total_responses = get_survey_response_count($sv_id, 'completed');
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$sq_id = $question['sq_id'];
|
||||
|
||||
if (in_array($question['sq_type'], ['radio', 'checkbox', 'select'])) {
|
||||
// 선택형 질문 통계
|
||||
$sql = "SELECT sa_value, COUNT(*) as cnt
|
||||
FROM survey_answers sa
|
||||
JOIN survey_responses sr ON sa.sr_id = sr.sr_id
|
||||
WHERE sr.sv_id = '$sv_id' AND sa.sq_id = '$sq_id' AND sr.sr_status = 'completed'
|
||||
GROUP BY sa_value";
|
||||
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$percentage = $total_responses > 0 ? round(($row['cnt'] / $total_responses) * 100, 2) : 0;
|
||||
|
||||
$insert_sql = "INSERT INTO survey_statistics
|
||||
(sv_id, sq_id, ss_option_value, ss_count, ss_percentage)
|
||||
VALUES
|
||||
('$sv_id', '$sq_id', '{$row['sa_value']}', '{$row['cnt']}', '$percentage')";
|
||||
sql_query($insert_sql);
|
||||
}
|
||||
} elseif ($question['sq_type'] == 'rating') {
|
||||
// 평점 질문 통계
|
||||
$sql = "SELECT sa_value, COUNT(*) as cnt
|
||||
FROM survey_answers sa
|
||||
JOIN survey_responses sr ON sa.sr_id = sr.sr_id
|
||||
WHERE sr.sv_id = '$sv_id' AND sa.sq_id = '$sq_id' AND sr.sr_status = 'completed'
|
||||
GROUP BY sa_value
|
||||
ORDER BY CAST(sa_value AS UNSIGNED)";
|
||||
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$percentage = $total_responses > 0 ? round(($row['cnt'] / $total_responses) * 100, 2) : 0;
|
||||
|
||||
$insert_sql = "INSERT INTO survey_statistics
|
||||
(sv_id, sq_id, ss_option_value, ss_count, ss_percentage)
|
||||
VALUES
|
||||
('$sv_id', '$sq_id', '{$row['sa_value']}', '{$row['cnt']}', '$percentage')";
|
||||
sql_query($insert_sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 템플릿 목록 가져오기
|
||||
*/
|
||||
function get_survey_templates($category = '') {
|
||||
global $g5;
|
||||
|
||||
$where = "st_is_public = 1";
|
||||
if ($category) {
|
||||
$where .= " AND st_category = '$category'";
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM survey_templates WHERE $where ORDER BY st_created_at DESC";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$templates = array();
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$templates[] = $row;
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 템플릿으로 설문 생성
|
||||
*/
|
||||
function create_survey_from_template($template_id, $title, $created_by) {
|
||||
global $g5;
|
||||
|
||||
$template = sql_fetch("SELECT * FROM survey_templates WHERE st_id = '$template_id'");
|
||||
if (!$template) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$template_data = json_decode($template['st_data'], true);
|
||||
|
||||
// 설문지 생성
|
||||
$sql = "INSERT INTO survey_master
|
||||
(sv_title, sv_description, sv_start_date, sv_end_date, sv_status, sv_created_by)
|
||||
VALUES
|
||||
('$title', '{$template_data['description']}', NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), '".SURVEY_STATUS_DRAFT."', '$created_by')";
|
||||
|
||||
sql_query($sql);
|
||||
$sv_id = sql_insert_id();
|
||||
|
||||
// 질문들 생성
|
||||
foreach ($template_data['questions'] as $index => $question) {
|
||||
$sq_order = $index + 1;
|
||||
$sq_type = $question['type'];
|
||||
$sq_title = addslashes($question['title']);
|
||||
$sq_required = isset($question['required']) && $question['required'] ? 1 : 0;
|
||||
$sq_options = isset($question['options']) ? addslashes(json_encode($question['options'])) : '';
|
||||
$sq_validation = isset($question['validation']) ? addslashes(json_encode($question['validation'])) : '';
|
||||
|
||||
$sql = "INSERT INTO survey_questions
|
||||
(sv_id, sq_order, sq_type, sq_title, sq_required, sq_options, sq_validation)
|
||||
VALUES
|
||||
('$sv_id', '$sq_order', '$sq_type', '$sq_title', '$sq_required', '$sq_options', '$sq_validation')";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
return $sv_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 데이터 엑셀 내보내기용 배열 생성
|
||||
*/
|
||||
function get_survey_export_data($sv_id) {
|
||||
global $g5;
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
$questions = get_survey_questions($sv_id);
|
||||
|
||||
// 헤더 생성
|
||||
$headers = ['응답ID', '응답자', 'IP주소', '시작시간', '완료시간'];
|
||||
foreach ($questions as $question) {
|
||||
$headers[] = strip_tags($question['sq_title']);
|
||||
}
|
||||
|
||||
// 데이터 생성
|
||||
$data = array();
|
||||
$data[] = $headers;
|
||||
|
||||
$sql = "SELECT * FROM survey_responses
|
||||
WHERE sv_id = '$sv_id' AND sr_status = 'completed'
|
||||
ORDER BY sr_completed_at DESC";
|
||||
|
||||
$result = sql_query($sql);
|
||||
while ($response = sql_fetch_array($result)) {
|
||||
$row = array();
|
||||
$row[] = $response['sr_id'];
|
||||
$row[] = $response['sr_mb_id'] ?: '익명';
|
||||
$row[] = $response['sr_ip'];
|
||||
$row[] = $response['sr_started_at'];
|
||||
$row[] = $response['sr_completed_at'];
|
||||
|
||||
// 각 질문별 답변
|
||||
foreach ($questions as $question) {
|
||||
$answer_sql = "SELECT sa_value, sa_text FROM survey_answers
|
||||
WHERE sr_id = '{$response['sr_id']}' AND sq_id = '{$question['sq_id']}'";
|
||||
$answer_result = sql_query($answer_sql);
|
||||
|
||||
$answers = array();
|
||||
while ($answer = sql_fetch_array($answer_result)) {
|
||||
if ($answer['sa_text']) {
|
||||
$answers[] = $answer['sa_value'] . ' (' . $answer['sa_text'] . ')';
|
||||
} else {
|
||||
$answers[] = $answer['sa_value'];
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = implode(', ', $answers);
|
||||
}
|
||||
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설문 유효성 검사
|
||||
*/
|
||||
function validate_survey_access($sv_id, $mb_id = null, $ip = '') {
|
||||
global $g5;
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
return array('success' => false, 'message' => '존재하지 않는 설문입니다.');
|
||||
}
|
||||
|
||||
// 상태 확인
|
||||
if ($survey['sv_status'] != SURVEY_STATUS_ACTIVE) {
|
||||
return array('success' => false, 'message' => '현재 참여할 수 없는 설문입니다.');
|
||||
}
|
||||
|
||||
// 기간 확인
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($now < $survey['sv_start_date']) {
|
||||
return array('success' => false, 'message' => '아직 시작되지 않은 설문입니다.');
|
||||
}
|
||||
|
||||
if ($now > $survey['sv_end_date']) {
|
||||
return array('success' => false, 'message' => '종료된 설문입니다.');
|
||||
}
|
||||
|
||||
// 중복 참여 확인
|
||||
if (!$survey['sv_allow_multiple']) {
|
||||
$where = "sv_id = '$sv_id' AND sr_status = 'completed'";
|
||||
|
||||
if ($mb_id) {
|
||||
$where .= " AND sr_mb_id = '$mb_id'";
|
||||
} else {
|
||||
$where .= " AND sr_ip = '$ip'";
|
||||
}
|
||||
|
||||
$existing = sql_fetch("SELECT COUNT(*) as cnt FROM survey_responses WHERE $where");
|
||||
if ($existing['cnt'] > 0) {
|
||||
return array('success' => false, 'message' => '이미 참여하신 설문입니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 최대 응답 수 확인
|
||||
if ($survey['sv_max_responses']) {
|
||||
$total_responses = get_survey_response_count($sv_id, 'completed');
|
||||
if ($total_responses >= $survey['sv_max_responses']) {
|
||||
return array('success' => false, 'message' => '설문 참여 인원이 마감되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
return array('success' => true, 'survey' => $survey);
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
$sub_menu = '710304';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$g5['title'] = '응답 관리';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 설문 선택
|
||||
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
|
||||
|
||||
// 설문 목록 가져오기
|
||||
$survey_list = array();
|
||||
$survey_sql = "SELECT sv_id, sv_title FROM survey_master ORDER BY sv_created_at DESC";
|
||||
$survey_result = sql_query($survey_sql);
|
||||
while ($survey_row = sql_fetch_array($survey_result)) {
|
||||
$survey_list[] = $survey_row;
|
||||
}
|
||||
|
||||
if ($sv_id) {
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', 'response_list.php');
|
||||
}
|
||||
}
|
||||
|
||||
// 변수 초기화
|
||||
$sfl = isset($_GET['sfl']) ? clean_xss_tags($_GET['sfl']) : 'sr_mb_id';
|
||||
$stx = isset($_GET['stx']) ? clean_xss_tags($_GET['stx']) : '';
|
||||
$status = isset($_GET['status']) ? clean_xss_tags($_GET['status']) : '';
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
|
||||
// 검색 조건
|
||||
$where = " WHERE 1=1 ";
|
||||
$sql_search = "";
|
||||
|
||||
if ($sv_id) {
|
||||
$where .= " AND sr.sv_id = '".sql_real_escape_string($sv_id)."' ";
|
||||
$sql_search .= "&sv_id=$sv_id";
|
||||
}
|
||||
|
||||
if ($stx) {
|
||||
if ($sfl == 'sr_mb_id') {
|
||||
$where .= " AND sr.sr_mb_id LIKE '%".sql_real_escape_string($stx)."%' ";
|
||||
} elseif ($sfl == 'sr_ip') {
|
||||
$where .= " AND sr.sr_ip LIKE '%".sql_real_escape_string($stx)."%' ";
|
||||
}
|
||||
$sql_search .= "&sfl=$sfl&stx=".urlencode($stx);
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
$where .= " AND sr.sr_status = '".sql_real_escape_string($status)."' ";
|
||||
$sql_search .= "&status=".urlencode($status);
|
||||
}
|
||||
|
||||
// 페이징
|
||||
$sql_common = " FROM survey_responses sr
|
||||
LEFT JOIN survey_master sm ON sr.sv_id = sm.sv_id
|
||||
$where ";
|
||||
|
||||
$sql = " SELECT COUNT(*) as cnt $sql_common ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = 20;
|
||||
$total_page = ceil($total_count / $rows);
|
||||
if ($page < 1) $page = 1;
|
||||
$from_record = ($page - 1) * $rows;
|
||||
|
||||
$sql = " SELECT sr.*, sm.sv_title
|
||||
$sql_common
|
||||
ORDER BY sr.sr_started_at DESC
|
||||
LIMIT $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 상태별 카운트
|
||||
$status_counts = array();
|
||||
$status_where = $sv_id ? " WHERE sv_id = '$sv_id' " : " WHERE 1=1 ";
|
||||
$status_sql = "SELECT sr_status, COUNT(*) as cnt FROM survey_responses $status_where GROUP BY sr_status";
|
||||
$status_result = sql_query($status_sql);
|
||||
while ($status_row = sql_fetch_array($status_result)) {
|
||||
$status_counts[$status_row['sr_status']] = $status_row['cnt'];
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.response-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.response-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.survey-selector {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.survey-selector select {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.status-btn:hover,
|
||||
.status-btn.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.response-table {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.response-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.response-table th {
|
||||
background: #fff;
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.response-table td {
|
||||
padding: 15px 10px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.response-table tr:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-started { background: #fff3cd; color: #856404; }
|
||||
.status-completed { background: #d4edda; color: #155724; }
|
||||
.status-abandoned { background: #f8d7da; color: #721c24; }
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-info { background: #17a2b8; color: white; }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
|
||||
.btn-sm:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form select,
|
||||
.search-form input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.response-stats {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.response-table {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.response-table th,
|
||||
.response-table td {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="response-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-users"></i> 응답 관리</h1>
|
||||
<p>설문 응답을 관리하고 분석할 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="response-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['completed'] ?? 0); ?></div>
|
||||
<div class="stat-label">완료된 응답</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['started'] ?? 0); ?></div>
|
||||
<div class="stat-label">진행중인 응답</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['abandoned'] ?? 0); ?></div>
|
||||
<div class="stat-label">중단된 응답</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_count); ?></div>
|
||||
<div class="stat-label">전체 응답</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="survey-selector">
|
||||
<label for="survey_select"><strong>설문 선택:</strong></label>
|
||||
<select id="survey_select" onchange="location.href='?sv_id='+this.value">
|
||||
<option value="">전체 설문</option>
|
||||
<?php foreach ($survey_list as $survey_item): ?>
|
||||
<option value="<?php echo $survey_item['sv_id']; ?>" <?php echo $sv_id == $survey_item['sv_id'] ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($survey_item['sv_title']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<form method="get" class="form-row">
|
||||
<?php if ($sv_id): ?>
|
||||
<input type="hidden" name="sv_id" value="<?php echo $sv_id; ?>">
|
||||
<?php endif; ?>
|
||||
<select name="sfl">
|
||||
<option value="sr_mb_id"<?php echo $sfl == 'sr_mb_id' ? ' selected' : ''; ?>>회원ID</option>
|
||||
<option value="sr_ip"<?php echo $sfl == 'sr_ip' ? ' selected' : ''; ?>>IP주소</option>
|
||||
</select>
|
||||
<input type="text" name="stx" value="<?php echo $stx; ?>" placeholder="검색어를 입력하세요">
|
||||
<button type="submit" class="btn-sm btn-primary">
|
||||
<i class="fa fa-search"></i> 검색
|
||||
</button>
|
||||
<?php if ($stx): ?>
|
||||
<a href="?<?php echo $sv_id ? 'sv_id='.$sv_id : ''; ?>" class="btn-sm btn-warning">
|
||||
<i class="fa fa-times"></i> 초기화
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="status-filter">
|
||||
<a href="?<?php echo $sv_id ? 'sv_id='.$sv_id : ''; ?>" class="status-btn <?php echo !isset($_GET['status']) ? 'active' : ''; ?>">
|
||||
<i class="fa fa-list"></i> 전체
|
||||
</a>
|
||||
<a href="?<?php echo $sv_id ? 'sv_id='.$sv_id.'&' : ''; ?>status=completed" class="status-btn <?php echo $status == 'completed' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-check"></i> 완료 (<?php echo $status_counts['completed'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?<?php echo $sv_id ? 'sv_id='.$sv_id.'&' : ''; ?>status=started" class="status-btn <?php echo $status == 'started' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-play"></i> 진행중 (<?php echo $status_counts['started'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?<?php echo $sv_id ? 'sv_id='.$sv_id.'&' : ''; ?>status=abandoned" class="status-btn <?php echo $status == 'abandoned' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-times"></i> 중단 (<?php echo $status_counts['abandoned'] ?? 0; ?>)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="response-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<?php if (!$sv_id): ?>
|
||||
<th>설문 제목</th>
|
||||
<?php endif; ?>
|
||||
<th width="100">응답자</th>
|
||||
<th width="120">IP주소</th>
|
||||
<th width="80">상태</th>
|
||||
<th width="120">시작시간</th>
|
||||
<th width="120">완료시간</th>
|
||||
<th width="150">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
if (sql_num_rows($result) > 0) {
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
// 상태별 클래스
|
||||
$status_class = 'status-' . $row['sr_status'];
|
||||
$status_text = array(
|
||||
'started' => '진행중',
|
||||
'completed' => '완료',
|
||||
'abandoned' => '중단'
|
||||
);
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo $row['sr_id']; ?></td>
|
||||
<?php if (!$sv_id): ?>
|
||||
<td>
|
||||
<strong><?php echo htmlspecialchars($row['sv_title']); ?></strong>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td><?php echo $row['sr_mb_id'] ?: '익명'; ?></td>
|
||||
<td><?php echo $row['sr_ip']; ?></td>
|
||||
<td>
|
||||
<span class="status-badge <?php echo $status_class; ?>">
|
||||
<?php echo $status_text[$row['sr_status']]; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<small><?php echo date('Y-m-d H:i', strtotime($row['sr_started_at'])); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($row['sr_completed_at']): ?>
|
||||
<small><?php echo date('Y-m-d H:i', strtotime($row['sr_completed_at'])); ?></small>
|
||||
<?php else: ?>
|
||||
<span style="color: #999;">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<?php if ($row['sr_status'] == 'completed'): ?>
|
||||
<a href="response_view.php?sr_id=<?php echo $row['sr_id']; ?>" class="btn-sm btn-primary" title="응답 보기">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a href="statistics.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-info" title="통계">
|
||||
<i class="fa fa-chart-bar"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<a href="response_delete.php?sr_id=<?php echo $row['sr_id']; ?>" class="btn-sm btn-danger" title="삭제" onclick="return confirm('정말 삭제하시겠습니까?')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<tr>
|
||||
<td colspan="<?php echo $sv_id ? '7' : '8'; ?>" style="text-align: center; padding: 50px; color: #999;">
|
||||
<i class="fa fa-inbox" style="font-size: 3em; margin-bottom: 20px; display: block;"></i>
|
||||
등록된 응답이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 페이징
|
||||
if ($total_page > 1) {
|
||||
$paging = get_paging(10, $page, $total_page, "?$sql_search&page=");
|
||||
echo '<div style="margin-top: 20px;">' . $paging . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
$sub_menu = '710310';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$sr_id = isset($_GET['sr_id']) ? (int)$_GET['sr_id'] : 0;
|
||||
|
||||
if (!$sr_id) {
|
||||
alert('응답을 선택해주세요.', 'response_list.php');
|
||||
}
|
||||
|
||||
// 응답 정보 가져오기
|
||||
$response = sql_fetch("
|
||||
SELECT sr.*, sm.sv_title, sm.sv_description
|
||||
FROM survey_responses sr
|
||||
LEFT JOIN survey_master sm ON sr.sv_id = sm.sv_id
|
||||
WHERE sr.sr_id = '$sr_id'
|
||||
");
|
||||
|
||||
if (!$response) {
|
||||
alert('존재하지 않는 응답입니다.', 'response_list.php');
|
||||
}
|
||||
|
||||
$survey = get_survey($response['sv_id']);
|
||||
$questions = get_survey_questions($response['sv_id']);
|
||||
|
||||
// 답변 데이터 가져오기
|
||||
$answers = array();
|
||||
$answer_sql = "SELECT * FROM survey_answers WHERE sr_id = '$sr_id' ORDER BY sq_id";
|
||||
$answer_result = sql_query($answer_sql);
|
||||
while ($answer_row = sql_fetch_array($answer_result)) {
|
||||
if (!isset($answers[$answer_row['sq_id']])) {
|
||||
$answers[$answer_row['sq_id']] = array();
|
||||
}
|
||||
$answers[$answer_row['sq_id']][] = $answer_row;
|
||||
}
|
||||
|
||||
$g5['title'] = '응답 상세보기 - ' . $response['sv_title'];
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 응답 시간 계산
|
||||
$response_time = 0;
|
||||
if ($response['sr_completed_at'] && $response['sr_started_at']) {
|
||||
$start = strtotime($response['sr_started_at']);
|
||||
$end = strtotime($response['sr_completed_at']);
|
||||
$response_time = round(($end - $start) / 60, 1); // 분 단위
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.response-view-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.response-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.response-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.response-info {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-completed { background: #d4edda; color: #155724; }
|
||||
.status-started { background: #fff3cd; color: #856404; }
|
||||
.status-abandoned { background: #f8d7da; color: #721c24; }
|
||||
|
||||
.answers-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.answer-item {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 25px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.answer-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.question-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
border-radius: 10px;
|
||||
font-size: 0.7em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
margin-left: 35px;
|
||||
padding: 15px 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.answer-value {
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.answer-text {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.multiple-answers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.answer-chip {
|
||||
display: inline-block;
|
||||
padding: 5px 12px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-radius: 15px;
|
||||
font-size: 0.9em;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.no-answer {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
background: #138496;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.response-view-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="response-view-container">
|
||||
<div class="response-header">
|
||||
<h1><i class="fa fa-eye"></i> 응답 상세보기</h1>
|
||||
<p><?php echo htmlspecialchars($response['sv_title']); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="response-info">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">응답 ID</div>
|
||||
<div class="info-value"><?php echo $response['sr_id']; ?></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">응답자</div>
|
||||
<div class="info-value"><?php echo $response['sr_mb_id'] ?: '익명'; ?></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">IP 주소</div>
|
||||
<div class="info-value"><?php echo $response['sr_ip']; ?></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">상태</div>
|
||||
<div class="info-value">
|
||||
<span class="status-badge status-<?php echo $response['sr_status']; ?>">
|
||||
<?php
|
||||
$status_text = array(
|
||||
'started' => '진행중',
|
||||
'completed' => '완료',
|
||||
'abandoned' => '중단'
|
||||
);
|
||||
echo $status_text[$response['sr_status']];
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">시작 시간</div>
|
||||
<div class="info-value">
|
||||
<small><?php echo date('Y-m-d H:i:s', strtotime($response['sr_started_at'])); ?></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">완료 시간</div>
|
||||
<div class="info-value">
|
||||
<?php if ($response['sr_completed_at']): ?>
|
||||
<small><?php echo date('Y-m-d H:i:s', strtotime($response['sr_completed_at'])); ?></small>
|
||||
<?php else: ?>
|
||||
<span class="no-answer">미완료</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($response_time > 0): ?>
|
||||
<div class="info-item">
|
||||
<div class="info-label">응답 시간</div>
|
||||
<div class="info-value"><?php echo $response_time; ?>분</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="answers-container">
|
||||
<h2><i class="fa fa-list-alt"></i> 응답 내용</h2>
|
||||
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="answer-item">
|
||||
<div class="question-header">
|
||||
<span class="question-number"><?php echo $index + 1; ?></span>
|
||||
<h3 class="question-title"><?php echo htmlspecialchars($question['sq_title']); ?></h3>
|
||||
<span class="question-type"><?php echo $question['sq_type']; ?></span>
|
||||
</div>
|
||||
|
||||
<div class="answer-content">
|
||||
<?php if (isset($answers[$question['sq_id']]) && !empty($answers[$question['sq_id']])): ?>
|
||||
<?php if (count($answers[$question['sq_id']]) > 1): ?>
|
||||
<!-- 다중 답변 (체크박스) -->
|
||||
<div class="multiple-answers">
|
||||
<?php foreach ($answers[$question['sq_id']] as $answer): ?>
|
||||
<span class="answer-chip"><?php echo htmlspecialchars($answer['sa_value']); ?></span>
|
||||
<?php if ($answer['sa_text']): ?>
|
||||
<div class="answer-text"><?php echo htmlspecialchars($answer['sa_text']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- 단일 답변 -->
|
||||
<div class="answer-value">
|
||||
<?php echo nl2br(htmlspecialchars($answers[$question['sq_id']][0]['sa_value'])); ?>
|
||||
</div>
|
||||
<?php if ($answers[$question['sq_id']][0]['sa_text']): ?>
|
||||
<div class="answer-text">
|
||||
<?php echo nl2br(htmlspecialchars($answers[$question['sq_id']][0]['sa_text'])); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<div class="no-answer">답변 없음</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="response_list.php?sv_id=<?php echo $response['sv_id']; ?>" class="btn btn-secondary">
|
||||
<i class="fa fa-arrow-left"></i> 목록으로
|
||||
</a>
|
||||
<a href="statistics.php?sv_id=<?php echo $response['sv_id']; ?>" class="btn btn-info">
|
||||
<i class="fa fa-chart-bar"></i> 통계 보기
|
||||
</a>
|
||||
<a href="export.php?sv_id=<?php echo $response['sv_id']; ?>&format=excel" class="btn btn-primary">
|
||||
<i class="fa fa-file-excel"></i> 엑셀 다운로드
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
$sub_menu = '710304';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
|
||||
|
||||
if (!$sv_id) {
|
||||
alert('설문을 선택해주세요.', 'survey_list.php');
|
||||
}
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', 'survey_list.php');
|
||||
}
|
||||
|
||||
$questions = get_survey_questions($sv_id);
|
||||
$total_responses = get_survey_response_count($sv_id, 'completed');
|
||||
|
||||
// 통계 업데이트
|
||||
update_survey_statistics($sv_id);
|
||||
|
||||
$g5['title'] = '설문 통계 - ' . $survey['sv_title'];
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 응답 현황 통계
|
||||
$response_stats = sql_fetch("
|
||||
SELECT
|
||||
COUNT(CASE WHEN sr_status = 'completed' THEN 1 END) as completed,
|
||||
COUNT(CASE WHEN sr_status = 'started' THEN 1 END) as started,
|
||||
COUNT(CASE WHEN sr_status = 'abandoned' THEN 1 END) as abandoned,
|
||||
COUNT(*) as total
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
");
|
||||
|
||||
// 일별 응답 통계 (최근 30일)
|
||||
$daily_stats = [];
|
||||
$daily_sql = "
|
||||
SELECT
|
||||
DATE(sr_completed_at) as date,
|
||||
COUNT(*) as count
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
AND sr_completed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(sr_completed_at)
|
||||
ORDER BY date ASC
|
||||
";
|
||||
$daily_result = sql_query($daily_sql);
|
||||
while ($row = sql_fetch_array($daily_result)) {
|
||||
$daily_stats[$row['date']] = $row['count'];
|
||||
}
|
||||
|
||||
// 시간대별 응답 통계
|
||||
$hourly_stats = [];
|
||||
$hourly_sql = "
|
||||
SELECT
|
||||
HOUR(sr_completed_at) as hour,
|
||||
COUNT(*) as count
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
GROUP BY HOUR(sr_completed_at)
|
||||
ORDER BY hour ASC
|
||||
";
|
||||
$hourly_result = sql_query($hourly_sql);
|
||||
while ($row = sql_fetch_array($hourly_result)) {
|
||||
$hourly_stats[$row['hour']] = $row['count'];
|
||||
}
|
||||
|
||||
// 완료율 계산
|
||||
$completion_rate = $response_stats['total'] > 0 ?
|
||||
round(($response_stats['completed'] / $response_stats['total']) * 100, 1) : 0;
|
||||
|
||||
// 평균 응답 시간 계산
|
||||
$avg_time_sql = "
|
||||
SELECT AVG(TIMESTAMPDIFF(MINUTE, sr_started_at, sr_completed_at)) as avg_minutes
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
AND sr_completed_at IS NOT NULL
|
||||
";
|
||||
$avg_time_result = sql_fetch($avg_time_sql);
|
||||
$avg_response_time = round($avg_time_result['avg_minutes'] ?? 0, 1);
|
||||
?>
|
||||
|
||||
<!-- CSS는 statistics.css 파일에서 자동 로드됩니다 -->
|
||||
|
||||
<div class="statistics-container">
|
||||
<!-- 통계 헤더 -->
|
||||
<div class="stats-header">
|
||||
<h1><i class="fa fa-chart-bar"></i> 설문 통계</h1>
|
||||
<p><?php echo htmlspecialchars($survey['sv_title']); ?></p>
|
||||
</div>
|
||||
|
||||
<!-- 통계 개요 -->
|
||||
<div class="stats-overview">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_responses); ?></div>
|
||||
<div class="stat-label">완료된 응답</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo $completion_rate; ?>%</div>
|
||||
<div class="stat-label">완료율</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo $avg_response_time; ?>분</div>
|
||||
<div class="stat-label">평균 응답시간</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($response_stats['started']); ?></div>
|
||||
<div class="stat-label">진행중인 응답</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 응답 현황 차트 -->
|
||||
<div class="chart-section">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">응답 현황</h3>
|
||||
<div class="chart-controls">
|
||||
<button class="chart-btn active" data-chart="daily">일별</button>
|
||||
<button class="chart-btn" data-chart="hourly">시간대별</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="responseChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 질문별 통계 -->
|
||||
<div class="questions-stats">
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-stat">
|
||||
<div class="question-header">
|
||||
<span class="question-number"><?php echo $index + 1; ?></span>
|
||||
<h4 class="question-title"><?php echo htmlspecialchars($question['sq_title']); ?></h4>
|
||||
<span class="question-type"><?php echo $question['sq_type']; ?></span>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 질문별 통계 데이터 가져오기
|
||||
$question_stats_sql = "
|
||||
SELECT ss_option_value, ss_count, ss_percentage
|
||||
FROM survey_statistics
|
||||
WHERE sv_id = '$sv_id' AND sq_id = '{$question['sq_id']}'
|
||||
ORDER BY ss_count DESC
|
||||
";
|
||||
$question_stats_result = sql_query($question_stats_sql);
|
||||
$has_stats = sql_num_rows($question_stats_result) > 0;
|
||||
?>
|
||||
|
||||
<?php if ($has_stats): ?>
|
||||
<div class="question-stats-grid">
|
||||
<div class="answer-stats">
|
||||
<?php while ($stat = sql_fetch_array($question_stats_result)): ?>
|
||||
<div class="answer-item">
|
||||
<div class="answer-label"><?php echo htmlspecialchars($stat['ss_option_value']); ?></div>
|
||||
<div class="answer-bar">
|
||||
<div class="answer-fill" data-width="<?php echo $stat['ss_percentage']; ?>"></div>
|
||||
</div>
|
||||
<div class="answer-count"><?php echo number_format($stat['ss_count']); ?>명</div>
|
||||
<div class="answer-percentage"><?php echo $stat['ss_percentage']; ?>%</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
|
||||
<div class="question-chart">
|
||||
<canvas id="questionChart<?php echo $question['sq_id']; ?>"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php elseif (in_array($question['sq_type'], ['text', 'textarea'])): ?>
|
||||
<!-- 텍스트 응답 표시 -->
|
||||
<div class="text-responses">
|
||||
<?php
|
||||
$text_responses_sql = "
|
||||
SELECT sa.sa_value, sr.sr_completed_at, sr.sr_mb_id
|
||||
FROM survey_answers sa
|
||||
JOIN survey_responses sr ON sa.sr_id = sr.sr_id
|
||||
WHERE sa.sq_id = '{$question['sq_id']}'
|
||||
AND sr.sr_status = 'completed'
|
||||
AND sa.sa_value != ''
|
||||
ORDER BY sr.sr_completed_at DESC
|
||||
LIMIT 20
|
||||
";
|
||||
$text_responses_result = sql_query($text_responses_sql);
|
||||
|
||||
if (sql_num_rows($text_responses_result) > 0):
|
||||
while ($text_response = sql_fetch_array($text_responses_result)):
|
||||
?>
|
||||
<div class="text-response">
|
||||
<div class="text-response-meta">
|
||||
<?php echo $text_response['sr_mb_id'] ?: '익명'; ?> •
|
||||
<?php echo date('Y-m-d H:i', strtotime($text_response['sr_completed_at'])); ?>
|
||||
</div>
|
||||
<div class="text-response-content">
|
||||
<?php echo nl2br(htmlspecialchars($text_response['sa_value'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endwhile;
|
||||
else:
|
||||
?>
|
||||
<div class="text-response">
|
||||
<div class="text-response-content no-responses">
|
||||
아직 응답이 없습니다.
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="no-data">
|
||||
<i class="fa fa-chart-bar"></i>
|
||||
<p>아직 응답 데이터가 없습니다.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- 내보내기 섹션 -->
|
||||
<div class="export-section">
|
||||
<h3><i class="fa fa-download"></i> 데이터 내보내기</h3>
|
||||
<p>설문 결과를 다양한 형식으로 내보낼 수 있습니다.</p>
|
||||
|
||||
<div class="export-buttons">
|
||||
<a href="export.php?sv_id=<?php echo $sv_id; ?>&format=excel" class="export-btn">
|
||||
<i class="fa fa-file-excel"></i> 엑셀 다운로드
|
||||
</a>
|
||||
<a href="export.php?sv_id=<?php echo $sv_id; ?>&format=csv" class="export-btn secondary">
|
||||
<i class="fa fa-file-csv"></i> CSV 다운로드
|
||||
</a>
|
||||
<button onclick="printStatistics()" class="export-btn secondary">
|
||||
<i class="fa fa-print"></i> 인쇄하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart.js 라이브러리 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<!-- JavaScript는 statistics.js 파일에서 자동 로드됩니다 -->
|
||||
<script>
|
||||
// 페이지별 초기 데이터 설정
|
||||
// 💡 [수정] JavaScript에서 사용하는 데이터 구조(survey, stats, chartData)에 맞게 객체를 재구성합니다.
|
||||
// 이전에 발생했던 'daily' 또는 'title' 속성을 찾을 수 없다는 오류를 해결합니다.
|
||||
window.statisticsData = {
|
||||
survey: {
|
||||
id: <?php echo $sv_id; ?>,
|
||||
title: '<?php echo addslashes(htmlspecialchars($survey['sv_title'])); ?>'
|
||||
},
|
||||
stats: {
|
||||
totalResponses: '<?php echo number_format($total_responses); ?>',
|
||||
completionRate: '<?php echo $completion_rate; ?>',
|
||||
avgResponseTime: '<?php echo $avg_response_time; ?>',
|
||||
startedResponses: '<?php echo number_format($response_stats['started']); ?>'
|
||||
},
|
||||
chartData: {
|
||||
daily: <?php echo json_encode($daily_stats); ?>,
|
||||
hourly: <?php echo json_encode($hourly_stats); ?>
|
||||
},
|
||||
questions: <?php echo json_encode($questions); ?>
|
||||
};
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
$sub_menu = '710500';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$g5['title'] = '통계 분석';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 통계 분석 가능한 설문 목록 (완료된 응답이 있는 설문들)
|
||||
$sql = "SELECT sm.*,
|
||||
COUNT(sr.sr_id) as total_responses,
|
||||
COUNT(CASE WHEN sr.sr_status = 'completed' THEN 1 END) as completed_responses
|
||||
FROM survey_master sm
|
||||
LEFT JOIN survey_responses sr ON sm.sv_id = sr.sv_id
|
||||
WHERE sm.sv_status != 'deleted'
|
||||
GROUP BY sm.sv_id
|
||||
HAVING completed_responses > 0
|
||||
ORDER BY sm.sv_created_at DESC";
|
||||
|
||||
$result = sql_query($sql);
|
||||
?>
|
||||
|
||||
<style>
|
||||
.statistics-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.survey-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.survey-stats-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.survey-stats-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.survey-card-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.survey-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.survey-meta {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.survey-stats-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.8em;
|
||||
font-weight: 700;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.survey-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-stats {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-stats:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.btn-export:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 4em;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.completion-rate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
border-radius: 3px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
color: #AA20FF;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.survey-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.survey-stats-info {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.survey-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.survey-meta {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="statistics-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-chart-bar"></i> 통계 분석</h1>
|
||||
<p>설문별 응답 현황과 통계를 확인할 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (sql_num_rows($result) > 0): ?>
|
||||
<div class="survey-stats-grid">
|
||||
<?php while ($survey = sql_fetch_array($result)): ?>
|
||||
<?php
|
||||
$completion_rate = $survey['total_responses'] > 0 ?
|
||||
round(($survey['completed_responses'] / $survey['total_responses']) * 100, 1) : 0;
|
||||
|
||||
// 평균 응답 시간 계산
|
||||
$avg_time_sql = "
|
||||
SELECT AVG(TIMESTAMPDIFF(MINUTE, sr_started_at, sr_completed_at)) as avg_minutes
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '{$survey['sv_id']}'
|
||||
AND sr_status = 'completed'
|
||||
AND sr_completed_at IS NOT NULL
|
||||
";
|
||||
$avg_time_result = sql_fetch($avg_time_sql);
|
||||
$avg_response_time = round($avg_time_result['avg_minutes'] ?? 0, 1);
|
||||
?>
|
||||
|
||||
<div class="survey-stats-card">
|
||||
<div class="survey-card-header">
|
||||
<h3 class="survey-title"><?php echo htmlspecialchars($survey['sv_title']); ?></h3>
|
||||
<div class="survey-meta">
|
||||
<span><i class="fa fa-user"></i> <?php echo $survey['sv_created_by']; ?></span>
|
||||
<span><i class="fa fa-calendar"></i> <?php echo date('Y-m-d', strtotime($survey['sv_created_at'])); ?></span>
|
||||
<span class="status-badge status-<?php echo $survey['sv_status']; ?>">
|
||||
<?php
|
||||
$status_text = [
|
||||
'draft' => '작성중',
|
||||
'active' => '진행중',
|
||||
'closed' => '종료',
|
||||
'deleted' => '삭제됨'
|
||||
];
|
||||
echo $status_text[$survey['sv_status']];
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="survey-stats-info">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number"><?php echo number_format($survey['completed_responses']); ?></div>
|
||||
<div class="stat-label">완료된 응답</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number"><?php echo $avg_response_time; ?>분</div>
|
||||
<div class="stat-label">평균 응답시간</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="completion-rate">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: <?php echo $completion_rate; ?>%"></div>
|
||||
</div>
|
||||
<div class="progress-text"><?php echo $completion_rate; ?>%</div>
|
||||
</div>
|
||||
|
||||
<div class="survey-actions">
|
||||
<a href="statistics.php?sv_id=<?php echo $survey['sv_id']; ?>" class="btn-stats">
|
||||
<i class="fa fa-chart-bar"></i> 통계 보기
|
||||
</a>
|
||||
<a href="export.php?sv_id=<?php echo $survey['sv_id']; ?>" class="btn-stats btn-export">
|
||||
<i class="fa fa-file-excel"></i> 내보내기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa fa-chart-bar"></i>
|
||||
<h3>통계 분석 가능한 설문이 없습니다</h3>
|
||||
<p>완료된 응답이 있는 설문만 통계 분석이 가능합니다.</p>
|
||||
<a href="survey_list.php" style="color: #AA20FF; margin-top: 15px; display: inline-block;">
|
||||
설문 목록으로 이동
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -0,0 +1,998 @@
|
||||
<?php
|
||||
$sub_menu = '710304';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int) $_GET['sv_id'] : 0;
|
||||
|
||||
if (!$sv_id) {
|
||||
alert('설문을 선택해주세요.', 'survey_list.php');
|
||||
}
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', 'survey_list.php');
|
||||
}
|
||||
|
||||
$questions = get_survey_questions($sv_id);
|
||||
$total_responses = get_survey_response_count($sv_id, 'completed');
|
||||
|
||||
// 통계 업데이트
|
||||
update_survey_statistics($sv_id);
|
||||
|
||||
$g5['title'] = '설문 통계 - ' . $survey['sv_title'];
|
||||
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
||||
|
||||
// 응답 현황 통계
|
||||
$response_stats = sql_fetch("
|
||||
SELECT
|
||||
COUNT(CASE WHEN sr_status = 'completed' THEN 1 END) as completed,
|
||||
COUNT(CASE WHEN sr_status = 'started' THEN 1 END) as started,
|
||||
COUNT(CASE WHEN sr_status = 'abandoned' THEN 1 END) as abandoned,
|
||||
COUNT(*) as total
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
");
|
||||
|
||||
// 일별 응답 통계 (최근 30일)
|
||||
$daily_stats = array();
|
||||
$daily_sql = "
|
||||
SELECT
|
||||
DATE(sr_completed_at) as date,
|
||||
COUNT(*) as count
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
AND sr_completed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(sr_completed_at)
|
||||
ORDER BY date ASC
|
||||
";
|
||||
$daily_result = sql_query($daily_sql);
|
||||
while ($row = sql_fetch_array($daily_result)) {
|
||||
$daily_stats[$row['date']] = $row['count'];
|
||||
}
|
||||
|
||||
// 시간대별 응답 통계
|
||||
$hourly_stats = array();
|
||||
$hourly_sql = "
|
||||
SELECT
|
||||
HOUR(sr_completed_at) as hour,
|
||||
COUNT(*) as count
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
GROUP BY HOUR(sr_completed_at)
|
||||
ORDER BY hour ASC
|
||||
";
|
||||
$hourly_result = sql_query($hourly_sql);
|
||||
while ($row = sql_fetch_array($hourly_result)) {
|
||||
$hourly_stats[$row['hour']] = $row['count'];
|
||||
}
|
||||
|
||||
// 완료율 계산
|
||||
$completion_rate = $response_stats['total'] > 0 ?
|
||||
round(($response_stats['completed'] / $response_stats['total']) * 100, 1) : 0;
|
||||
|
||||
// 평균 응답 시간 계산
|
||||
$avg_time_sql = "
|
||||
SELECT AVG(TIMESTAMPDIFF(MINUTE, sr_started_at, sr_completed_at)) as avg_minutes
|
||||
FROM survey_responses
|
||||
WHERE sv_id = '$sv_id'
|
||||
AND sr_status = 'completed'
|
||||
AND sr_completed_at IS NOT NULL
|
||||
";
|
||||
$avg_time_result = sql_fetch($avg_time_sql);
|
||||
$avg_response_time = round($avg_time_result['avg_minutes'] ?? 0, 1);
|
||||
?>
|
||||
|
||||
<style>
|
||||
.statistics-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stats-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
.stats-header p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5em;
|
||||
font-weight: 700;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stat-change.positive {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.stat-change.negative {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chart-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-btn.active,
|
||||
.chart-btn:hover {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.questions-stats {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.question-stat {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.question-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #AA20FF;
|
||||
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.2em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
border-radius: 12px;
|
||||
font-size: 0.7em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.question-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.answer-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.answer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.answer-item:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.answer-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.answer-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.answer-label {
|
||||
min-width: 150px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.answer-count {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.answer-percentage {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
font-size: 0.9em;
|
||||
color: #AA20FF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.question-chart {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.text-responses {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.text-response {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.text-response:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.text-response-meta {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.text-response-content {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.export-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(170, 32, 255, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.export-btn.secondary {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.export-btn.secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.statistics-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.question-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.export-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.answer-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.answer-label,
|
||||
.answer-count,
|
||||
.answer-percentage {
|
||||
min-width: auto;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* 애니메이션 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card,
|
||||
.chart-section,
|
||||
.question-stat {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.stat-card:nth-child(even) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.question-stat:nth-child(even) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="statistics-container">
|
||||
<!-- 통계 헤더 -->
|
||||
<div class="stats-header">
|
||||
<h1><i class="fa fa-chart-bar"></i> 설문 통계</h1>
|
||||
<p><?php echo htmlspecialchars($survey['sv_title']); ?></p>
|
||||
</div>
|
||||
|
||||
<!-- 통계 개요 -->
|
||||
<div class="stats-overview">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_responses); ?></div>
|
||||
<div class="stat-label">완료된 응답</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo $completion_rate; ?>%</div>
|
||||
<div class="stat-label">완료율</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo $avg_response_time; ?>분</div>
|
||||
<div class="stat-label">평균 응답시간</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($response_stats['started']); ?></div>
|
||||
<div class="stat-label">진행중인 응답</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 응답 현황 차트 -->
|
||||
<div class="chart-section">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">응답 현황</h3>
|
||||
<div class="chart-controls">
|
||||
<button class="chart-btn active" data-chart="daily">일별</button>
|
||||
<button class="chart-btn" data-chart="hourly">시간대별</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="responseChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 질문별 통계 -->
|
||||
<div class="questions-stats">
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-stat">
|
||||
<div class="question-header">
|
||||
<span class="question-number"><?php echo $index + 1; ?></span>
|
||||
<h4 class="question-title"><?php echo htmlspecialchars($question['sq_title']); ?></h4>
|
||||
<span class="question-type"><?php echo $question['sq_type']; ?></span>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 질문별 통계 데이터 가져오기
|
||||
$question_stats_sql = "
|
||||
SELECT ss_option_value, ss_count, ss_percentage
|
||||
FROM survey_statistics
|
||||
WHERE sv_id = '$sv_id' AND sq_id = '{$question['sq_id']}'
|
||||
ORDER BY ss_count DESC
|
||||
";
|
||||
$question_stats_result = sql_query($question_stats_sql);
|
||||
$has_stats = sql_num_rows($question_stats_result) > 0;
|
||||
?>
|
||||
|
||||
<?php if ($has_stats): ?>
|
||||
<div class="question-stats-grid">
|
||||
<div class="answer-stats">
|
||||
<?php while ($stat = sql_fetch_array($question_stats_result)): ?>
|
||||
<div class="answer-item">
|
||||
<div class="answer-label"><?php echo htmlspecialchars($stat['ss_option_value']); ?></div>
|
||||
<div class="answer-bar">
|
||||
<div class="answer-fill" style="width: <?php echo $stat['ss_percentage']; ?>%"></div>
|
||||
</div>
|
||||
<div class="answer-count"><?php echo number_format($stat['ss_count']); ?>명</div>
|
||||
<div class="answer-percentage"><?php echo $stat['ss_percentage']; ?>%</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
|
||||
<div class="question-chart">
|
||||
<canvas id="questionChart<?php echo $question['sq_id']; ?>"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php elseif (in_array($question['sq_type'], ['text', 'textarea'])): ?>
|
||||
<!-- 텍스트 응답 표시 -->
|
||||
<div class="text-responses">
|
||||
<?php
|
||||
$text_responses_sql = "
|
||||
SELECT sa.sa_value, sr.sr_completed_at, sr.sr_mb_id
|
||||
FROM survey_answers sa
|
||||
JOIN survey_responses sr ON sa.sr_id = sr.sr_id
|
||||
WHERE sa.sq_id = '{$question['sq_id']}'
|
||||
AND sr.sr_status = 'completed'
|
||||
AND sa.sa_value != ''
|
||||
ORDER BY sr.sr_completed_at DESC
|
||||
LIMIT 20
|
||||
";
|
||||
$text_responses_result = sql_query($text_responses_sql);
|
||||
|
||||
if (sql_num_rows($text_responses_result) > 0):
|
||||
while ($text_response = sql_fetch_array($text_responses_result)):
|
||||
?>
|
||||
<div class="text-response">
|
||||
<div class="text-response-meta">
|
||||
<?php echo $text_response['sr_mb_id'] ?: '익명'; ?> •
|
||||
<?php echo date('Y-m-d H:i', strtotime($text_response['sr_completed_at'])); ?>
|
||||
</div>
|
||||
<div class="text-response-content">
|
||||
<?php echo nl2br(htmlspecialchars($text_response['sa_value'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endwhile;
|
||||
else:
|
||||
?>
|
||||
<div class="text-response">
|
||||
<div class="text-response-content" style="text-align: center; color: #666;">
|
||||
아직 응답이 없습니다.
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div style="text-align: center; padding: 40px; color: #666;">
|
||||
<i class="fa fa-chart-bar"
|
||||
style="font-size: 3em; margin-bottom: 15px; display: block; opacity: 0.3;"></i>
|
||||
<p>아직 응답 데이터가 없습니다.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- 내보내기 섹션 -->
|
||||
<div class="export-section">
|
||||
<h3><i class="fa fa-download"></i> 데이터 내보내기</h3>
|
||||
<p>설문 결과를 다양한 형식으로 내보낼 수 있습니다.</p>
|
||||
|
||||
<div class="export-buttons">
|
||||
<a href="export.php?sv_id=<?php echo $sv_id; ?>&format=excel" class="export-btn">
|
||||
<i class="fa fa-file-excel"></i> 엑셀 다운로드
|
||||
</a>
|
||||
<a href="export.php?sv_id=<?php echo $sv_id; ?>&format=csv" class="export-btn secondary">
|
||||
<i class="fa fa-file-csv"></i> CSV 다운로드
|
||||
</a>
|
||||
<button onclick="printStatistics()" class="export-btn secondary">
|
||||
<i class="fa fa-print"></i> 인쇄하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart.js 라이브러리 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
// 통계 데이터를 JavaScript로 전달
|
||||
window.statisticsData = {
|
||||
survey: {
|
||||
title: '<?php echo addslashes(htmlspecialchars($survey['sv_title'])); ?>',
|
||||
id: <?php echo $sv_id; ?>
|
||||
},
|
||||
stats: {
|
||||
totalResponses: '<?php echo number_format($total_responses); ?>',
|
||||
completionRate: '<?php echo $completion_rate; ?>',
|
||||
avgResponseTime: '<?php echo $avg_response_time; ?>',
|
||||
startedResponses: '<?php echo number_format($response_stats['started']); ?>'
|
||||
},
|
||||
chartData: {
|
||||
daily: <?php echo json_encode($daily_stats); ?>,
|
||||
hourly: <?php echo json_encode($hourly_stats); ?>
|
||||
},
|
||||
questions: <?php echo json_encode($questions); ?>
|
||||
};
|
||||
|
||||
// 응답 현황 차트
|
||||
let responseChart;
|
||||
|
||||
function initResponseChart() {
|
||||
const ctx = document.getElementById('responseChart').getContext('2d');
|
||||
|
||||
// 일별 데이터 준비 (최근 30일)
|
||||
const dailyLabels = [];
|
||||
const dailyValues = [];
|
||||
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
const label = date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
||||
|
||||
dailyLabels.push(label);
|
||||
dailyValues.push(dailyData[dateStr] || 0);
|
||||
}
|
||||
|
||||
responseChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dailyLabels,
|
||||
datasets: [{
|
||||
label: '일별 응답 수',
|
||||
data: dailyValues,
|
||||
borderColor: '#AA20FF',
|
||||
backgroundColor: 'rgba(170, 32, 255, 0.1)',
|
||||
borderWidth: 3,
|
||||
fill: true,
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateResponseChart(type) {
|
||||
if (!responseChart) return;
|
||||
|
||||
if (type === 'hourly') {
|
||||
// 시간대별 데이터 준비
|
||||
const hourlyLabels = [];
|
||||
const hourlyValues = [];
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
hourlyLabels.push(i + '시');
|
||||
hourlyValues.push(hourlyData[i] || 0);
|
||||
}
|
||||
|
||||
responseChart.data.labels = hourlyLabels;
|
||||
responseChart.data.datasets[0].data = hourlyValues;
|
||||
responseChart.data.datasets[0].label = '시간대별 응답 수';
|
||||
responseChart.options.scales.x.title = { display: true, text: '시간' };
|
||||
} else {
|
||||
// 일별 데이터로 복원
|
||||
const dailyLabels = [];
|
||||
const dailyValues = [];
|
||||
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
const label = date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
||||
|
||||
dailyLabels.push(label);
|
||||
dailyValues.push(dailyData[dateStr] || 0);
|
||||
}
|
||||
|
||||
responseChart.data.labels = dailyLabels;
|
||||
responseChart.data.datasets[0].data = dailyValues;
|
||||
responseChart.data.datasets[0].label = '일별 응답 수';
|
||||
responseChart.options.scales.x.title = { display: true, text: '날짜' };
|
||||
}
|
||||
|
||||
responseChart.update();
|
||||
}
|
||||
|
||||
// 질문별 차트 초기화
|
||||
function initQuestionCharts() {
|
||||
<?php foreach ($questions as $question): ?>
|
||||
<?php if (in_array($question['sq_type'], ['radio', 'checkbox', 'select', 'rating'])): ?>
|
||||
{
|
||||
const ctx<?php echo $question['sq_id']; ?> = document.getElementById('questionChart<?php echo $question['sq_id']; ?>');
|
||||
if (ctx<?php echo $question['sq_id']; ?>) {
|
||||
<?php
|
||||
// 질문별 차트 데이터 준비
|
||||
$chart_data = array();
|
||||
$chart_labels = array();
|
||||
$chart_colors = array();
|
||||
|
||||
$chart_sql = "
|
||||
SELECT ss_option_value, ss_count
|
||||
FROM survey_statistics
|
||||
WHERE sv_id = '$sv_id' AND sq_id = '{$question['sq_id']}'
|
||||
ORDER BY ss_count DESC
|
||||
";
|
||||
$chart_result = sql_query($chart_sql);
|
||||
|
||||
$color_palette = ['#AA20FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8'];
|
||||
$color_index = 0;
|
||||
|
||||
while ($chart_row = sql_fetch_array($chart_result)) {
|
||||
$chart_labels[] = $chart_row['ss_option_value'];
|
||||
$chart_data[] = $chart_row['ss_count'];
|
||||
$chart_colors[] = $color_palette[$color_index % count($color_palette)];
|
||||
$color_index++;
|
||||
}
|
||||
?>
|
||||
|
||||
new Chart(ctx<?php echo $question['sq_id']; ?>, {
|
||||
type: '<?php echo $question['sq_type'] === 'rating' ? 'bar' : 'doughnut'; ?>',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_labels); ?>,
|
||||
datasets: [{
|
||||
data: <?php echo json_encode($chart_data); ?>,
|
||||
backgroundColor: <?php echo json_encode($chart_colors); ?>,
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
padding: 15,
|
||||
usePointStyle: true
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php if ($question['sq_type'] === 'rating'): ?>
|
||||
, scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php endif; ?>
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
}
|
||||
|
||||
// 차트 타입 변경 이벤트
|
||||
document.querySelectorAll('.chart-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('.chart-btn').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
const chartType = this.dataset.chart;
|
||||
updateResponseChart(chartType);
|
||||
});
|
||||
});
|
||||
|
||||
// 인쇄 기능
|
||||
function printStatistics() {
|
||||
const printWindow = window.open('', '_blank');
|
||||
|
||||
// PHP 데이터를 JavaScript 변수로 전달
|
||||
const surveyTitle = '<?php echo addslashes(htmlspecialchars($survey['sv_title'])); ?>';
|
||||
const totalResponses = '<?php echo number_format($total_responses); ?>';
|
||||
const completionRate = '<?php echo $completion_rate; ?>';
|
||||
const avgResponseTime = '<?php echo $avg_response_time; ?>';
|
||||
const startedResponses = '<?php echo number_format($response_stats['started']); ?>';
|
||||
|
||||
const printContent = `
|
||||
<html>
|
||||
<head>
|
||||
<title>설문 통계 - ${surveyTitle}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
|
||||
.stat-item { text-align: center; padding: 15px; border: 1px solid #ddd; }
|
||||
.stat-number { font-size: 2em; font-weight: bold; color: #AA20FF; }
|
||||
.question { margin-bottom: 30px; page-break-inside: avoid; }
|
||||
.question-title { font-size: 1.2em; font-weight: bold; margin-bottom: 15px; }
|
||||
.answer-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; }
|
||||
.question-stat { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; }
|
||||
.question-header { margin-bottom: 15px; }
|
||||
.answer-stats { margin-top: 15px; }
|
||||
@media print { .no-print { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>설문 통계</h1>
|
||||
<h2>${surveyTitle}</h2>
|
||||
<p>생성일: ${new Date().toLocaleDateString('ko-KR')}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${totalResponses}</div>
|
||||
<div>완료된 응답</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${completionRate}%</div>
|
||||
<div>완료율</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${avgResponseTime}분</div>
|
||||
<div>평균 응답시간</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">${startedResponses}</div>
|
||||
<div>진행중인 응답</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="questions-section">
|
||||
${getQuestionsForPrint()}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
printWindow.document.write(printContent);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
}
|
||||
|
||||
// 질문 통계를 인쇄용으로 변환하는 함수
|
||||
function getQuestionsForPrint() {
|
||||
const questionsStats = document.querySelector('.questions-stats');
|
||||
if (!questionsStats) return '';
|
||||
|
||||
let printHtml = '';
|
||||
const questionItems = questionsStats.querySelectorAll('.question-stat');
|
||||
|
||||
questionItems.forEach((question, index) => {
|
||||
const questionTitle = question.querySelector('.question-title');
|
||||
const questionType = question.querySelector('.question-type');
|
||||
const answerItems = question.querySelectorAll('.answer-item');
|
||||
const textResponses = question.querySelectorAll('.text-response');
|
||||
|
||||
if (questionTitle) {
|
||||
printHtml += `
|
||||
<div class="question-stat">
|
||||
<div class="question-header">
|
||||
<h3>${index + 1}. ${questionTitle.textContent}</h3>
|
||||
${questionType ? `<span style="color: #666; font-size: 0.9em;">[${questionType.textContent}]</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (answerItems.length > 0) {
|
||||
printHtml += '<div class="answer-stats">';
|
||||
answerItems.forEach(item => {
|
||||
const label = item.querySelector('.answer-label');
|
||||
const count = item.querySelector('.answer-count');
|
||||
const percentage = item.querySelector('.answer-percentage');
|
||||
|
||||
if (label && count && percentage) {
|
||||
printHtml += `
|
||||
<div class="answer-item">
|
||||
<span>${label.textContent}</span>
|
||||
<span>${count.textContent} (${percentage.textContent})</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
printHtml += '</div>';
|
||||
}
|
||||
|
||||
if (textResponses.length > 0) {
|
||||
printHtml += '<div class="text-responses" style="margin-top: 15px;">';
|
||||
printHtml += '<h4>주요 응답:</h4>';
|
||||
textResponses.forEach((response, idx) => {
|
||||
if (idx < 5) { // 최대 5개만 인쇄
|
||||
const content = response.querySelector('.text-response-content');
|
||||
if (content) {
|
||||
printHtml += `<p style="margin: 10px 0; padding: 10px; background: #f9f9f9; border-left: 3px solid #AA20FF;">${content.textContent}</p>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
printHtml += '</div>';
|
||||
}
|
||||
|
||||
printHtml += '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
return printHtml;
|
||||
}
|
||||
|
||||
// 페이지 로드 시 차트 초기화
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initResponseChart();
|
||||
initQuestionCharts();
|
||||
|
||||
// 애니메이션 효과
|
||||
const animateNumbers = () => {
|
||||
document.querySelectorAll('.stat-number').forEach(element => {
|
||||
const target = parseInt(element.textContent.replace(/,/g, ''));
|
||||
let current = 0;
|
||||
const increment = target / 50;
|
||||
const timer = setInterval(() => {
|
||||
current += increment;
|
||||
if (current >= target) {
|
||||
current = target;
|
||||
clearInterval(timer);
|
||||
}
|
||||
element.textContent = Math.floor(current).toLocaleString('ko-KR');
|
||||
}, 20);
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
$sub_menu = '710100';
|
||||
include_once('./_common.php');
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
|
||||
|
||||
if (!$sv_id) {
|
||||
alert('설문 ID가 없습니다.', './survey_list.php');
|
||||
}
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', './survey_list.php');
|
||||
}
|
||||
|
||||
if ($survey['sv_status'] != 'draft') {
|
||||
alert('임시저장 상태의 설문만 활성화할 수 있습니다.', './survey_list.php');
|
||||
}
|
||||
|
||||
// 질문이 있는지 확인
|
||||
$questions = get_survey_questions($sv_id);
|
||||
if (empty($questions)) {
|
||||
alert('질문이 없는 설문은 활성화할 수 없습니다.', './survey_form.php?sv_id='.$sv_id);
|
||||
}
|
||||
|
||||
// 설문 활성화
|
||||
if (update_survey_status($sv_id, 'active')) {
|
||||
alert('설문이 활성화되었습니다.', './survey_list.php');
|
||||
} else {
|
||||
alert('설문 활성화에 실패했습니다.', './survey_list.php');
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
$sub_menu = '710100';
|
||||
include_once('./_common.php');
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int) $_GET['sv_id'] : 0;
|
||||
|
||||
if (!$sv_id) {
|
||||
alert('설문 ID가 없습니다.', './survey_list.php');
|
||||
}
|
||||
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', './survey_list.php');
|
||||
}
|
||||
|
||||
// 응답이 있는 설문은 삭제 대신 상태를 'deleted'로 변경
|
||||
$response_count = get_survey_response_count($sv_id, '');
|
||||
if ($response_count > 0) {
|
||||
// 응답이 있는 경우 소프트 삭제
|
||||
if (update_survey_status($sv_id, 'deleted')) {
|
||||
alert('설문이 삭제 처리되었습니다.', './survey_list.php');
|
||||
} else {
|
||||
alert('설문 삭제에 실패했습니다.', './survey_list.php');
|
||||
}
|
||||
} else {
|
||||
// 응답이 없는 경우 완전 삭제
|
||||
$sql = "DELETE FROM survey_master WHERE sv_id = '$sv_id'";
|
||||
if (sql_query($sql)) {
|
||||
alert('설문이 완전히 삭제되었습니다.', './survey_list.php');
|
||||
} else {
|
||||
alert('설문 삭제에 실패했습니다.', './survey_list.php');
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
$sub_menu = '710200';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
|
||||
$template_id = isset($_GET['template_id']) ? (int)$_GET['template_id'] : 0;
|
||||
$is_edit = $sv_id > 0;
|
||||
|
||||
$survey = [
|
||||
'sv_title' => '',
|
||||
'sv_description' => '',
|
||||
'sv_start_date' => date('Y-m-d H:i'),
|
||||
'sv_end_date' => date('Y-m-d H:i', strtotime('+30 days')),
|
||||
'sv_allow_anonymous' => 1,
|
||||
'sv_allow_multiple' => 0,
|
||||
'sv_max_responses' => '',
|
||||
'sv_theme_color' => '#AA20FF',
|
||||
'sv_thank_message' => ''
|
||||
];
|
||||
|
||||
$questions = [];
|
||||
|
||||
if ($is_edit) {
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', 'survey_list.php');
|
||||
}
|
||||
$questions = get_survey_questions($sv_id);
|
||||
$g5['title'] = '설문 수정';
|
||||
} else {
|
||||
$g5['title'] = '설문 작성';
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 템플릿 목록
|
||||
$templates = get_survey_templates();
|
||||
?>
|
||||
|
||||
<!-- CSS는 survey_form.css 파일에서 자동 로드됩니다 -->
|
||||
|
||||
<div class="survey-form-container">
|
||||
<div class="form-header">
|
||||
<h1><?php echo $is_edit ? '설문 수정' : '새 설문 만들기'; ?></h1>
|
||||
<p><?php echo $is_edit ? '설문 내용을 수정할 수 있습니다' : '전문적인 설문조사를 쉽게 만들어보세요'; ?></p>
|
||||
</div>
|
||||
|
||||
<form id="surveyForm" method="post" action="survey_form_update.php">
|
||||
<input type="hidden" name="sv_id" value="<?php echo $sv_id; ?>">
|
||||
|
||||
<!-- 탭 메뉴 -->
|
||||
<div class="form-tabs">
|
||||
<button type="button" class="form-tab active" data-tab="basic">
|
||||
<i class="fa fa-info-circle"></i> 기본 정보
|
||||
</button>
|
||||
<button type="button" class="form-tab" data-tab="questions">
|
||||
<i class="fa fa-question-circle"></i> 질문 설정
|
||||
</button>
|
||||
<button type="button" class="form-tab" data-tab="settings">
|
||||
<i class="fa fa-cog"></i> 고급 설정
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 기본 정보 탭 -->
|
||||
<div class="tab-content active" id="basic">
|
||||
<?php if (!$is_edit): ?>
|
||||
<!-- 템플릿 선택 -->
|
||||
<div class="template-section">
|
||||
<h3><i class="fa fa-magic"></i> 템플릿으로 빠르게 시작하기</h3>
|
||||
<p class="help-text">미리 만들어진 템플릿을 선택하면 질문이 자동으로 추가됩니다.</p>
|
||||
|
||||
<div class="template-grid">
|
||||
<div class="template-card" data-template="0">
|
||||
<h4><i class="fa fa-plus"></i> 직접 작성</h4>
|
||||
<p>처음부터 직접 설문을 만들어보세요</p>
|
||||
</div>
|
||||
<?php foreach ($templates as $template): ?>
|
||||
<div class="template-card" data-template="<?php echo $template['st_id']; ?>">
|
||||
<h4><?php echo htmlspecialchars($template['st_name']); ?></h4>
|
||||
<p><?php echo htmlspecialchars($template['st_description']); ?></p>
|
||||
<small class="template-category"><?php echo $template['st_category']; ?></small>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<input type="hidden" name="template_id" id="templateId" value="<?php echo $template_id; ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label required">설문 제목</label>
|
||||
<input type="text" name="sv_title" class="form-input"
|
||||
value="<?php echo htmlspecialchars($survey['sv_title']); ?>"
|
||||
placeholder="설문조사의 제목을 입력하세요" required>
|
||||
<div class="help-text">명확하고 흥미로운 제목을 작성하면 더 많은 참여를 유도할 수 있습니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">설문 설명</label>
|
||||
<textarea name="sv_description" class="form-textarea"
|
||||
placeholder="설문의 목적과 내용을 간단히 설명해주세요"><?php echo htmlspecialchars($survey['sv_description']); ?></textarea>
|
||||
<div class="help-text">설문의 목적과 예상 소요시간을 안내하면 참여율이 높아집니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<label class="form-label required">시작일시</label>
|
||||
<input type="datetime-local" name="sv_start_date" class="form-input"
|
||||
value="<?php echo date('Y-m-d\TH:i', strtotime($survey['sv_start_date'])); ?>" required>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<label class="form-label required">종료일시</label>
|
||||
<input type="datetime-local" name="sv_end_date" class="form-input"
|
||||
value="<?php echo date('Y-m-d\TH:i', strtotime($survey['sv_end_date'])); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">테마 색상</label>
|
||||
<input type="color" name="sv_theme_color" class="color-picker"
|
||||
value="<?php echo $survey['sv_theme_color']; ?>">
|
||||
<div class="help-text">설문 페이지의 메인 색상을 선택하세요.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">완료 메시지</label>
|
||||
<textarea name="sv_thank_message" class="form-textarea"
|
||||
placeholder="설문 완료 후 표시될 감사 메시지를 입력하세요"><?php echo htmlspecialchars($survey['sv_thank_message']); ?></textarea>
|
||||
<div class="help-text">참여자에게 감사 인사와 함께 추가 안내사항을 전달할 수 있습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 질문 설정 탭 -->
|
||||
<div class="tab-content" id="questions">
|
||||
<div class="questions-container">
|
||||
<div id="questionsList">
|
||||
<?php if (!empty($questions)): ?>
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-item" data-question-id="<?php echo $question['sq_id']; ?>">
|
||||
<!-- 기존 질문 렌더링 로직은 JavaScript에서 처리 -->
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-question-btn" onclick="addQuestion()">
|
||||
<i class="fa fa-plus"></i> 질문 추가하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 고급 설정 탭 -->
|
||||
<div class="tab-content" id="settings">
|
||||
<div class="form-group">
|
||||
<label class="form-label">참여 설정</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="sv_allow_anonymous" value="1"
|
||||
<?php echo $survey['sv_allow_anonymous'] ? 'checked' : ''; ?>>
|
||||
<label>익명 참여 허용</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="sv_allow_multiple" value="1"
|
||||
<?php echo $survey['sv_allow_multiple'] ? 'checked' : ''; ?>>
|
||||
<label>중복 참여 허용</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-text">익명 참여를 허용하면 로그인하지 않은 사용자도 참여할 수 있습니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">최대 응답 수</label>
|
||||
<input type="number" name="sv_max_responses" class="form-input"
|
||||
value="<?php echo $survey['sv_max_responses']; ?>"
|
||||
placeholder="제한 없음" min="1">
|
||||
<div class="help-text">응답 수를 제한하려면 숫자를 입력하세요. 비워두면 제한이 없습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 폼 액션 -->
|
||||
<div class="form-actions">
|
||||
<button type="submit" name="action" value="save_draft" class="btn btn-secondary">
|
||||
<i class="fa fa-save"></i> 임시저장
|
||||
</button>
|
||||
<button type="submit" name="action" value="save_and_activate" class="btn btn-success">
|
||||
<i class="fa fa-play"></i> 저장 후 활성화
|
||||
</button>
|
||||
<a href="survey_list.php" class="btn btn-secondary">
|
||||
<i class="fa fa-times"></i> 취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript는 survey_form.js 파일에서 자동 로드됩니다 -->
|
||||
<script>
|
||||
// 페이지별 초기 데이터 설정
|
||||
window.surveyFormData = {
|
||||
questionCount: <?php echo count($questions); ?>,
|
||||
templateId: <?php echo $template_id; ?>,
|
||||
isEdit: <?php echo $is_edit ? 'true' : 'false'; ?>
|
||||
};
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,944 @@
|
||||
<?php
|
||||
$sub_menu = '710200';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$sv_id = isset($_GET['sv_id']) ? (int)$_GET['sv_id'] : 0;
|
||||
$template_id = isset($_GET['template_id']) ? (int)$_GET['template_id'] : 0;
|
||||
$is_edit = $sv_id > 0;
|
||||
|
||||
$survey = array(
|
||||
'sv_title' => '',
|
||||
'sv_description' => '',
|
||||
'sv_start_date' => date('Y-m-d H:i'),
|
||||
'sv_end_date' => date('Y-m-d H:i', strtotime('+30 days')),
|
||||
'sv_allow_anonymous' => 1,
|
||||
'sv_allow_multiple' => 0,
|
||||
'sv_max_responses' => '',
|
||||
'sv_theme_color' => '#AA20FF',
|
||||
'sv_thank_message' => ''
|
||||
);
|
||||
|
||||
$questions = array();
|
||||
|
||||
if ($is_edit) {
|
||||
$survey = get_survey($sv_id);
|
||||
if (!$survey) {
|
||||
alert('존재하지 않는 설문입니다.', 'survey_list.php');
|
||||
}
|
||||
$questions = get_survey_questions($sv_id);
|
||||
$g5['title'] = '설문 수정';
|
||||
} else {
|
||||
$g5['title'] = '설문 작성';
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 템플릿 목록
|
||||
$templates = get_survey_templates();
|
||||
?>
|
||||
|
||||
<style>
|
||||
.survey-form-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
|
||||
.form-tabs {
|
||||
display: flex;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-tab {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-tab.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-tab:not(.active):hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.form-label.required::after {
|
||||
content: ' *';
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: all 0.3s ease;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 셀렉트 박스 높이 수정 */
|
||||
select,
|
||||
.form-select,
|
||||
.question-type-select {
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
padding: 8px 12px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #AA20FF;
|
||||
background: white;
|
||||
box-shadow: 0 0 0 3px rgba(170, 32, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-col {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
accent-color: #AA20FF;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.template-section {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
border-color: #AA20FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.template-card.selected {
|
||||
border-color: #AA20FF;
|
||||
background: rgba(170, 32, 255, 0.05);
|
||||
}
|
||||
|
||||
.questions-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
background: #fff;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.question-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-sm:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.question-type-select {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.option-input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.add-option-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.add-question-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.add-question-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.survey-form-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="survey-form-container">
|
||||
<div class="form-header">
|
||||
<h1><?php echo $is_edit ? '설문 수정' : '새 설문 만들기'; ?></h1>
|
||||
<p><?php echo $is_edit ? '설문 내용을 수정할 수 있습니다' : '전문적인 설문조사를 쉽게 만들어보세요'; ?></p>
|
||||
</div>
|
||||
|
||||
<form id="surveyForm" method="post" action="survey_form_update.php">
|
||||
<input type="hidden" name="sv_id" value="<?php echo $sv_id; ?>">
|
||||
|
||||
<!-- 탭 메뉴 -->
|
||||
<div class="form-tabs">
|
||||
<button type="button" class="form-tab active" data-tab="basic">
|
||||
<i class="fa fa-info-circle"></i> 기본 정보
|
||||
</button>
|
||||
<button type="button" class="form-tab" data-tab="questions">
|
||||
<i class="fa fa-question-circle"></i> 질문 설정
|
||||
</button>
|
||||
<button type="button" class="form-tab" data-tab="settings">
|
||||
<i class="fa fa-cog"></i> 고급 설정
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 기본 정보 탭 -->
|
||||
<div class="tab-content active" id="basic">
|
||||
<?php if (!$is_edit): ?>
|
||||
<!-- 템플릿 선택 -->
|
||||
<div class="template-section">
|
||||
<h3><i class="fa fa-magic"></i> 템플릿으로 빠르게 시작하기</h3>
|
||||
<p class="help-text">미리 만들어진 템플릿을 선택하면 질문이 자동으로 추가됩니다.</p>
|
||||
|
||||
<div class="template-grid">
|
||||
<div class="template-card" data-template="0">
|
||||
<h4><i class="fa fa-plus"></i> 직접 작성</h4>
|
||||
<p>처음부터 직접 설문을 만들어보세요</p>
|
||||
</div>
|
||||
<?php foreach ($templates as $template): ?>
|
||||
<div class="template-card" data-template="<?php echo $template['st_id']; ?>">
|
||||
<h4><?php echo htmlspecialchars($template['st_name']); ?></h4>
|
||||
<p><?php echo htmlspecialchars($template['st_description']); ?></p>
|
||||
<small style="color: #666;"><?php echo $template['st_category']; ?></small>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<input type="hidden" name="template_id" id="templateId" value="<?php echo $template_id; ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label required">설문 제목</label>
|
||||
<input type="text" name="sv_title" class="form-input"
|
||||
value="<?php echo htmlspecialchars($survey['sv_title']); ?>"
|
||||
placeholder="설문조사의 제목을 입력하세요" required>
|
||||
<div class="help-text">명확하고 흥미로운 제목을 작성하면 더 많은 참여를 유도할 수 있습니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">설문 설명</label>
|
||||
<textarea name="sv_description" class="form-textarea"
|
||||
placeholder="설문의 목적과 내용을 간단히 설명해주세요"><?php echo htmlspecialchars($survey['sv_description']); ?></textarea>
|
||||
<div class="help-text">설문의 목적과 예상 소요시간을 안내하면 참여율이 높아집니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<label class="form-label required">시작일시</label>
|
||||
<input type="datetime-local" name="sv_start_date" class="form-input"
|
||||
value="<?php echo date('Y-m-d\TH:i', strtotime($survey['sv_start_date'])); ?>" required>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<label class="form-label required">종료일시</label>
|
||||
<input type="datetime-local" name="sv_end_date" class="form-input"
|
||||
value="<?php echo date('Y-m-d\TH:i', strtotime($survey['sv_end_date'])); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">테마 색상</label>
|
||||
<input type="color" name="sv_theme_color" class="color-picker"
|
||||
value="<?php echo $survey['sv_theme_color']; ?>">
|
||||
<div class="help-text">설문 페이지의 메인 색상을 선택하세요.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">완료 메시지</label>
|
||||
<textarea name="sv_thank_message" class="form-textarea"
|
||||
placeholder="설문 완료 후 표시될 감사 메시지를 입력하세요"><?php echo htmlspecialchars($survey['sv_thank_message']); ?></textarea>
|
||||
<div class="help-text">참여자에게 감사 인사와 함께 추가 안내사항을 전달할 수 있습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 질문 설정 탭 -->
|
||||
<div class="tab-content" id="questions">
|
||||
<div class="questions-container">
|
||||
<div id="questionsList">
|
||||
<?php if (!empty($questions)): ?>
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-item" data-question-id="<?php echo $question['sq_id']; ?>">
|
||||
<!-- 질문 내용 렌더링 -->
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-question-btn" onclick="addQuestion()">
|
||||
<i class="fa fa-plus"></i> 질문 추가하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 고급 설정 탭 -->
|
||||
<div class="tab-content" id="settings">
|
||||
<div class="form-group">
|
||||
<label class="form-label">참여 설정</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="sv_allow_anonymous" value="1"
|
||||
<?php echo $survey['sv_allow_anonymous'] ? 'checked' : ''; ?>>
|
||||
<label>익명 참여 허용</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="sv_allow_multiple" value="1"
|
||||
<?php echo $survey['sv_allow_multiple'] ? 'checked' : ''; ?>>
|
||||
<label>중복 참여 허용</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-text">익명 참여를 허용하면 로그인하지 않은 사용자도 참여할 수 있습니다.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">최대 응답 수</label>
|
||||
<input type="number" name="sv_max_responses" class="form-input"
|
||||
value="<?php echo $survey['sv_max_responses']; ?>"
|
||||
placeholder="제한 없음" min="1">
|
||||
<div class="help-text">응답 수를 제한하려면 숫자를 입력하세요. 비워두면 제한이 없습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 폼 액션 -->
|
||||
<div class="form-actions">
|
||||
<button type="submit" name="action" value="save_draft" class="btn btn-secondary">
|
||||
<i class="fa fa-save"></i> 임시저장
|
||||
</button>
|
||||
<button type="submit" name="action" value="save_and_activate" class="btn btn-success">
|
||||
<i class="fa fa-play"></i> 저장 후 활성화
|
||||
</button>
|
||||
<a href="survey_list.php" class="btn btn-secondary">
|
||||
<i class="fa fa-times"></i> 취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let questionCount = <?php echo count($questions); ?>;
|
||||
const questionTypes = {
|
||||
'text': '단답형',
|
||||
'textarea': '장문형',
|
||||
'radio': '단일선택',
|
||||
'checkbox': '다중선택',
|
||||
'select': '드롭다운',
|
||||
'rating': '평점',
|
||||
'date': '날짜'
|
||||
};
|
||||
|
||||
// 탭 전환
|
||||
document.querySelectorAll('.form-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const targetTab = this.dataset.tab;
|
||||
|
||||
// 탭 활성화
|
||||
document.querySelectorAll('.form-tab').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 콘텐츠 표시
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(targetTab).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 템플릿 선택
|
||||
document.querySelectorAll('.template-card').forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
document.querySelectorAll('.template-card').forEach(c => c.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
const templateId = this.dataset.template;
|
||||
document.getElementById('templateId').value = templateId;
|
||||
|
||||
// 템플릿 질문 로드
|
||||
if (templateId > 0) {
|
||||
loadTemplateQuestions(templateId);
|
||||
} else {
|
||||
// 직접 작성 선택 시 기존 질문들 초기화
|
||||
document.getElementById('questionsList').innerHTML = '';
|
||||
questionCount = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 템플릿 질문 로드 함수
|
||||
function loadTemplateQuestions(templateId) {
|
||||
fetch(`ajax_get_template.php?st_id=${templateId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// 기존 질문들 초기화
|
||||
document.getElementById('questionsList').innerHTML = '';
|
||||
questionCount = 0;
|
||||
|
||||
// 템플릿 질문들 추가
|
||||
data.questions.forEach((question, index) => {
|
||||
addTemplateQuestion(question, index + 1);
|
||||
});
|
||||
|
||||
// 기본 정보도 템플릿에서 가져오기
|
||||
if (data.template.st_name) {
|
||||
document.querySelector('input[name="sv_title"]').value = data.template.st_name;
|
||||
}
|
||||
if (data.template.st_description) {
|
||||
document.querySelector('textarea[name="sv_description"]').value = data.template.st_description;
|
||||
}
|
||||
|
||||
alert('템플릿이 적용되었습니다. 질문 설정 탭에서 확인해보세요.');
|
||||
} else {
|
||||
alert('템플릿 로드 실패: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('템플릿 로드 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
// 템플릿 질문 추가 함수
|
||||
function addTemplateQuestion(questionData, questionNumber) {
|
||||
questionCount = questionNumber;
|
||||
|
||||
const optionsHtml = ['radio', 'checkbox', 'select'].includes(questionData.stq_type) && questionData.stq_options.length > 0
|
||||
? `<div class="options-container" style="display: block;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
${questionData.stq_options.map((option, index) => `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" value="${option}" placeholder="선택지 ${index + 1}">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>`
|
||||
: `<div class="options-container" style="display: none;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" placeholder="선택지 1">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionNumber}][options][]" class="option-input" placeholder="선택지 2">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>`;
|
||||
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-question-index="${questionNumber}">
|
||||
<div class="question-header">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div class="question-number">${questionNumber}</div>
|
||||
<div style="flex: 1;">
|
||||
<select name="questions[${questionNumber}][type]" class="question-type-select form-select" onchange="updateQuestionType(this)">
|
||||
<option value="text" ${questionData.stq_type === 'text' ? 'selected' : ''}>단답형</option>
|
||||
<option value="textarea" ${questionData.stq_type === 'textarea' ? 'selected' : ''}>장문형</option>
|
||||
<option value="radio" ${questionData.stq_type === 'radio' ? 'selected' : ''}>단일선택</option>
|
||||
<option value="checkbox" ${questionData.stq_type === 'checkbox' ? 'selected' : ''}>다중선택</option>
|
||||
<option value="select" ${questionData.stq_type === 'select' ? 'selected' : ''}>드롭다운</option>
|
||||
<option value="rating" ${questionData.stq_type === 'rating' ? 'selected' : ''}>평점</option>
|
||||
<option value="date" ${questionData.stq_type === 'date' ? 'selected' : ''}>날짜</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-controls">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'up')">↑</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'down')">↓</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionNumber}][title]" class="form-input"
|
||||
value="${questionData.stq_title}" placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionNumber}][description]" class="form-textarea"
|
||||
placeholder="질문에 대한 추가 설명">${questionData.stq_description || ''}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="questions[${questionNumber}][required]" value="1" ${questionData.stq_required ? 'checked' : ''}>
|
||||
<label>필수 질문</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${optionsHtml}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionsList').insertAdjacentHTML('beforeend', questionHtml);
|
||||
}
|
||||
|
||||
// 질문 추가
|
||||
function addQuestion() {
|
||||
questionCount++;
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-question-index="${questionCount}">
|
||||
<div class="question-header">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div class="question-number">${questionCount}</div>
|
||||
<div style="flex: 1;">
|
||||
<select name="questions[${questionCount}][type]" class="question-type-select form-select" onchange="updateQuestionType(this)">
|
||||
<option value="text">단답형</option>
|
||||
<option value="textarea">장문형</option>
|
||||
<option value="radio">단일선택</option>
|
||||
<option value="checkbox">다중선택</option>
|
||||
<option value="select">드롭다운</option>
|
||||
<option value="rating">평점</option>
|
||||
<option value="date">날짜</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-controls">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'up')">↑</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(this, 'down')">↓</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionCount}][title]" class="form-input" placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionCount}][description]" class="form-textarea" placeholder="질문에 대한 추가 설명"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" name="questions[${questionCount}][required]" value="1">
|
||||
<label>필수 질문</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-container" style="display: none;">
|
||||
<label class="form-label">선택지</label>
|
||||
<div class="options-list">
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionCount}][options][]" class="option-input" placeholder="선택지 1">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionCount}][options][]" class="option-input" placeholder="선택지 2">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="add-option-btn" onclick="addOption(this)">선택지 추가</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionsList').insertAdjacentHTML('beforeend', questionHtml);
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
// 질문 타입 변경
|
||||
function updateQuestionType(select) {
|
||||
const questionItem = select.closest('.question-item');
|
||||
const optionsContainer = questionItem.querySelector('.options-container');
|
||||
const questionType = select.value;
|
||||
|
||||
if (['radio', 'checkbox', 'select'].includes(questionType)) {
|
||||
optionsContainer.style.display = 'block';
|
||||
} else {
|
||||
optionsContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 삭제
|
||||
function removeQuestion(button) {
|
||||
if (confirm('이 질문을 삭제하시겠습니까?')) {
|
||||
button.closest('.question-item').remove();
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 순서 변경
|
||||
function moveQuestion(button, direction) {
|
||||
const questionItem = button.closest('.question-item');
|
||||
const sibling = direction === 'up' ? questionItem.previousElementSibling : questionItem.nextElementSibling;
|
||||
|
||||
if (sibling) {
|
||||
if (direction === 'up') {
|
||||
questionItem.parentNode.insertBefore(questionItem, sibling);
|
||||
} else {
|
||||
questionItem.parentNode.insertBefore(sibling, questionItem);
|
||||
}
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
// 선택지 추가
|
||||
function addOption(button) {
|
||||
const optionsList = button.previousElementSibling;
|
||||
const questionIndex = button.closest('.question-item').dataset.questionIndex;
|
||||
const optionCount = optionsList.children.length + 1;
|
||||
|
||||
const optionHtml = `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionIndex}][options][]" class="option-input" placeholder="선택지 ${optionCount}">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">삭제</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
optionsList.insertAdjacentHTML('beforeend', optionHtml);
|
||||
}
|
||||
|
||||
// 선택지 삭제
|
||||
function removeOption(button) {
|
||||
const optionsList = button.closest('.options-list');
|
||||
if (optionsList.children.length > 2) {
|
||||
button.closest('.option-item').remove();
|
||||
} else {
|
||||
alert('최소 2개의 선택지가 필요합니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 질문 번호 업데이트
|
||||
function updateQuestionNumbers() {
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
questions.forEach((question, index) => {
|
||||
const number = index + 1;
|
||||
question.querySelector('.question-number').textContent = number;
|
||||
question.dataset.questionIndex = number;
|
||||
|
||||
// input name 속성 업데이트
|
||||
const inputs = question.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.name && input.name.includes('questions[')) {
|
||||
input.name = input.name.replace(/questions\[\d+\]/, `questions[${number}]`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
questionCount = questions.length;
|
||||
}
|
||||
|
||||
// 폼 제출 전 검증
|
||||
document.getElementById('surveyForm').addEventListener('submit', function(e) {
|
||||
const title = document.querySelector('input[name="sv_title"]').value.trim();
|
||||
const startDate = new Date(document.querySelector('input[name="sv_start_date"]').value);
|
||||
const endDate = new Date(document.querySelector('input[name="sv_end_date"]').value);
|
||||
|
||||
if (!title) {
|
||||
alert('설문 제목을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
alert('종료일시는 시작일시보다 늦어야 합니다.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
if (questions.length === 0) {
|
||||
alert('최소 1개의 질문을 추가해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 질문 제목 검증
|
||||
let hasEmptyQuestion = false;
|
||||
questions.forEach(question => {
|
||||
const titleInput = question.querySelector('input[name*="[title]"]');
|
||||
if (!titleInput.value.trim()) {
|
||||
hasEmptyQuestion = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasEmptyQuestion) {
|
||||
alert('모든 질문의 제목을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// 페이지 로드 시 기존 질문들의 타입에 따라 옵션 표시
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.question-type-select').forEach(select => {
|
||||
updateQuestionType(select);
|
||||
});
|
||||
|
||||
// URL에서 template_id가 있으면 해당 템플릿 자동 선택
|
||||
const templateId = document.getElementById('templateId').value;
|
||||
if (templateId > 0) {
|
||||
const templateCard = document.querySelector(`[data-template="${templateId}"]`);
|
||||
if (templateCard) {
|
||||
templateCard.click(); // 템플릿 카드 클릭하여 자동 선택
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
$sub_menu = '710200';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$sv_id = isset($_POST['sv_id']) ? (int)$_POST['sv_id'] : 0;
|
||||
$action = isset($_POST['action']) ? $_POST['action'] : 'save_draft';
|
||||
$template_id = isset($_POST['template_id']) ? (int)$_POST['template_id'] : 0;
|
||||
|
||||
// 기본 정보
|
||||
$sv_title = isset($_POST['sv_title']) ? trim($_POST['sv_title']) : '';
|
||||
$sv_description = isset($_POST['sv_description']) ? trim($_POST['sv_description']) : '';
|
||||
$sv_start_date = isset($_POST['sv_start_date']) ? $_POST['sv_start_date'] : '';
|
||||
$sv_end_date = isset($_POST['sv_end_date']) ? $_POST['sv_end_date'] : '';
|
||||
$sv_allow_anonymous = isset($_POST['sv_allow_anonymous']) ? 1 : 0;
|
||||
$sv_allow_multiple = isset($_POST['sv_allow_multiple']) ? 1 : 0;
|
||||
$sv_max_responses = isset($_POST['sv_max_responses']) ? (int)$_POST['sv_max_responses'] : null;
|
||||
$sv_theme_color = isset($_POST['sv_theme_color']) ? $_POST['sv_theme_color'] : '#AA20FF';
|
||||
$sv_thank_message = isset($_POST['sv_thank_message']) ? trim($_POST['sv_thank_message']) : '';
|
||||
|
||||
// 유효성 검사
|
||||
if (!$sv_title) {
|
||||
alert('설문 제목을 입력해주세요.');
|
||||
}
|
||||
|
||||
if (!$sv_start_date || !$sv_end_date) {
|
||||
alert('설문 시작일과 종료일을 입력해주세요.');
|
||||
}
|
||||
|
||||
if (strtotime($sv_start_date) >= strtotime($sv_end_date)) {
|
||||
alert('종료일시는 시작일시보다 늦어야 합니다.');
|
||||
}
|
||||
|
||||
// 상태 결정
|
||||
$sv_status = ($action === 'save_and_activate') ? 'active' : 'draft';
|
||||
|
||||
// 질문 데이터
|
||||
$questions = isset($_POST['questions']) ? $_POST['questions'] : [];
|
||||
|
||||
if ($sv_id > 0) {
|
||||
// 기존 설문 수정
|
||||
$sql = "UPDATE survey_master SET
|
||||
sv_title = '".sql_real_escape_string($sv_title)."',
|
||||
sv_description = '".sql_real_escape_string($sv_description)."',
|
||||
sv_start_date = '".sql_real_escape_string($sv_start_date)."',
|
||||
sv_end_date = '".sql_real_escape_string($sv_end_date)."',
|
||||
sv_status = '".sql_real_escape_string($sv_status)."',
|
||||
sv_allow_anonymous = '$sv_allow_anonymous',
|
||||
sv_allow_multiple = '$sv_allow_multiple',
|
||||
sv_max_responses = ".($sv_max_responses ? "'$sv_max_responses'" : 'NULL').",
|
||||
sv_theme_color = '".sql_real_escape_string($sv_theme_color)."',
|
||||
sv_thank_message = '".sql_real_escape_string($sv_thank_message)."',
|
||||
sv_updated_at = NOW()
|
||||
WHERE sv_id = '$sv_id'";
|
||||
|
||||
sql_query($sql);
|
||||
|
||||
// 기존 질문들 삭제
|
||||
sql_query("DELETE FROM survey_questions WHERE sv_id = '$sv_id'");
|
||||
|
||||
} else {
|
||||
// 새 설문 생성
|
||||
if ($template_id > 0) {
|
||||
// 템플릿으로부터 설문 생성
|
||||
$new_sv_id = create_survey_from_template($template_id, $sv_title, $member['mb_id']);
|
||||
if ($new_sv_id) {
|
||||
// 템플릿으로 생성된 설문의 기본 정보 업데이트
|
||||
$sql = "UPDATE survey_master SET
|
||||
sv_description = '".sql_real_escape_string($sv_description)."',
|
||||
sv_start_date = '".sql_real_escape_string($sv_start_date)."',
|
||||
sv_end_date = '".sql_real_escape_string($sv_end_date)."',
|
||||
sv_status = '".sql_real_escape_string($sv_status)."',
|
||||
sv_allow_anonymous = '$sv_allow_anonymous',
|
||||
sv_allow_multiple = '$sv_allow_multiple',
|
||||
sv_max_responses = ".($sv_max_responses ? "'$sv_max_responses'" : 'NULL').",
|
||||
sv_theme_color = '".sql_real_escape_string($sv_theme_color)."',
|
||||
sv_thank_message = '".sql_real_escape_string($sv_thank_message)."'
|
||||
WHERE sv_id = '$new_sv_id'";
|
||||
sql_query($sql);
|
||||
|
||||
$sv_id = $new_sv_id;
|
||||
|
||||
// 템플릿 질문들을 사용자가 수정했다면 업데이트
|
||||
if (!empty($questions)) {
|
||||
sql_query("DELETE FROM survey_questions WHERE sv_id = '$sv_id'");
|
||||
}
|
||||
} else {
|
||||
alert('템플릿으로부터 설문 생성에 실패했습니다.');
|
||||
}
|
||||
} else {
|
||||
// 직접 생성
|
||||
$sql = "INSERT INTO survey_master
|
||||
(sv_title, sv_description, sv_start_date, sv_end_date, sv_status,
|
||||
sv_allow_anonymous, sv_allow_multiple, sv_max_responses, sv_theme_color,
|
||||
sv_thank_message, sv_created_by, sv_created_at)
|
||||
VALUES
|
||||
('".sql_real_escape_string($sv_title)."',
|
||||
'".sql_real_escape_string($sv_description)."',
|
||||
'".sql_real_escape_string($sv_start_date)."',
|
||||
'".sql_real_escape_string($sv_end_date)."',
|
||||
'".sql_real_escape_string($sv_status)."',
|
||||
'$sv_allow_anonymous',
|
||||
'$sv_allow_multiple',
|
||||
".($sv_max_responses ? "'$sv_max_responses'" : 'NULL').",
|
||||
'".sql_real_escape_string($sv_theme_color)."',
|
||||
'".sql_real_escape_string($sv_thank_message)."',
|
||||
'{$member['mb_id']}',
|
||||
NOW())";
|
||||
|
||||
sql_query($sql);
|
||||
$sv_id = sql_insert_id();
|
||||
|
||||
if (!$sv_id) {
|
||||
alert('설문 생성에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 질문들 저장 (템플릿 사용 시에도 사용자가 수정했다면 저장)
|
||||
if (!empty($questions)) {
|
||||
foreach ($questions as $order => $question) {
|
||||
$sq_type = isset($question['type']) ? trim($question['type']) : 'text';
|
||||
$sq_title = isset($question['title']) ? trim($question['title']) : '';
|
||||
$sq_description = isset($question['description']) ? trim($question['description']) : '';
|
||||
$sq_required = isset($question['required']) ? 1 : 0;
|
||||
$sq_options = '';
|
||||
|
||||
if (!$sq_title) {
|
||||
continue; // 제목이 없는 질문은 건너뛰기
|
||||
}
|
||||
|
||||
// 옵션 처리 (객관식 질문인 경우)
|
||||
if (in_array($sq_type, ['radio', 'checkbox', 'select']) && isset($question['options'])) {
|
||||
$options = array_filter($question['options'], function($option) {
|
||||
return trim($option) !== '';
|
||||
});
|
||||
|
||||
if (!empty($options)) {
|
||||
$sq_options = json_encode(array_values($options), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO survey_questions
|
||||
(sv_id, sq_order, sq_type, sq_title, sq_description, sq_required, sq_options, sq_created_at)
|
||||
VALUES
|
||||
('$sv_id',
|
||||
'".($order + 1)."',
|
||||
'".sql_real_escape_string($sq_type)."',
|
||||
'".sql_real_escape_string($sq_title)."',
|
||||
'".sql_real_escape_string($sq_description)."',
|
||||
'$sq_required',
|
||||
'".sql_real_escape_string($sq_options)."',
|
||||
NOW())";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
// 활성화된 설문인 경우 질문이 있는지 확인
|
||||
if ($sv_status === 'active') {
|
||||
$question_count = sql_fetch("SELECT COUNT(*) as cnt FROM survey_questions WHERE sv_id = '$sv_id'")['cnt'];
|
||||
if ($question_count == 0) {
|
||||
// 질문이 없으면 임시저장 상태로 변경
|
||||
sql_query("UPDATE survey_master SET sv_status = 'draft' WHERE sv_id = '$sv_id'");
|
||||
alert('질문이 없어서 임시저장 상태로 저장되었습니다. 질문을 추가한 후 활성화해주세요.', 'survey_form.php?sv_id='.$sv_id);
|
||||
}
|
||||
}
|
||||
|
||||
$message = '';
|
||||
if ($action === 'save_and_activate') {
|
||||
$message = '설문이 저장되고 활성화되었습니다.';
|
||||
} else {
|
||||
$message = '설문이 임시저장되었습니다.';
|
||||
}
|
||||
|
||||
alert($message, 'survey_list.php');
|
||||
?>
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
$sub_menu = '710100';
|
||||
include_once('./_common.php');
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$g5['title'] = '설문 목록';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 변수 초기화
|
||||
$sfl = isset($_GET['sfl']) ? clean_xss_tags($_GET['sfl']) : 'sv_title';
|
||||
$stx = isset($_GET['stx']) ? clean_xss_tags($_GET['stx']) : '';
|
||||
$status = isset($_GET['status']) ? clean_xss_tags($_GET['status']) : '';
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
|
||||
// 검색 조건
|
||||
$where = " WHERE 1=1 ";
|
||||
$sql_search = "";
|
||||
if ($stx) {
|
||||
$where .= " AND $sfl LIKE '%".sql_real_escape_string($stx)."%' ";
|
||||
$sql_search .= "&sfl=$sfl&stx=".urlencode($stx);
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
$where .= " AND sv_status = '".sql_real_escape_string($status)."' ";
|
||||
$sql_search .= "&status=".urlencode($status);
|
||||
}
|
||||
|
||||
// 페이징
|
||||
$sql_common = " FROM survey_master $where ";
|
||||
$sql = " SELECT COUNT(*) as cnt $sql_common ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = 20;
|
||||
$total_page = ceil($total_count / $rows);
|
||||
if ($page < 1) $page = 1;
|
||||
$from_record = ($page - 1) * $rows;
|
||||
|
||||
$sql = " SELECT * $sql_common ORDER BY sv_id DESC LIMIT $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 상태별 카운트
|
||||
$status_counts = array();
|
||||
$status_sql = "SELECT sv_status, COUNT(*) as cnt FROM survey_master GROUP BY sv_status";
|
||||
$status_result = sql_query($status_sql);
|
||||
while ($status_row = sql_fetch_array($status_result)) {
|
||||
$status_counts[$status_row['sv_status']] = $status_row['cnt'];
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.survey-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.survey-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.status-btn:hover,
|
||||
.status-btn.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.survey-table {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.survey-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.survey-table th {
|
||||
background: #fff;
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.survey-table td {
|
||||
padding: 15px 10px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.survey-table tr:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-draft { background: #e9ecef; color: #495057; }
|
||||
.status-active { background: #d4edda; color: #155724; }
|
||||
.status-closed { background: #f8d7da; color: #721c24; }
|
||||
.status-deleted { background: #d1ecf1; color: #0c5460; }
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary { background: #007bff; color: white; }
|
||||
.btn-success { background: #28a745; color: white; }
|
||||
.btn-warning { background: #ffc107; color: #212529; }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
.btn-info { background: #17a2b8; color: white; }
|
||||
|
||||
.btn-sm:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form select,
|
||||
.search-form input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
font-size: 1.5em;
|
||||
box-shadow: 0 4px 12px rgba(170, 32, 255, 0.3);
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.create-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: scale(1.1);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="survey-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-poll"></i> 설문 관리</h1>
|
||||
<p>설문조사를 생성하고 관리할 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="survey-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['active'] ?? 0); ?></div>
|
||||
<div class="stat-label">진행중인 설문</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['draft'] ?? 0); ?></div>
|
||||
<div class="stat-label">작성중인 설문</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($status_counts['closed'] ?? 0); ?></div>
|
||||
<div class="stat-label">종료된 설문</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_count); ?></div>
|
||||
<div class="stat-label">전체 설문</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<form method="get" class="form-row">
|
||||
<select name="sfl">
|
||||
<option value="sv_title"<?php echo $sfl == 'sv_title' ? ' selected' : ''; ?>>제목</option>
|
||||
<option value="sv_description"<?php echo $sfl == 'sv_description' ? ' selected' : ''; ?>>설명</option>
|
||||
<option value="sv_created_by"<?php echo $sfl == 'sv_created_by' ? ' selected' : ''; ?>>작성자</option>
|
||||
</select>
|
||||
<input type="text" name="stx" value="<?php echo $stx; ?>" placeholder="검색어를 입력하세요">
|
||||
<button type="submit" class="btn-sm btn-primary">
|
||||
<i class="fa fa-search"></i> 검색
|
||||
</button>
|
||||
<?php if ($stx): ?>
|
||||
<a href="?" class="btn-sm btn-warning">
|
||||
<i class="fa fa-times"></i> 초기화
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="status-filter">
|
||||
<a href="?" class="status-btn <?php echo !isset($_GET['status']) ? 'active' : ''; ?>">
|
||||
<i class="fa fa-list"></i> 전체
|
||||
</a>
|
||||
<a href="?status=draft" class="status-btn <?php echo $status == 'draft' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-edit"></i> 작성중 (<?php echo $status_counts['draft'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?status=active" class="status-btn <?php echo $status == 'active' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-play"></i> 진행중 (<?php echo $status_counts['active'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?status=closed" class="status-btn <?php echo $status == 'closed' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-stop"></i> 종료 (<?php echo $status_counts['closed'] ?? 0; ?>)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="survey-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th>설문 제목</th>
|
||||
<th width="100">상태</th>
|
||||
<th width="120">응답 수</th>
|
||||
<th width="150">설문 기간</th>
|
||||
<th width="100">작성자</th>
|
||||
<th width="120">생성일</th>
|
||||
<th width="200">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
if (sql_num_rows($result) > 0) {
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$response_count = get_survey_response_count($row['sv_id'], 'completed');
|
||||
$total_responses = get_survey_response_count($row['sv_id'], '');
|
||||
|
||||
// 상태별 클래스
|
||||
$status_class = 'status-' . $row['sv_status'];
|
||||
$status_text = array(
|
||||
'draft' => '작성중',
|
||||
'active' => '진행중',
|
||||
'closed' => '종료',
|
||||
'deleted' => '삭제됨'
|
||||
);
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo $row['sv_id']; ?></td>
|
||||
<td>
|
||||
<strong><?php echo htmlspecialchars($row['sv_title']); ?></strong>
|
||||
<?php if ($row['sv_description']): ?>
|
||||
<br><small style="color: #666;"><?php
|
||||
if (function_exists('mb_substr')) {
|
||||
echo htmlspecialchars(mb_substr($row['sv_description'], 0, 50)) . '...';
|
||||
} else {
|
||||
echo htmlspecialchars(substr($row['sv_description'], 0, 150)) . '...'; // mbstring이 없을 경우, 바이트 단위로 자름
|
||||
}
|
||||
?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge <?php echo $status_class; ?>">
|
||||
<?php echo $status_text[$row['sv_status']]; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong><?php echo number_format($response_count); ?></strong>
|
||||
<?php if ($total_responses > $response_count): ?>
|
||||
<br><small style="color: #666;">진행중: <?php echo $total_responses - $response_count; ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<small>
|
||||
<?php echo date('m/d', strtotime($row['sv_start_date'])); ?> ~<br>
|
||||
<?php echo date('m/d', strtotime($row['sv_end_date'])); ?>
|
||||
</small>
|
||||
</td>
|
||||
<td><?php echo $row['sv_created_by']; ?></td>
|
||||
<td>
|
||||
<small><?php echo date('Y-m-d', strtotime($row['sv_created_at'])); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="survey_form.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-primary" title="수정">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
<?php if ($row['sv_status'] == 'draft'): ?>
|
||||
<a href="survey_activate.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-success" title="활성화" onclick="return confirm('설문을 활성화하시겠습니까?')">
|
||||
<i class="fa fa-play"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($response_count > 0): ?>
|
||||
<a href="statistics.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-info" title="통계">
|
||||
<i class="fa fa-chart-bar"></i>
|
||||
</a>
|
||||
<a href="export.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-warning" title="엑셀 다운로드">
|
||||
<i class="fa fa-file-excel"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<a href="survey_delete.php?sv_id=<?php echo $row['sv_id']; ?>" class="btn-sm btn-danger" title="삭제" onclick="return confirm('정말 삭제하시겠습니까?')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<tr>
|
||||
<td colspan="8" style="text-align: center; padding: 50px; color: #999;">
|
||||
<i class="fa fa-inbox" style="font-size: 3em; margin-bottom: 20px; display: block;"></i>
|
||||
등록된 설문이 없습니다.<br>
|
||||
<a href="survey_form.php" style="color: #AA20FF; margin-top: 10px; display: inline-block;">첫 설문을 만들어보세요</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 페이징
|
||||
if ($total_page > 1) {
|
||||
$paging = get_paging(10, $page, $total_page, "?$sql_search&page=");
|
||||
echo '<div style="margin-top: 20px;">' . $paging . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<a href="survey_form.php" class="create-btn" title="새 설문 만들기">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$st_id = isset($_GET['st_id']) ? (int)$_GET['st_id'] : 0;
|
||||
$is_edit = $st_id > 0;
|
||||
|
||||
$template = [
|
||||
'st_name' => '',
|
||||
'st_description' => '',
|
||||
'st_category' => '고객서비스',
|
||||
'st_is_public' => 1
|
||||
];
|
||||
|
||||
$questions = [];
|
||||
|
||||
if ($is_edit) {
|
||||
$template = sql_fetch("SELECT * FROM survey_templates WHERE st_id = '$st_id'");
|
||||
if (!$template) {
|
||||
alert('존재하지 않는 템플릿입니다.', 'template_list.php');
|
||||
}
|
||||
|
||||
// 템플릿 질문들 가져오기
|
||||
$question_sql = "SELECT * FROM survey_template_questions WHERE st_id = '$st_id' ORDER BY stq_order ASC";
|
||||
$question_result = sql_query($question_sql);
|
||||
while ($question = sql_fetch_array($question_result)) {
|
||||
$questions[] = $question;
|
||||
}
|
||||
|
||||
$g5['title'] = '템플릿 수정';
|
||||
} else {
|
||||
$g5['title'] = '새 템플릿 만들기';
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
|
||||
<!-- CSS는 template_form.css 파일에서 자동 로드됩니다 -->
|
||||
|
||||
<div class="template-form-container">
|
||||
<div class="form-header">
|
||||
<h1><i class="fa fa-magic"></i> <?php echo $is_edit ? '템플릿 수정' : '새 템플릿 만들기'; ?></h1>
|
||||
<p>설문 템플릿을 만들어 다른 사용자들과 공유하세요</p>
|
||||
</div>
|
||||
|
||||
<div class="form-content">
|
||||
<form name="templateForm" method="post" action="template_form_update.php">
|
||||
<?php if ($is_edit): ?>
|
||||
<input type="hidden" name="st_id" value="<?php echo $st_id; ?>">
|
||||
<input type="hidden" name="mode" value="update">
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="mode" value="insert">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fa fa-info-circle"></i> 기본 정보
|
||||
</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label required">템플릿 이름</label>
|
||||
<input type="text" name="st_name" class="form-control"
|
||||
value="<?php echo htmlspecialchars($template['st_name']); ?>"
|
||||
placeholder="템플릿 이름을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">템플릿 설명</label>
|
||||
<textarea name="st_description" class="form-control" rows="3"
|
||||
placeholder="템플릿에 대한 설명을 입력하세요"><?php echo htmlspecialchars($template['st_description']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label required">카테고리</label>
|
||||
<select name="st_category" class="form-control form-select" required>
|
||||
<option value="고객서비스"<?php echo $template['st_category'] == '고객서비스' ? ' selected' : ''; ?>>고객서비스</option>
|
||||
<option value="마케팅"<?php echo $template['st_category'] == '마케팅' ? ' selected' : ''; ?>>마케팅</option>
|
||||
<option value="제품개발"<?php echo $template['st_category'] == '제품개발' ? ' selected' : ''; ?>>제품개발</option>
|
||||
<option value="인사관리"<?php echo $template['st_category'] == '인사관리' ? ' selected' : ''; ?>>인사관리</option>
|
||||
<option value="기타"<?php echo $template['st_category'] == '기타' ? ' selected' : ''; ?>>기타</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">공개 설정</label>
|
||||
<select name="st_is_public" class="form-control form-select">
|
||||
<option value="1"<?php echo $template['st_is_public'] == 1 ? ' selected' : ''; ?>>공개</option>
|
||||
<option value="0"<?php echo $template['st_is_public'] == 0 ? ' selected' : ''; ?>>비공개</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fa fa-question-circle"></i> 질문 구성
|
||||
</h2>
|
||||
|
||||
<div class="questions-container" id="questionsContainer">
|
||||
<?php if (count($questions) > 0): ?>
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-item" data-index="<?php echo $index; ?>">
|
||||
<div class="question-header">
|
||||
<div class="question-number"><?php echo $index + 1; ?></div>
|
||||
<div class="question-title-text">질문 <?php echo $index + 1; ?></div>
|
||||
<div class="question-actions">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(<?php echo $index; ?>, 'up')">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(<?php echo $index; ?>, 'down')">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(<?php echo $index; ?>)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="questions[<?php echo $index; ?>][stq_id]" value="<?php echo $question['stq_id']; ?>">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[<?php echo $index; ?>][stq_title]" class="form-control"
|
||||
value="<?php echo htmlspecialchars($question['stq_title']); ?>"
|
||||
placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group question-type-group">
|
||||
<label class="form-label">질문 유형</label>
|
||||
<select name="questions[<?php echo $index; ?>][stq_type]" class="form-control form-select question-type-select"
|
||||
onchange="toggleOptions(<?php echo $index; ?>)">
|
||||
<option value="text"<?php echo $question['stq_type'] == 'text' ? ' selected' : ''; ?>>단답형</option>
|
||||
<option value="textarea"<?php echo $question['stq_type'] == 'textarea' ? ' selected' : ''; ?>>장문형</option>
|
||||
<option value="radio"<?php echo $question['stq_type'] == 'radio' ? ' selected' : ''; ?>>객관식(단일)</option>
|
||||
<option value="checkbox"<?php echo $question['stq_type'] == 'checkbox' ? ' selected' : ''; ?>>객관식(다중)</option>
|
||||
<option value="select"<?php echo $question['stq_type'] == 'select' ? ' selected' : ''; ?>>드롭다운</option>
|
||||
<option value="rating"<?php echo $question['stq_type'] == 'rating' ? ' selected' : ''; ?>>평점</option>
|
||||
<option value="date"<?php echo $question['stq_type'] == 'date' ? ' selected' : ''; ?>>날짜</option>
|
||||
<option value="email"<?php echo $question['stq_type'] == 'email' ? ' selected' : ''; ?>>이메일</option>
|
||||
<option value="number"<?php echo $question['stq_type'] == 'number' ? ' selected' : ''; ?>>숫자</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[<?php echo $index; ?>][stq_description]" class="form-control" rows="2"
|
||||
placeholder="질문에 대한 추가 설명"><?php echo htmlspecialchars($question['stq_description']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="questions[<?php echo $index; ?>][stq_required]" value="1"
|
||||
<?php echo $question['stq_required'] ? 'checked' : ''; ?>>
|
||||
필수 질문
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (in_array($question['stq_type'], ['radio', 'checkbox', 'select'])): ?>
|
||||
<div class="options-container" id="optionsContainer<?php echo $index; ?>">
|
||||
<label class="form-label">선택 옵션</label>
|
||||
<?php
|
||||
$options = json_decode($question['stq_options'], true);
|
||||
if (is_array($options)):
|
||||
foreach ($options as $opt_index => $option):
|
||||
?>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[<?php echo $index; ?>][options][]"
|
||||
class="form-control option-input"
|
||||
value="<?php echo htmlspecialchars($option); ?>"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
<button type="button" class="btn-add-option" onclick="addOption(<?php echo $index; ?>)">
|
||||
<i class="fa fa-plus"></i> 옵션 추가
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="empty-questions" id="emptyQuestions">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
<h3>질문을 추가해주세요</h3>
|
||||
<p>아래 버튼을 클릭하여 첫 번째 질문을 만들어보세요.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-add-question" onclick="addQuestion()">
|
||||
<i class="fa fa-plus"></i> 질문 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fa fa-save"></i> <?php echo $is_edit ? '템플릿 수정' : '템플릿 저장'; ?>
|
||||
</button>
|
||||
<a href="template_list.php" class="btn-primary btn-outline">
|
||||
<i class="fa fa-times"></i> 취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript는 template_form.js 파일에서 자동 로드됩니다 -->
|
||||
<script>
|
||||
// 페이지별 초기 데이터 설정
|
||||
window.templateFormData = {
|
||||
questionIndex: <?php echo count($questions); ?>,
|
||||
isEdit: <?php echo $is_edit ? 'true' : 'false'; ?>
|
||||
};
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,720 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$st_id = isset($_GET['st_id']) ? (int)$_GET['st_id'] : 0;
|
||||
$is_edit = $st_id > 0;
|
||||
|
||||
$template = array(
|
||||
'st_name' => '',
|
||||
'st_description' => '',
|
||||
'st_category' => '고객서비스',
|
||||
'st_is_public' => 1
|
||||
);
|
||||
|
||||
$questions = array();
|
||||
|
||||
if ($is_edit) {
|
||||
$template = sql_fetch("SELECT * FROM survey_templates WHERE st_id = '$st_id'");
|
||||
if (!$template) {
|
||||
alert('존재하지 않는 템플릿입니다.', 'template_list.php');
|
||||
}
|
||||
|
||||
// 템플릿 질문들 가져오기
|
||||
$question_sql = "SELECT * FROM survey_template_questions WHERE st_id = '$st_id' ORDER BY stq_order ASC";
|
||||
$question_result = sql_query($question_sql);
|
||||
while ($question = sql_fetch_array($question_result)) {
|
||||
$questions[] = $question;
|
||||
}
|
||||
|
||||
$g5['title'] = '템플릿 수정';
|
||||
} else {
|
||||
$g5['title'] = '새 템플릿 만들기';
|
||||
}
|
||||
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.template-form-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.form-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
margin-right: 10px;
|
||||
color: #AA20FF;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-label.required::after {
|
||||
content: ' *';
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #AA20FF;
|
||||
box-shadow: 0 0 0 3px rgba(170, 32, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 12px center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px 12px;
|
||||
padding-right: 40px;
|
||||
height: 43px !important;
|
||||
line-height: 41px !important;
|
||||
}
|
||||
|
||||
.questions-container {
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-right: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85em;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.option-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-add-option {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn-add-option:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.btn-add-question {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-add-question:hover {
|
||||
background: #8A1ACC;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #8A1ACC;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #6c757d;
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.empty-questions {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-questions i {
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.template-form-container {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="template-form-container">
|
||||
<div class="form-header">
|
||||
<h1><i class="fa fa-magic"></i> <?php echo $is_edit ? '템플릿 수정' : '새 템플릿 만들기'; ?></h1>
|
||||
<p>설문 템플릿을 만들어 다른 사용자들과 공유하세요</p>
|
||||
</div>
|
||||
|
||||
<div class="form-content">
|
||||
<form name="templateForm" method="post" action="template_form_update.php">
|
||||
<?php if ($is_edit): ?>
|
||||
<input type="hidden" name="st_id" value="<?php echo $st_id; ?>">
|
||||
<input type="hidden" name="mode" value="update">
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="mode" value="insert">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fa fa-info-circle"></i> 기본 정보
|
||||
</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label required">템플릿 이름</label>
|
||||
<input type="text" name="st_name" class="form-control"
|
||||
value="<?php echo htmlspecialchars($template['st_name']); ?>"
|
||||
placeholder="템플릿 이름을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">템플릿 설명</label>
|
||||
<textarea name="st_description" class="form-control" rows="3"
|
||||
placeholder="템플릿에 대한 설명을 입력하세요"><?php echo htmlspecialchars($template['st_description']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label required">카테고리</label>
|
||||
<select name="st_category" class="form-control form-select" required>
|
||||
<option value="고객서비스"<?php echo $template['st_category'] == '고객서비스' ? ' selected' : ''; ?>>고객서비스</option>
|
||||
<option value="마케팅"<?php echo $template['st_category'] == '마케팅' ? ' selected' : ''; ?>>마케팅</option>
|
||||
<option value="제품개발"<?php echo $template['st_category'] == '제품개발' ? ' selected' : ''; ?>>제품개발</option>
|
||||
<option value="인사관리"<?php echo $template['st_category'] == '인사관리' ? ' selected' : ''; ?>>인사관리</option>
|
||||
<option value="기타"<?php echo $template['st_category'] == '기타' ? ' selected' : ''; ?>>기타</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">공개 설정</label>
|
||||
<select name="st_is_public" class="form-control form-select">
|
||||
<option value="1"<?php echo $template['st_is_public'] == 1 ? ' selected' : ''; ?>>공개</option>
|
||||
<option value="0"<?php echo $template['st_is_public'] == 0 ? ' selected' : ''; ?>>비공개</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h2 class="section-title">
|
||||
<i class="fa fa-question-circle"></i> 질문 구성
|
||||
</h2>
|
||||
|
||||
<div class="questions-container" id="questionsContainer">
|
||||
<?php if (count($questions) > 0): ?>
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-item" data-index="<?php echo $index; ?>">
|
||||
<div class="question-header">
|
||||
<div class="question-number"><?php echo $index + 1; ?></div>
|
||||
<div style="flex: 1;">질문 <?php echo $index + 1; ?></div>
|
||||
<div class="question-actions">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(<?php echo $index; ?>, 'up')">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(<?php echo $index; ?>, 'down')">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(<?php echo $index; ?>)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="questions[<?php echo $index; ?>][stq_id]" value="<?php echo $question['stq_id']; ?>">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[<?php echo $index; ?>][stq_title]" class="form-control"
|
||||
value="<?php echo htmlspecialchars($question['stq_title']); ?>"
|
||||
placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="min-width: 150px;">
|
||||
<label class="form-label">질문 유형</label>
|
||||
<select name="questions[<?php echo $index; ?>][stq_type]" class="form-control form-select question-type-select"
|
||||
onchange="toggleOptions(<?php echo $index; ?>)">
|
||||
<option value="text"<?php echo $question['stq_type'] == 'text' ? ' selected' : ''; ?>>단답형</option>
|
||||
<option value="textarea"<?php echo $question['stq_type'] == 'textarea' ? ' selected' : ''; ?>>장문형</option>
|
||||
<option value="radio"<?php echo $question['stq_type'] == 'radio' ? ' selected' : ''; ?>>객관식(단일)</option>
|
||||
<option value="checkbox"<?php echo $question['stq_type'] == 'checkbox' ? ' selected' : ''; ?>>객관식(다중)</option>
|
||||
<option value="select"<?php echo $question['stq_type'] == 'select' ? ' selected' : ''; ?>>드롭다운</option>
|
||||
<option value="rating"<?php echo $question['stq_type'] == 'rating' ? ' selected' : ''; ?>>평점</option>
|
||||
<option value="date"<?php echo $question['stq_type'] == 'date' ? ' selected' : ''; ?>>날짜</option>
|
||||
<option value="email"<?php echo $question['stq_type'] == 'email' ? ' selected' : ''; ?>>이메일</option>
|
||||
<option value="number"<?php echo $question['stq_type'] == 'number' ? ' selected' : ''; ?>>숫자</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[<?php echo $index; ?>][stq_description]" class="form-control" rows="2"
|
||||
placeholder="질문에 대한 추가 설명"><?php echo htmlspecialchars($question['stq_description']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="questions[<?php echo $index; ?>][stq_required]" value="1"
|
||||
<?php echo $question['stq_required'] ? 'checked' : ''; ?>>
|
||||
필수 질문
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (in_array($question['stq_type'], ['radio', 'checkbox', 'select'])): ?>
|
||||
<div class="options-container" id="optionsContainer<?php echo $index; ?>">
|
||||
<label class="form-label">선택 옵션</label>
|
||||
<?php
|
||||
$options = json_decode($question['stq_options'], true);
|
||||
if (is_array($options)):
|
||||
foreach ($options as $opt_index => $option):
|
||||
?>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[<?php echo $index; ?>][options][]"
|
||||
class="form-control option-input"
|
||||
value="<?php echo htmlspecialchars($option); ?>"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
<button type="button" class="btn-add-option" onclick="addOption(<?php echo $index; ?>)">
|
||||
<i class="fa fa-plus"></i> 옵션 추가
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="empty-questions" id="emptyQuestions">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
<h3>질문을 추가해주세요</h3>
|
||||
<p>아래 버튼을 클릭하여 첫 번째 질문을 만들어보세요.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-add-question" onclick="addQuestion()">
|
||||
<i class="fa fa-plus"></i> 질문 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fa fa-save"></i> <?php echo $is_edit ? '템플릿 수정' : '템플릿 저장'; ?>
|
||||
</button>
|
||||
<a href="template_list.php" class="btn-primary btn-outline">
|
||||
<i class="fa fa-times"></i> 취소
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let questionIndex = <?php echo count($questions); ?>;
|
||||
|
||||
function addQuestion() {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
const emptyState = document.getElementById('emptyQuestions');
|
||||
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
const questionHtml = `
|
||||
<div class="question-item" data-index="${questionIndex}">
|
||||
<div class="question-header">
|
||||
<div class="question-number">${questionIndex + 1}</div>
|
||||
<div style="flex: 1;">질문 ${questionIndex + 1}</div>
|
||||
<div class="question-actions">
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(${questionIndex}, 'up')">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-secondary" onclick="moveQuestion(${questionIndex}, 'down')">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeQuestion(${questionIndex})">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 제목</label>
|
||||
<input type="text" name="questions[${questionIndex}][stq_title]" class="form-control"
|
||||
placeholder="질문을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="min-width: 150px;">
|
||||
<label class="form-label">질문 유형</label>
|
||||
<select name="questions[${questionIndex}][stq_type]" class="form-control form-select question-type-select"
|
||||
onchange="toggleOptions(${questionIndex})">
|
||||
<option value="text">단답형</option>
|
||||
<option value="textarea">장문형</option>
|
||||
<option value="radio">객관식(단일)</option>
|
||||
<option value="checkbox">객관식(다중)</option>
|
||||
<option value="select">드롭다운</option>
|
||||
<option value="rating">평점</option>
|
||||
<option value="date">날짜</option>
|
||||
<option value="email">이메일</option>
|
||||
<option value="number">숫자</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">질문 설명 (선택사항)</label>
|
||||
<textarea name="questions[${questionIndex}][stq_description]" class="form-control" rows="2"
|
||||
placeholder="질문에 대한 추가 설명"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="questions[${questionIndex}][stq_required]" value="1">
|
||||
필수 질문
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', questionHtml);
|
||||
questionIndex++;
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
function removeQuestion(index) {
|
||||
if (confirm('이 질문을 삭제하시겠습니까?')) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
questionItem.remove();
|
||||
|
||||
const remainingQuestions = document.querySelectorAll('.question-item');
|
||||
if (remainingQuestions.length === 0) {
|
||||
const container = document.getElementById('questionsContainer');
|
||||
container.innerHTML = `
|
||||
<div class="empty-questions" id="emptyQuestions">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
<h3>질문을 추가해주세요</h3>
|
||||
<p>아래 버튼을 클릭하여 첫 번째 질문을 만들어보세요.</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveQuestion(index, direction) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
const container = document.getElementById('questionsContainer');
|
||||
|
||||
if (direction === 'up' && questionItem.previousElementSibling) {
|
||||
container.insertBefore(questionItem, questionItem.previousElementSibling);
|
||||
} else if (direction === 'down' && questionItem.nextElementSibling) {
|
||||
container.insertBefore(questionItem.nextElementSibling, questionItem);
|
||||
}
|
||||
|
||||
updateQuestionNumbers();
|
||||
}
|
||||
|
||||
function updateQuestionNumbers() {
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
questions.forEach((question, index) => {
|
||||
const numberElement = question.querySelector('.question-number');
|
||||
const titleElement = question.querySelector('.question-header > div:nth-child(2)');
|
||||
|
||||
numberElement.textContent = index + 1;
|
||||
titleElement.textContent = `질문 ${index + 1}`;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleOptions(index) {
|
||||
const questionItem = document.querySelector(`[data-index="${index}"]`);
|
||||
const typeSelect = questionItem.querySelector('.question-type-select');
|
||||
const selectedType = typeSelect.value;
|
||||
|
||||
// 기존 옵션 컨테이너 제거
|
||||
const existingOptions = questionItem.querySelector('.options-container');
|
||||
if (existingOptions) {
|
||||
existingOptions.remove();
|
||||
}
|
||||
|
||||
// 객관식 질문인 경우 옵션 컨테이너 추가
|
||||
if (['radio', 'checkbox', 'select'].includes(selectedType)) {
|
||||
const optionsHtml = `
|
||||
<div class="options-container" id="optionsContainer${index}">
|
||||
<label class="form-label">선택 옵션</label>
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${index}][options][]"
|
||||
class="form-control option-input"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn-add-option" onclick="addOption(${index})">
|
||||
<i class="fa fa-plus"></i> 옵션 추가
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
questionItem.insertAdjacentHTML('beforeend', optionsHtml);
|
||||
}
|
||||
}
|
||||
|
||||
function addOption(questionIndex) {
|
||||
const container = document.getElementById(`optionsContainer${questionIndex}`);
|
||||
const addButton = container.querySelector('.btn-add-option');
|
||||
|
||||
const optionHtml = `
|
||||
<div class="option-item">
|
||||
<input type="text" name="questions[${questionIndex}][options][]"
|
||||
class="form-control option-input"
|
||||
placeholder="옵션을 입력하세요">
|
||||
<button type="button" class="btn-sm btn-danger" onclick="removeOption(this)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
addButton.insertAdjacentHTML('beforebegin', optionHtml);
|
||||
}
|
||||
|
||||
function removeOption(button) {
|
||||
button.parentElement.remove();
|
||||
}
|
||||
|
||||
// 폼 제출 전 검증
|
||||
document.querySelector('form[name="templateForm"]').addEventListener('submit', function(e) {
|
||||
const templateName = document.querySelector('input[name="st_name"]').value.trim();
|
||||
if (!templateName) {
|
||||
alert('템플릿 이름을 입력해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = document.querySelectorAll('.question-item');
|
||||
if (questions.length === 0) {
|
||||
alert('최소 1개 이상의 질문을 추가해주세요.');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 각 질문의 제목 검증
|
||||
let hasEmptyTitle = false;
|
||||
questions.forEach((question, index) => {
|
||||
const titleInput = question.querySelector('input[name*="[stq_title]"]');
|
||||
if (!titleInput.value.trim()) {
|
||||
alert(`질문 ${index + 1}의 제목을 입력해주세요.`);
|
||||
hasEmptyTitle = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasEmptyTitle) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
$mode = isset($_POST['mode']) ? $_POST['mode'] : '';
|
||||
$st_id = isset($_POST['st_id']) ? (int)$_POST['st_id'] : 0;
|
||||
|
||||
if (!$mode) {
|
||||
alert('잘못된 접근입니다.');
|
||||
}
|
||||
|
||||
// 기본 정보
|
||||
$st_name = isset($_POST['st_name']) ? trim($_POST['st_name']) : '';
|
||||
$st_description = isset($_POST['st_description']) ? trim($_POST['st_description']) : '';
|
||||
$st_category = isset($_POST['st_category']) ? trim($_POST['st_category']) : '기타';
|
||||
$st_is_public = isset($_POST['st_is_public']) ? (int)$_POST['st_is_public'] : 1;
|
||||
|
||||
if (!$st_name) {
|
||||
alert('템플릿 이름을 입력해주세요.');
|
||||
}
|
||||
|
||||
// 질문 데이터
|
||||
$questions = isset($_POST['questions']) ? $_POST['questions'] : array();
|
||||
|
||||
if (empty($questions)) {
|
||||
alert('최소 1개 이상의 질문을 추가해주세요.');
|
||||
}
|
||||
|
||||
if ($mode == 'insert') {
|
||||
// 새 템플릿 생성
|
||||
$sql = "INSERT INTO survey_templates
|
||||
(st_name, st_description, st_category, st_is_public, st_created_by, st_created_at)
|
||||
VALUES
|
||||
('".sql_real_escape_string($st_name)."',
|
||||
'".sql_real_escape_string($st_description)."',
|
||||
'".sql_real_escape_string($st_category)."',
|
||||
'$st_is_public',
|
||||
'{$member['mb_id']}',
|
||||
NOW())";
|
||||
|
||||
sql_query($sql);
|
||||
$st_id = sql_insert_id();
|
||||
|
||||
if (!$st_id) {
|
||||
alert('템플릿 생성에 실패했습니다.');
|
||||
}
|
||||
|
||||
} else if ($mode == 'update') {
|
||||
// 기존 템플릿 수정
|
||||
if (!$st_id) {
|
||||
alert('템플릿 ID가 없습니다.');
|
||||
}
|
||||
|
||||
$sql = "UPDATE survey_templates SET
|
||||
st_name = '".sql_real_escape_string($st_name)."',
|
||||
st_description = '".sql_real_escape_string($st_description)."',
|
||||
st_category = '".sql_real_escape_string($st_category)."',
|
||||
st_is_public = '$st_is_public',
|
||||
st_updated_at = NOW()
|
||||
WHERE st_id = '$st_id'";
|
||||
|
||||
sql_query($sql);
|
||||
|
||||
// 기존 질문들 삭제
|
||||
sql_query("DELETE FROM survey_template_questions WHERE st_id = '$st_id'");
|
||||
}
|
||||
|
||||
// 질문들 저장
|
||||
foreach ($questions as $order => $question) {
|
||||
$stq_title = isset($question['stq_title']) ? trim($question['stq_title']) : '';
|
||||
$stq_description = isset($question['stq_description']) ? trim($question['stq_description']) : '';
|
||||
$stq_type = isset($question['stq_type']) ? trim($question['stq_type']) : 'text';
|
||||
$stq_required = isset($question['stq_required']) ? 1 : 0;
|
||||
$stq_options = '';
|
||||
|
||||
if (!$stq_title) {
|
||||
continue; // 제목이 없는 질문은 건너뛰기
|
||||
}
|
||||
|
||||
// 옵션 처리 (객관식 질문인 경우)
|
||||
if (in_array($stq_type, ['radio', 'checkbox', 'select']) && isset($question['options'])) {
|
||||
$options = array_filter($question['options'], function($option) {
|
||||
return trim($option) !== '';
|
||||
});
|
||||
|
||||
if (!empty($options)) {
|
||||
$stq_options = json_encode(array_values($options), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO survey_template_questions
|
||||
(st_id, stq_order, stq_type, stq_title, stq_description, stq_required, stq_options, stq_created_at)
|
||||
VALUES
|
||||
('$st_id',
|
||||
'".($order + 1)."',
|
||||
'".sql_real_escape_string($stq_type)."',
|
||||
'".sql_real_escape_string($stq_title)."',
|
||||
'".sql_real_escape_string($stq_description)."',
|
||||
'$stq_required',
|
||||
'".sql_real_escape_string($stq_options)."',
|
||||
NOW())";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
$message = $mode == 'insert' ? '템플릿이 생성되었습니다.' : '템플릿이 수정되었습니다.';
|
||||
alert($message, 'template_list.php');
|
||||
?>
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$g5['title'] = '템플릿 관리';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 변수 초기화
|
||||
$sfl = isset($_GET['sfl']) ? clean_xss_tags($_GET['sfl']) : 'st_name';
|
||||
$stx = isset($_GET['stx']) ? clean_xss_tags($_GET['stx']) : '';
|
||||
$category = isset($_GET['category']) ? clean_xss_tags($_GET['category']) : '';
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
|
||||
// 검색 조건
|
||||
$where = " WHERE st_is_public = 1 ";
|
||||
$sql_search = "";
|
||||
|
||||
if ($stx) {
|
||||
$where .= " AND $sfl LIKE '%".sql_real_escape_string($stx)."%' ";
|
||||
$sql_search .= "&sfl=$sfl&stx=".urlencode($stx);
|
||||
}
|
||||
|
||||
if ($category) {
|
||||
$where .= " AND st_category = '".sql_real_escape_string($category)."' ";
|
||||
$sql_search .= "&category=".urlencode($category);
|
||||
}
|
||||
|
||||
// 페이징
|
||||
$sql_common = " FROM survey_templates $where ";
|
||||
$sql = " SELECT COUNT(*) as cnt $sql_common ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = 20;
|
||||
$total_page = ceil($total_count / $rows);
|
||||
if ($page < 1) $page = 1;
|
||||
$from_record = ($page - 1) * $rows;
|
||||
|
||||
$sql = " SELECT * $sql_common ORDER BY st_created_at DESC LIMIT $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 카테고리별 카운트
|
||||
$category_counts = array();
|
||||
$category_sql = "SELECT st_category, COUNT(*) as cnt FROM survey_templates WHERE st_is_public = 1 GROUP BY st_category";
|
||||
$category_result = sql_query($category_sql);
|
||||
while ($category_row = sql_fetch_array($category_result)) {
|
||||
$category_counts[$category_row['st_category']] = $category_row['cnt'];
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.template-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.template-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #AA20FF;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.category-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.category-btn:hover,
|
||||
.category-btn.active {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
border-color: #AA20FF;
|
||||
}
|
||||
|
||||
.template-card-header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.template-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.template-category {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.template-description {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9em;
|
||||
color: #888;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.template-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-template {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-use {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-use:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-preview {
|
||||
background: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-preview:hover {
|
||||
background: #e9ecef;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form select,
|
||||
.search-form input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
font-size: 1.5em;
|
||||
box-shadow: 0 4px 12px rgba(170, 32, 255, 0.3);
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.create-btn:hover {
|
||||
background: #8A1ACC;
|
||||
transform: scale(1.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.template-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.template-stats {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-form .form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="template-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-magic"></i> 템플릿 관리</h1>
|
||||
<p>미리 만들어진 설문 템플릿을 관리하고 새로운 템플릿을 만들 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['고객서비스'] ?? 0); ?></div>
|
||||
<div class="stat-label">고객서비스</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['마케팅'] ?? 0); ?></div>
|
||||
<div class="stat-label">마케팅</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['제품개발'] ?? 0); ?></div>
|
||||
<div class="stat-label">제품개발</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_count); ?></div>
|
||||
<div class="stat-label">전체 템플릿</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<form method="get" class="form-row">
|
||||
<select name="sfl">
|
||||
<option value="st_name"<?php echo $sfl == 'st_name' ? ' selected' : ''; ?>>템플릿명</option>
|
||||
<option value="st_description"<?php echo $sfl == 'st_description' ? ' selected' : ''; ?>>설명</option>
|
||||
<option value="st_category"<?php echo $sfl == 'st_category' ? ' selected' : ''; ?>>카테고리</option>
|
||||
</select>
|
||||
<input type="text" name="stx" value="<?php echo $stx; ?>" placeholder="검색어를 입력하세요">
|
||||
<button type="submit" class="btn-sm btn-primary">
|
||||
<i class="fa fa-search"></i> 검색
|
||||
</button>
|
||||
<?php if ($stx): ?>
|
||||
<a href="?" class="btn-sm btn-warning">
|
||||
<i class="fa fa-times"></i> 초기화
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="category-filter">
|
||||
<a href="?" class="category-btn <?php echo !isset($_GET['category']) ? 'active' : ''; ?>">
|
||||
<i class="fa fa-list"></i> 전체
|
||||
</a>
|
||||
<a href="?category=고객서비스" class="category-btn <?php echo $category == '고객서비스' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-users"></i> 고객서비스 (<?php echo $category_counts['고객서비스'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?category=마케팅" class="category-btn <?php echo $category == '마케팅' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-bullhorn"></i> 마케팅 (<?php echo $category_counts['마케팅'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?category=제품개발" class="category-btn <?php echo $category == '제품개발' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-cogs"></i> 제품개발 (<?php echo $category_counts['제품개발'] ?? 0; ?>)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (sql_num_rows($result) > 0): ?>
|
||||
<div class="template-grid">
|
||||
<?php while ($template = sql_fetch_array($result)): ?>
|
||||
<div class="template-card">
|
||||
<div class="template-card-header">
|
||||
<h3 class="template-title"><?php echo htmlspecialchars($template['st_name']); ?></h3>
|
||||
<span class="template-category"><?php echo htmlspecialchars($template['st_category']); ?></span>
|
||||
</div>
|
||||
|
||||
<p class="template-description"><?php echo htmlspecialchars($template['st_description']); ?></p>
|
||||
|
||||
<div class="template-meta">
|
||||
<span><i class="fa fa-user"></i> <?php echo $template['st_created_by']; ?></span>
|
||||
<span><i class="fa fa-calendar"></i> <?php echo date('Y-m-d', strtotime($template['st_created_at'])); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="template-actions">
|
||||
<a href="survey_form.php?template_id=<?php echo $template['st_id']; ?>" class="btn-template btn-use">
|
||||
<i class="fa fa-plus"></i> 사용하기
|
||||
</a>
|
||||
<a href="template_preview.php?st_id=<?php echo $template['st_id']; ?>" class="btn-template btn-preview">
|
||||
<i class="fa fa-eye"></i> 미리보기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 페이징
|
||||
if ($total_page > 1) {
|
||||
$paging = get_paging(10, $page, $total_page, "?$sql_search&page=");
|
||||
echo '<div style="margin-top: 20px;">' . $paging . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php else: ?>
|
||||
<div style="text-align: center; padding: 80px 20px; color: #666;">
|
||||
<i class="fa fa-magic" style="font-size: 4em; margin-bottom: 20px; display: block; opacity: 0.3;"></i>
|
||||
<h3>등록된 템플릿이 없습니다</h3>
|
||||
<p>새로운 템플릿을 만들어보세요.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="template_form.php" class="create-btn" title="새 템플릿 만들기">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$g5['title'] = '템플릿 관리';
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
|
||||
// 변수 초기화
|
||||
$sfl = isset($_GET['sfl']) ? clean_xss_tags($_GET['sfl']) : 'st_name';
|
||||
$stx = isset($_GET['stx']) ? clean_xss_tags($_GET['stx']) : '';
|
||||
$category = isset($_GET['category']) ? clean_xss_tags($_GET['category']) : '';
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
|
||||
// 검색 조건
|
||||
$where = " WHERE st_is_public = 1 ";
|
||||
$sql_search = "";
|
||||
|
||||
if ($stx) {
|
||||
$where .= " AND $sfl LIKE '%".sql_real_escape_string($stx)."%' ";
|
||||
$sql_search .= "&sfl=$sfl&stx=".urlencode($stx);
|
||||
}
|
||||
|
||||
if ($category) {
|
||||
$where .= " AND st_category = '".sql_real_escape_string($category)."' ";
|
||||
$sql_search .= "&category=".urlencode($category);
|
||||
}
|
||||
|
||||
// 페이징
|
||||
$sql_common = " FROM survey_templates $where ";
|
||||
$sql = " SELECT COUNT(*) as cnt $sql_common ";
|
||||
$row = sql_fetch($sql);
|
||||
$total_count = $row['cnt'];
|
||||
|
||||
$rows = 20;
|
||||
$total_page = ceil($total_count / $rows);
|
||||
if ($page < 1) $page = 1;
|
||||
$from_record = ($page - 1) * $rows;
|
||||
|
||||
$sql = " SELECT * $sql_common ORDER BY st_created_at DESC LIMIT $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 카테고리별 카운트
|
||||
$category_counts = [];
|
||||
$category_sql = "SELECT st_category, COUNT(*) as cnt FROM survey_templates WHERE st_is_public = 1 GROUP BY st_category";
|
||||
$category_result = sql_query($category_sql);
|
||||
while ($category_row = sql_fetch_array($category_result)) {
|
||||
$category_counts[$category_row['st_category']] = $category_row['cnt'];
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- CSS는 template_list.css 파일에서 자동 로드됩니다 -->
|
||||
|
||||
<div class="template-header">
|
||||
<div>
|
||||
<h1><i class="fa fa-magic"></i> 템플릿 관리</h1>
|
||||
<p>미리 만들어진 설문 템플릿을 관리하고 새로운 템플릿을 만들 수 있습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['고객서비스'] ?? 0); ?></div>
|
||||
<div class="stat-label">고객서비스</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['마케팅'] ?? 0); ?></div>
|
||||
<div class="stat-label">마케팅</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($category_counts['제품개발'] ?? 0); ?></div>
|
||||
<div class="stat-label">제품개발</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number"><?php echo number_format($total_count); ?></div>
|
||||
<div class="stat-label">전체 템플릿</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<form method="get" class="form-row">
|
||||
<select name="sfl">
|
||||
<option value="st_name"<?php echo $sfl == 'st_name' ? ' selected' : ''; ?>>템플릿명</option>
|
||||
<option value="st_description"<?php echo $sfl == 'st_description' ? ' selected' : ''; ?>>설명</option>
|
||||
<option value="st_category"<?php echo $sfl == 'st_category' ? ' selected' : ''; ?>>카테고리</option>
|
||||
</select>
|
||||
<input type="text" name="stx" value="<?php echo $stx; ?>" placeholder="검색어를 입력하세요">
|
||||
<button type="submit" class="btn-sm btn-primary">
|
||||
<i class="fa fa-search"></i> 검색
|
||||
</button>
|
||||
<?php if ($stx): ?>
|
||||
<a href="?" class="btn-sm btn-warning">
|
||||
<i class="fa fa-times"></i> 초기화
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="category-filter">
|
||||
<a href="?" class="category-btn <?php echo !isset($_GET['category']) ? 'active' : ''; ?>">
|
||||
<i class="fa fa-list"></i> 전체
|
||||
</a>
|
||||
<a href="?category=고객서비스" class="category-btn <?php echo $category == '고객서비스' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-users"></i> 고객서비스 (<?php echo $category_counts['고객서비스'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?category=마케팅" class="category-btn <?php echo $category == '마케팅' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-bullhorn"></i> 마케팅 (<?php echo $category_counts['마케팅'] ?? 0; ?>)
|
||||
</a>
|
||||
<a href="?category=제품개발" class="category-btn <?php echo $category == '제품개발' ? 'active' : ''; ?>">
|
||||
<i class="fa fa-cogs"></i> 제품개발 (<?php echo $category_counts['제품개발'] ?? 0; ?>)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (sql_num_rows($result) > 0): ?>
|
||||
<div class="template-grid">
|
||||
<?php while ($template = sql_fetch_array($result)): ?>
|
||||
<div class="template-card">
|
||||
<div class="template-card-header">
|
||||
<h3 class="template-title"><?php echo htmlspecialchars($template['st_name']); ?></h3>
|
||||
<span class="template-category"><?php echo htmlspecialchars($template['st_category']); ?></span>
|
||||
</div>
|
||||
|
||||
<p class="template-description"><?php echo htmlspecialchars($template['st_description']); ?></p>
|
||||
|
||||
<div class="template-meta">
|
||||
<span><i class="fa fa-user"></i> <?php echo $template['st_created_by']; ?></span>
|
||||
<span><i class="fa fa-calendar"></i> <?php echo date('Y-m-d', strtotime($template['st_created_at'])); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="template-actions">
|
||||
<a href="survey_form.php?template_id=<?php echo $template['st_id']; ?>" class="btn-template btn-use">
|
||||
<i class="fa fa-plus"></i> 사용하기
|
||||
</a>
|
||||
<a href="template_preview.php?st_id=<?php echo $template['st_id']; ?>" class="btn-template btn-preview">
|
||||
<i class="fa fa-eye"></i> 미리보기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 페이징
|
||||
if ($total_page > 1) {
|
||||
$paging = get_paging(10, $page, $total_page, "?$sql_search&page=");
|
||||
echo '<div class="pagination-wrapper">' . $paging . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa fa-magic"></i>
|
||||
<h3>등록된 템플릿이 없습니다</h3>
|
||||
<p>새로운 템플릿을 만들어보세요.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="template_form.php" class="create-btn" title="새 템플릿 만들기">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
$sub_menu = '710300';
|
||||
include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
$st_id = isset($_GET['st_id']) ? (int)$_GET['st_id'] : 0;
|
||||
|
||||
if (!$st_id) {
|
||||
alert('템플릿을 선택해주세요.', 'template_list.php');
|
||||
}
|
||||
|
||||
// 템플릿 정보 가져오기
|
||||
$template = sql_fetch("SELECT * FROM survey_templates WHERE st_id = '$st_id' AND st_is_public = 1");
|
||||
if (!$template) {
|
||||
alert('존재하지 않는 템플릿입니다.', 'template_list.php');
|
||||
}
|
||||
|
||||
// 템플릿 질문들 가져오기
|
||||
$questions = array();
|
||||
$question_sql = "SELECT * FROM survey_template_questions WHERE st_id = '$st_id' ORDER BY stq_order ASC";
|
||||
$question_result = sql_query($question_sql);
|
||||
while ($question = sql_fetch_array($question_result)) {
|
||||
$questions[] = $question;
|
||||
}
|
||||
|
||||
$g5['title'] = '템플릿 미리보기 - ' . $template['st_name'];
|
||||
include_once(G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.preview-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
background: linear-gradient(135deg, #AA20FF 0%, #8A1ACC 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.preview-description {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
background: #fff;
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.meta-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
margin-bottom: 30px;
|
||||
padding: 25px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #AA20FF;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-right: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.question-title {
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.question-required {
|
||||
color: #dc3545;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.question-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.question-type {
|
||||
display: inline-block;
|
||||
background: #e9ecef;
|
||||
color: #666;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.question-options {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.option-input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.preview-actions {
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
margin: 0 10px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #AA20FF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #8A1ACC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 30px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 3em;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 768px) {
|
||||
.preview-container {
|
||||
margin: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="preview-container">
|
||||
<div class="preview-header">
|
||||
<h1 class="preview-title"><?php echo htmlspecialchars($template['st_name']); ?></h1>
|
||||
<?php if ($template['st_description']): ?>
|
||||
<p class="preview-description"><?php echo nl2br(htmlspecialchars($template['st_description'])); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="preview-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">카테고리</span>
|
||||
<span class="meta-value"><?php echo htmlspecialchars($template['st_category']); ?></span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">작성자</span>
|
||||
<span class="meta-value"><?php echo htmlspecialchars($template['st_created_by']); ?></span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">생성일</span>
|
||||
<span class="meta-value"><?php echo date('Y년 m월 d일', strtotime($template['st_created_at'])); ?></span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">질문 수</span>
|
||||
<span class="meta-value"><?php echo count($questions); ?>개</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-content">
|
||||
<?php if (count($questions) > 0): ?>
|
||||
<?php foreach ($questions as $index => $question): ?>
|
||||
<div class="question-item">
|
||||
<div class="question-header">
|
||||
<div class="question-number"><?php echo $index + 1; ?></div>
|
||||
<div class="question-title">
|
||||
<?php echo htmlspecialchars($question['stq_title']); ?>
|
||||
<?php if ($question['stq_required']): ?>
|
||||
<span class="question-required">*</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$type_names = array(
|
||||
'text' => '단답형',
|
||||
'textarea' => '장문형',
|
||||
'radio' => '객관식 (단일선택)',
|
||||
'checkbox' => '객관식 (다중선택)',
|
||||
'select' => '드롭다운',
|
||||
'rating' => '평점',
|
||||
'date' => '날짜',
|
||||
'email' => '이메일',
|
||||
'number' => '숫자'
|
||||
);
|
||||
?>
|
||||
<div class="question-type">
|
||||
<i class="fa fa-tag"></i> <?php echo $type_names[$question['stq_type']] ?? $question['stq_type']; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($question['stq_description']): ?>
|
||||
<div class="question-description">
|
||||
<?php echo nl2br(htmlspecialchars($question['stq_description'])); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array($question['stq_type'], ['radio', 'checkbox', 'select']) && $question['stq_options']): ?>
|
||||
<div class="question-options">
|
||||
<?php
|
||||
$options = json_decode($question['stq_options'], true);
|
||||
if (is_array($options)):
|
||||
foreach ($options as $option):
|
||||
?>
|
||||
<div class="option-item">
|
||||
<input type="<?php echo $question['stq_type'] == 'checkbox' ? 'checkbox' : 'radio'; ?>"
|
||||
class="option-input" disabled>
|
||||
<span class="option-text"><?php echo htmlspecialchars($option); ?></span>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<?php elseif ($question['stq_type'] == 'rating'): ?>
|
||||
<div class="question-options">
|
||||
<div class="option-item">
|
||||
<?php for ($i = 1; $i <= 5; $i++): ?>
|
||||
<i class="fa fa-star-o" style="color: #ddd; margin-right: 5px;"></i>
|
||||
<?php endfor; ?>
|
||||
<span class="option-text">1-5점 평점</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($question['stq_type'] == 'text'): ?>
|
||||
<div class="question-options">
|
||||
<input type="text" class="form-control" placeholder="답변을 입력하세요" disabled style="background: #fff;">
|
||||
</div>
|
||||
<?php elseif ($question['stq_type'] == 'textarea'): ?>
|
||||
<div class="question-options">
|
||||
<textarea class="form-control" rows="3" placeholder="답변을 입력하세요" disabled style="background: #fff;"></textarea>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
<h3>질문이 없습니다</h3>
|
||||
<p>이 템플릿에는 아직 질문이 등록되지 않았습니다.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="preview-actions">
|
||||
<a href="survey_form.php?template_id=<?php echo $template['st_id']; ?>" class="btn-action btn-primary">
|
||||
<i class="fa fa-plus"></i> 이 템플릿으로 설문 만들기
|
||||
</a>
|
||||
<a href="template_list.php" class="btn-action btn-secondary">
|
||||
<i class="fa fa-list"></i> 템플릿 목록으로
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
?>
|
||||
Reference in New Issue
Block a user