Files
2026-06-11 18:47:38 +09:00

392 lines
13 KiB
PHP

<?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');
?>