고수님 도와주세요. ㅠㅠ
본문
저는 제미나이한테 조직도를 만들어 달라고 부탁했는데 잘 만들어서 디비에 저장까지 되는 구현했는데 문제는 ㅠㅠ 헤더와 푸터가 제대로 못 불러옵니다.
그림보는 바 같이 이렇게 됩니다. 자기 말론 인코딩이 못 불러와서 이렇게 된다고 하더군요 저도 코딩 지식이 기본 밖에 모르는 사람이라 저거 때문에 몇시간째 수정하고 있는데도 안되어서 결국엔 여기까지 와서 질문을 남깁니다. ㅠㅠㅠ
제 코드는
<?php
// PHP 에러 표시 (문제 해결 후 반드시 제거하거나 Off로 설정하세요)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// 그누보드 핵심 파일 로드 시도
// !!!!!!!!!! 이 부분을 강세경님의 실제 _common.php 파일의 절대 경로로 수정해주세요 !!!!!!!!!!!!!
// 예시: $common_path = '/var/services/web/ksk507/_common.php';
// 예시: $common_path = '/volume1/web/그누보드설치폴더/_common.php';
$common_path = '/var/services/web/ksk507/_common.php'; // <-- 이 부분을 강세경님의 실제 경로로 변경하세요!
// 이 아래는 수정할 필요 없습니다.
if (!file_exists($common_path)) {
echo "<h1 style='color:red; text-align:center;'>오류: 그누보드 핵심 파일을 찾을 수 없습니다.</h1>";
echo "<p style='color:red; text-align:center;'>확인된 경로: '".$common_path."'</p>";
echo "<p style='color:red; text-align:center;'>_common.php 파일이 해당 경로에 존재하는지 확인해주세요.</p>";
echo "<p style='color:red; text-align:center;'>'timeline.php' 파일의 코드를 열어 '$common_path = ...' 부분을 실제 _common.php 경로로 수정해야 합니다.</p>";
exit;
}
include_once($common_path);
// MySQL 연결 문자셋을 UTF-8로 강제 설정
if (function_exists('sql_query')) {
sql_query("SET NAMES utf8mb4");
}
// HTML 응답의 문자 인코딩을 UTF-8로 명시적으로 설정
header('Content-Type: text/html; charset=UTF-8');
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");
ini_set('default_charset', 'UTF-8');
// 이 시점부터는 G5_PATH 상수가 정의되어 있으므로 안전하게 사용할 수 있습니다.
$g5['title'] = '강세경님의 연혁 관리'; // 페이지 제목 설정
include_once(G5_PATH.'/head.php'); // 그누보드 헤더 포함 (G5_PATH 사용)
// ----------------------------------------------------
// 데이터 처리 로직 (POST 요청 처리)
// ----------------------------------------------------
// 연혁 추가/수정 처리
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$mode = isset($_POST['mode']) ? $_POST['mode'] : '';
$tl_id = isset($_POST['tl_id']) ? (int)$_POST['tl_id'] : 0;
$tl_year = isset($_POST['timelineYear']) ? sql_real_escape_string(trim($_POST['timelineYear'])) : '';
$tl_month = isset($_POST['timelineMonth']) ? sql_real_escape_string(trim($_POST['timelineMonth'])) : '';
$tl_day = isset($_POST['timelineDay']) ? sql_real_escape_string(trim($_POST['timelineDay'])) : '';
$tl_description = isset($_POST['timelineDescription']) ? sql_real_escape_string(trim($_POST['timelineDescription'])) : '';
// 필수 필드 유효성 검사
if (!$tl_year || !$tl_month || !$tl_day || !$tl_description) {
alert("연도, 월, 일, 내용을 모두 입력해주세요.");
exit;
}
// 모드에 따른 데이터베이스 작업
if ($mode == 'insert') {
$sql = " INSERT INTO g5_timeline SET
tl_year = '{$tl_year}',
tl_month = '{$tl_month}',
tl_day = '{$tl_day}',
tl_description = '{$tl_description}',
tl_datetime = NOW() ";
sql_query($sql);
alert("연혁이 추가되었습니다.", './timeline.php'); // 추가 후 현재 페이지로 리다이렉트
} else if ($mode == 'update' && $tl_id) {
$sql = " UPDATE g5_timeline SET
tl_year = '{$tl_year}',
tl_month = '{$tl_month}',
tl_day = '{$tl_day}',
tl_description = '{$tl_description}'
WHERE tl_id = '{$tl_id}' ";
sql_query($sql);
alert("연혁이 수정되었습니다.", './timeline.php'); // 수정 후 현재 페이지로 리다이렉트
}
}
// 연혁 삭제 처리 (GET 요청)
if (isset($_GET['mode']) && $_GET['mode'] == 'delete') {
$tl_id = isset($_GET['tl_id']) ? (int)$_GET['tl_id'] : 0;
if ($tl_id) {
sql_query(" DELETE FROM g5_timeline WHERE tl_id = '{$tl_id}' ");
alert("연혁이 삭제되었습니다.", './timeline.php'); // 삭제 후 현재 페이지로 리다이렉트
} else {
alert("잘못된 접근입니다.", './timeline.php');
}
}
// ----------------------------------------------------
// HTML 및 JavaScript 출력
// ----------------------------------------------------
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $g5['title']; ?></title>
<style>
/* CSS 코드는 변경 없이 그대로 붙여넣으세요 */
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; margin: 0; padding: 20px; background-color: #f4f7f6; }
.container { max-width: 900px; margin: 0 auto; padding: 20px; background-color: #fff; box-shadow: 0 0 15px rgba(0,0,0,0.05); border-radius: 8px; }
.section-heading { text-align: center; font-size: 2.2em; color: #444; margin-bottom: 50px; font-weight: 600; margin-top: 20px; }
.timeline { position: relative; padding: 0; margin: 0; }
.timeline::before { content: ''; position: absolute; width: 2px; background-color: #ddd; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 1; }
.timeline-item::before { content: ''; position: absolute; width: 14px; height: 14px; border-radius: 50%; background-color: #5bc0de; border: 2px solid #fff; top: 5px; left: 50%; transform: translateX(-50%); z-index: 3; }
.timeline-item { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 60px; position: relative; z-index: 2; width: 100%; }
.timeline-year { flex-basis: 45%; padding-right: 40px; font-size: 1.8em; font-weight: 700; color: #34495e; padding-top: 0px; text-align: right; }
.timeline-content { flex-basis: 45%; padding-left: 40px; text-align: left; }
.timeline-content h3 { font-size: 1.2em; color: #444; margin-top: 0; margin-bottom: 10px; }
.timeline-content ul { list-style: none; padding: 0; margin: 0; }
.timeline-content ul li { margin-bottom: 8px; font-size: 0.95em; color: #555; display: flex; align-items: flex-start; position: relative; padding-right: 120px; }
.timeline-content .event-date-detail { font-weight: bold; color: #5bc0de; flex-shrink: 0; order: 2; text-align: right; min-width: 50px; margin-left: auto; padding-left: 10px; }
.timeline-content .event-description { flex-grow: 1; order: 1; }
.timeline-item .action-buttons { position: absolute; top: 0px; right: 0px; display: flex; gap: 5px; z-index: 10; }
.timeline-item .action-buttons button { padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8em; transition: background-color 0.2s ease; }
.timeline-item .action-buttons .edit-btn { background-color: #f0ad4e; color: white; }
.timeline-item .action-buttons .edit-btn:hover { background-color: #ec971f; }
.timeline-item .action-buttons .delete-btn { background-color: #d9534f; color: white; }
.timeline-item .action-buttons .delete-btn:hover { background-color: #c9302c; }
.timeline-item:nth-child(even) .timeline-year { order: 2; text-align: left; padding-left: 40px; padding-right: 0; }
.timeline-item:nth-child(even) .timeline-content { order: 1; text-align: right; padding-right: 40px; padding-left: 0; }
.timeline-item:nth-child(even) .timeline-content .event-date-detail { order: 1; margin-left: 0; margin-right: auto; text-align: left; padding-right: 10px; padding-left: 0; }
.timeline-content .event-description { order: 2; }
.timeline-item:nth-child(even) .action-buttons { left: 0px; right: auto; }
@media (max-width: 768px) {
.timeline::before { left: 20px; transform: translateX(0); }
.timeline-item { flex-direction: column; align-items: flex-start; margin-left: 20px; }
.timeline-item::before { left: 20px; transform: translateX(-50%); }
.timeline-year, .timeline-content { width: 100%; flex-basis: auto; padding: 0; text-align: left !important; }
.timeline-year { font-size: 1.5em; margin-bottom: 10px; padding-left: 30px; order: 1 !important; }
.timeline-content { padding-left: 30px; order: 2 !important; }
.timeline-item:nth-child(even) .timeline-year, .timeline-item:nth-child(even) .timeline-content { padding-left: 30px; padding-right: 0; }
.timeline-content .event-date-detail { order: 1 !important; margin-left: 0 !important; margin-right: 0 !important; text-align: left !important; padding-left: 0 !important; padding-right: 10px !important; }
.timeline-content .event-description { order: 2 !important; }
.timeline-item .action-buttons { position: static; margin-top: 10px; margin-left: 30px; justify-content: flex-start; }
.timeline-item:nth-child(even) .action-buttons { left: auto; right: auto; }
}
.add-timeline-form { background-color: #f0f8ff; padding: 25px; border-radius: 8px; margin-bottom: 40px; border: 1px solid #e0f2f7; }
.add-timeline-form h3 { color: #34495e; margin-top: 0; margin-bottom: 20px; }
.add-timeline-form label { display: block; margin-bottom: 8px; font-weight: 600; color: #34495e; }
.add-timeline-form input[type="text"], .add-timeline-form textarea, .add-timeline-form select { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; font-size: 1em; box-sizing: border-box; background-color: #fff; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23000000%22%20d%3D%22M287%2C114.1L146.2%2C292.4L5.5%2C114.1H287z%22%2F%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: right 10px center; background-size: 12px; }
.date-select-group { display: flex; gap: 10px; margin-bottom: 15px; }
.date-select-group select { flex: 1; width: auto; margin-bottom: 0; }
.add-timeline-form textarea { resize: vertical; min-height: 60px; }
.add-timeline-form button#addTimelineBtn, .add-timeline-form button#updateTimelineBtn, .add-timeline-form button#cancelEditBtn { background-color: #5bc0de; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: background-color 0.2s ease; margin-right: 10px; }
.add-timeline-form button#addTimelineBtn:hover, .add-timeline-form button#updateTimelineBtn:hover { background-color: #46b8da; }
.add-timeline-form button#cancelEditBtn { background-color: #6c757d; }
.add-timeline-form button#cancelEditBtn:hover { background-color: #5a6268; }
.add-timeline-form button#updateTimelineBtn, .add-timeline-form button#cancelEditBtn { display: none; }
</style>
</head>
<body>
<div class="container">
<h2 class="section-heading">연혁</h2>
<div class="add-timeline-form">
<h3 id="formTitle">새로운 연혁 추가</h3>
<form id="timelineForm" method="post" action="./timeline.php">
<input type="hidden" name="mode" id="formMode" value="insert">
<input type="hidden" name="tl_id" id="formTlId" value="">
<label for="timelineYear">연도:</label>
<select id="timelineYear" name="timelineYear"></select>
<label>날짜:</label>
<div class="date-select-group">
<select id="timelineMonth" name="timelineMonth"></select>
<select id="timelineDay" name="timelineDay"></select>
</div>
<label for="timelineDescription">내용:</label>
<textarea id="timelineDescription" name="timelineDescription" placeholder="연혁 내용을 입력하세요"></textarea>
<button type="submit" id="addTimelineBtn">연혁 추가</button>
<button type="submit" id="updateTimelineBtn">수정 완료</button>
<button type="button" id="cancelEditBtn">수정 취소</button>
</form>
</div>
<div class="timeline" id="timelineContainer">
<?php
// 데이터베이스에서 연혁 데이터 불러오기 (최신순으로 정렬)
$sql = " SELECT * FROM g5_timeline ORDER BY tl_year DESC, tl_month DESC, tl_day DESC ";
$result = sql_query($sql);
$item_index = 0; // 지그재그 클래스를 위한 인덱스
if ($result && function_exists('sql_num_rows') && sql_num_rows($result) > 0) {
while ($row = sql_fetch_array($result)) {
$item_index++;
$class_even = ($item_index % 2 == 0) ? ' timeline-item-even' : '';
?>
<div class="timeline-item<?php echo $class_even; ?>">
<div class="timeline-year"><?php echo $row['tl_year']; ?></div>
<div class="timeline-content">
<ul>
<li>
<span class="event-date-detail"><?php echo $row['tl_month'].'.'.$row['tl_day']; ?></span>
<span class="event-description"><?php echo $row['tl_description']; ?></span>
<div class="action-buttons">
<button class="edit-btn"
data-tl_id="<?php echo $row['tl_id']; ?>"
data-year="<?php echo $row['tl_year']; ?>"
data-month="<?php echo $row['tl_month']; ?>"
data-day="<?php echo $row['tl_day']; ?>"
data-description="<?php echo $row['tl_description']; ?>">수정</button>
<button class="delete-btn" data-tl_id="<?php echo $row['tl_id']; ?>">삭제</button>
</div>
</li>
</ul>
</div>
</div>
<?php
}
} else {
// 데이터가 없거나 쿼리 실패 시에는 아무것도 출력하지 않습니다.
}
?>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const addTimelineBtn = document.getElementById('addTimelineBtn');
const updateTimelineBtn = document.getElementById('updateTimelineBtn');
const cancelEditBtn = document.getElementById('cancelEditBtn');
const formTitle = document.getElementById('formTitle');
const timelineYearSelect = document.getElementById('timelineYear');
const timelineMonthSelect = document.getElementById('timelineMonth');
const timelineDaySelect = document.getElementById('timelineDay');
const timelineDescriptionInput = document.getElementById('timelineDescription');
const timelineContainer = document.getElementById('timelineContainer');
const timelineForm = document.getElementById('timelineForm');
const formMode = document.getElementById('formMode');
const formTlId = document.getElementById('formTlId');
// 연도 셀렉트 박스 동적 생성 함수
function populateYears() {
const currentYear = new Date().getFullYear();
const futureYearsToAdd = 5; // 현재 연도 이후 5년까지 표시
const startYear = currentYear + futureYearsToAdd;
const endYear = 1999; // 1999년까지 표시
timelineYearSelect.innerHTML = ''; // 기존 옵션 제거
const defaultYearOption = document.createElement('option');
defaultYearOption.value = "";
defaultYearOption.textContent = "연도 선택";
defaultYearOption.selected = true;
defaultYearOption.disabled = true;
timelineYearSelect.appendChild(defaultYearOption);
for (let year = startYear; year >= endYear; year--) {
const option = document.createElement('option');
option.value = year;
option.textContent = year;
timelineYearSelect.appendChild(option);
}
}
// 월 셀렉트 박스 동적 생성 함수
function populateMonths() {
timelineMonthSelect.innerHTML = ''; // 기존 옵션 제거
const defaultMonthOption = document.createElement('option');
defaultMonthOption.value = "";
defaultMonthOption.textContent = "월 선택";
defaultMonthOption.selected = true;
defaultMonthOption.disabled = true;
timelineMonthSelect.appendChild(defaultMonthOption);
for (let month = 1; month <= 12; month++) {
const option = document.createElement('option');
option.value = String(month).padStart(2, '0'); // 01, 02 형식으로
option.textContent = month + '월';
timelineMonthSelect.appendChild(option);
}
}
// 일 셀렉트 박스 동적 생성 함수 (월에 따라 일수가 달라지는 로직 포함)
function populateDays(year, month, selectedDay = null) {
timelineDaySelect.innerHTML = ''; // 기존 옵션 제거
const defaultDayOption = document.createElement('option');
defaultDayOption.value = "";
defaultDayOption.textContent = "일 선택";
defaultDayOption.selected = true;
defaultDayOption.disabled = true;
timelineDaySelect.appendChild(defaultDayOption);
let daysInMonth = 31; // 기본값
if (year && month) {
// 선택된 연도와 월을 기반으로 해당 월의 마지막 날짜를 계산
daysInMonth = new Date(year, month, 0).getDate();
} else {
// 월 또는 연도가 선택되지 않은 경우, 임시로 최대 31일까지 표시 (사용자 편의)
daysInMonth = 31;
}
for (let day = 1; day <= daysInMonth; day++) {
const option = document.createElement('option');
option.value = String(day).padStart(2, '0'); // 01, 02 형식으로
option.textContent = day + '일';
if (selectedDay && parseInt(selectedDay) === day) {
option.selected = true;
}
timelineDaySelect.appendChild(option);
}
}
// 월/연도 변경 시 일자 업데이트 이벤트 리스너
timelineMonthSelect.addEventListener('change', function() {
const year = timelineYearSelect.value;
const month = timelineMonthSelect.value;
populateDays(year, month);
});
timelineYearSelect.addEventListener('change', function() {
const year = timelineYearSelect.value;
const month = timelineMonthSelect.value;
populateDays(year, month);
});
// 페이지 로드 시 셀렉트 박스 채우기
populateYears();
populateMonths();
populateDays(); // 초기 로드 시에는 기본값으로 채움
// 폼 초기화 함수
function resetForm() {
timelineYearSelect.value = "";
timelineMonthSelect.value = "";
timelineDaySelect.value = "";
timelineDescriptionInput.value = '';
addTimelineBtn.style.display = 'inline-block'; // '추가' 버튼 보이게
updateTimelineBtn.style.display = 'none'; // '수정 완료' 버튼 숨기게
cancelEditBtn.style.display = 'none'; // '수정 취소' 버튼 숨기게
formTitle.textContent = '새로운 연혁 추가'; // 폼 제목 초기화
formMode.value = 'insert'; // 모드를 'insert'로 재설정
formTlId.value = ''; // tl_id 초기화
populateDays(); // 일자 셀렉트 박스 초기화 (월,일 선택 안된 상태)
}
// "수정" 및 "삭제" 버튼 클릭 이벤트 (이벤트 위임 사용)
timelineContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('edit-btn')) {
const button = event.target;
const tl_id = button.dataset.tl_id;
const year = button.dataset.year;
const month = button.dataset.month;
const day = button.dataset.day;
const description = button.dataset.description; // 이제 htmlspecialchars() 제거된 순수 텍스트
// 폼 제목 변경
formTitle.textContent = '연혁 수정';
// '추가' 버튼 숨기고 '수정 완료', '수정 취소' 버튼 표시
addTimelineBtn.style.display = 'none';
updateTimelineBtn.style.display = 'inline-block';
cancelEditBtn.style.display = 'inline-block';
// 폼 필드에 값 채우기
timelineYearSelect.value = year;
populateDays(year, month, day); // 해당 연월에 맞는 일자 목록 생성 후 선택된 일 설정
timelineMonthSelect.value = month; // 선택된 월 설정
// timelineDaySelect.value는 populateDays 함수에서 처리됨
timelineDescriptionInput.value = description;
// 폼의 hidden input에 값 설정
formMode.value = 'update';
formTlId.value = tl_id;
// 폼으로 스크롤 이동
window.scrollTo({
top: 0,
behavior: 'smooth'
});
} else if (event.target.classList.contains('delete-btn')) {
const button = event.target;
const tl_id = button.dataset.tl_id;
if (confirm('정말로 이 연혁 항목을 삭제하시겠습니까?')) {
// 삭제는 GET 요청으로 처리 (페이지 새로고침)
window.location.href = './timeline.php?mode=delete&tl_id=' + tl_id;
}
}
});
// "연혁 추가" 버튼 클릭 시 유효성 검사 및 폼 제출
addTimelineBtn.addEventListener('click', function(event) {
if (!timelineYearSelect.value || !timelineMonthSelect.value || !timelineDaySelect.value || !timelineDescriptionInput.value.trim()) {
alert('연도, 월, 일, 내용을 모두 입력해주세요.');
event.preventDefault(); // 폼 제출 방지
return false;
}
formMode.value = 'insert'; // 명시적으로 insert 모드 설정
// 폼은 type="submit"이므로 자동으로 제출됩니다.
});
// "수정 완료" 버튼 클릭 시 유효성 검사 및 폼 제출
updateTimelineBtn.addEventListener('click', function(event) {
if (!timelineYearSelect.value || !timelineMonthSelect.value || !timelineDaySelect.value || !timelineDescriptionInput.value.trim()) {
alert('연도, 월, 일, 내용을 모두 입력해주세요.');
alert("연도, 월, 일, 내용을 모두 입력해주세요.");
event.preventDefault(); // 폼 제출 방지
return false;
}
formMode.value = 'update'; // 명시적으로 update 모드 설정
// 폼은 type="submit"이므로 자동으로 제출됩니다.
});
// "수정 취소" 버튼 클릭 이벤트
cancelEditBtn.addEventListener('click', function() {
resetForm(); // 폼 초기화
});
// PHP에서 출력된 항목의 CSS 클래스를 조정 (PHP가 데이터베이스 정렬을 하므로 JavaScript에서는 필요 없을 수 있음)
function updateTimelineItemClasses() {
const items = timelineContainer.querySelectorAll('.timeline-item');
items.forEach((item, index) => {
item.classList.remove('timeline-item-even');
if ((index + 1) % 2 === 0) { // 짝수 번째 항목에 클래스 추가
item.classList.add('timeline-item-even');
}
});
}
// 페이지 로드 시 한 번 실행 (PHP에서 이미 클래스를 할당하지만, 혹시 동적으로 추가/삭제될 경우 대비)
updateTimelineItemClasses();
});
</script>
</body>
</html>
답변 3
확실친 않지만, 왠지 느낌이 tl_description 필드값에 쌍따옴표(") 가 들어가서 html 구조가 무너졌을수 있을듯한 코드네요.
만약 위 경우가 맞다면, 아래 해결책입니다.
- g5_timeline 테이블에서 tl_description 값에 쌍따옴표가 있다면 제거(추천) 또는 \" 로 치환
- addslashes 사용 : <?php echo $row['tl_description']; ?> 를 <?php echo addslashes($row['tl_description']); ?> 로 변경 (2군데 보입니다)
해결안되시면 내아이디 클릭해서 sns 주세요 도와드릴테닌깐요
그누보드 커스텀 페이지에서, 헤더/푸터 불러오는 코드 형태는 다음과 같습니다. https://sir.kr/qa/495462
<?php
include_once('../common.php');
include_once(G5_PATH.'/head.php');
?>
<?php
include_once(G5_PATH.'/tail.php');
?>
-------------------
<?php
include_once('./_common.php');
// 데이터 처리
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$mode = $_POST['mode'] ?? '';
$tl_id = (int)($_POST['tl_id'] ?? 0);
$tl_year = sql_real_escape_string(trim($_POST['timelineYear'] ?? ''));
$tl_month = sql_real_escape_string(trim($_POST['timelineMonth'] ?? ''));
$tl_day = sql_real_escape_string(trim($_POST['timelineDay'] ?? ''));
$tl_description = sql_real_escape_string(trim($_POST['timelineDescription'] ?? ''));
if (!$tl_year || !$tl_month || !$tl_day || !$tl_description) {
alert("연도, 월, 일, 내용을 모두 입력해주세요.");
exit;
}
if ($mode == 'insert') {
sql_query("INSERT INTO g5_timeline SET tl_year='{$tl_year}', tl_month='{$tl_month}', tl_day='{$tl_day}', tl_description='{$tl_description}', tl_datetime=NOW()");
alert("연혁이 추가되었습니다.", './timeline.php');
} elseif ($mode == 'update' && $tl_id) {
sql_query("UPDATE g5_timeline SET tl_year='{$tl_year}', tl_month='{$tl_month}', tl_day='{$tl_day}', tl_description='{$tl_description}' WHERE tl_id='{$tl_id}'");
alert("연혁이 수정되었습니다.", './timeline.php');
}
}
// 삭제 처리
if (($_GET['mode'] ?? '') == 'delete') {
$tl_id = (int)($_GET['tl_id'] ?? 0);
if ($tl_id) {
sql_query("DELETE FROM g5_timeline WHERE tl_id='{$tl_id}'");
alert("연혁이 삭제되었습니다.", './timeline.php');
}
}
$g5['title'] = '연혁 관리';
include_once(G5_PATH.'/head.php');
?>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background: #f4f7f6; }
.container { max-width: 900px; margin: 0 auto; padding: 20px; background: #fff; box-shadow: 0 0 15px rgba(0,0,0,0.05); border-radius: 8px; }
.section-heading { text-align: center; font-size: 2.2em; color: #444; margin: 20px 0 50px; font-weight: 600; }
/* Timeline */
.timeline { position: relative; padding: 0; margin: 0; }
.timeline::before { content: ''; position: absolute; width: 2px; background: #ddd; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 1; }
.timeline-item { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 60px; position: relative; z-index: 2; }
.timeline-item::before { content: ''; position: absolute; width: 14px; height: 14px; border-radius: 50%; background: #5bc0de; border: 2px solid #fff; top: 5px; left: 50%; transform: translateX(-50%); z-index: 3; }
.timeline-year { flex-basis: 45%; padding-right: 40px; font-size: 1.8em; font-weight: 700; color: #34495e; text-align: right; }
.timeline-content { flex-basis: 45%; padding-left: 40px; }
.timeline-content ul { list-style: none; padding: 0; margin: 0; }
.timeline-content li { margin-bottom: 8px; font-size: 0.95em; color: #555; display: flex; align-items: flex-start; position: relative; padding-right: 120px; }
.event-date-detail { font-weight: bold; color: #5bc0de; flex-shrink: 0; order: 2; text-align: right; min-width: 50px; margin-left: auto; padding-left: 10px; }
.event-description { flex-grow: 1; order: 1; }
.action-buttons { position: absolute; top: 0; right: 0; display: flex; gap: 5px; z-index: 10; }
.action-buttons button { padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8em; transition: background-color 0.2s; }
.edit-btn { background: #f0ad4e; color: white; }
.edit-btn:hover { background: #ec971f; }
.delete-btn { background: #d9534f; color: white; }
.delete-btn:hover { background: #c9302c; }
/* Alternating layout */
.timeline-item:nth-child(even) .timeline-year { order: 2; text-align: left; padding-left: 40px; padding-right: 0; }
.timeline-item:nth-child(even) .timeline-content { order: 1; text-align: right; padding-right: 40px; padding-left: 0; }
.timeline-item:nth-child(even) .event-date-detail { order: 1; margin-left: 0; margin-right: auto; text-align: left; padding-right: 10px; padding-left: 0; }
.timeline-item:nth-child(even) .action-buttons { left: 0; right: auto; }
/* Form */
.form { background: #f0f8ff; padding: 25px; border-radius: 8px; margin-bottom: 40px; border: 1px solid #e0f2f7; }
.form h3 { color: #34495e; margin: 0 0 20px; }
.form label { display: block; margin-bottom: 8px; font-weight: 600; color: #34495e; }
.form input, .form textarea, .form select { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; font-size: 1em; box-sizing: border-box; }
.date-group { display: flex; gap: 10px; margin-bottom: 15px; }
.date-group select { flex: 1; width: auto; margin-bottom: 0; }
.form textarea { resize: vertical; min-height: 60px; }
.form button { background: #5bc0de; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: background-color 0.2s; margin-right: 10px; }
.form button:hover { background: #46b8da; }
.form .cancel { background: #6c757d; }
.form .cancel:hover { background: #5a6268; }
.form .update, .form .cancel { display: none; }
/* Mobile */
@media (max-width: 768px) {
.timeline::before { left: 20px; transform: translateX(0); }
.timeline-item { flex-direction: column; align-items: flex-start; margin-left: 20px; }
.timeline-item::before { left: 20px; transform: translateX(-50%); }
.timeline-year, .timeline-content { width: 100%; flex-basis: auto; padding: 0; text-align: left !important; }
.timeline-year { font-size: 1.5em; margin-bottom: 10px; padding-left: 30px; order: 1 !important; }
.timeline-content { padding-left: 30px; order: 2 !important; }
.timeline-item:nth-child(even) .timeline-year, .timeline-item:nth-child(even) .timeline-content { padding-left: 30px; padding-right: 0; }
.event-date-detail { order: 1 !important; margin: 0 !important; text-align: left !important; padding: 0 10px 0 0 !important; }
.event-description { order: 2 !important; }
.action-buttons { position: static; margin-top: 10px; margin-left: 30px; justify-content: flex-start; }
.timeline-item:nth-child(even) .action-buttons { left: auto; right: auto; }
}
</style>
<div class="container">
<h2 class="section-heading">연혁</h2>
<div class="form">
<h3 id="formTitle">새로운 연혁 추가</h3>
<form method="post">
<input type="hidden" name="mode" id="mode" value="insert">
<input type="hidden" name="tl_id" id="tl_id">
<label>연도:</label>
<select id="year" name="timelineYear"></select>
<label>날짜:</label>
<div class="date-group">
<select id="month" name="timelineMonth"></select>
<select id="day" name="timelineDay"></select>
</div>
<label>내용:</label>
<textarea id="description" name="timelineDescription" placeholder="연혁 내용을 입력하세요"></textarea>
<button type="submit" id="add">연혁 추가</button>
<button type="submit" id="update" class="update">수정 완료</button>
<button type="button" id="cancel" class="cancel">수정 취소</button>
</form>
</div>
<div class="timeline">
<?php
$result = sql_query("SELECT * FROM g5_timeline ORDER BY tl_year DESC, tl_month DESC, tl_day DESC");
$index = 0;
while ($row = sql_fetch_array($result)) {
$index++;
?>
<div class="timeline-item">
<div class="timeline-year"><?= $row['tl_year'] ?></div>
<div class="timeline-content">
<ul>
<li>
<span class="event-date-detail"><?= $row['tl_month'].'.'.$row['tl_day'] ?></span>
<span class="event-description"><?= $row['tl_description'] ?></span>
<div class="action-buttons">
<button class="edit-btn" data-id="<?= $row['tl_id'] ?>" data-year="<?= $row['tl_year'] ?>" data-month="<?= $row['tl_month'] ?>" data-day="<?= $row['tl_day'] ?>" data-desc="<?= $row['tl_description'] ?>">수정</button>
<button class="delete-btn" data-id="<?= $row['tl_id'] ?>">삭제</button>
</div>
</li>
</ul>
</div>
</div>
<?php } ?>
</div>
</div>
<script>
$(function() {
var $year = $('#year'), $month = $('#month'), $day = $('#day'), $desc = $('#description');
var $add = $('#add'), $update = $('#update'), $cancel = $('#cancel'), $title = $('#formTitle');
var $mode = $('#mode'), $id = $('#tl_id');
// 옵션 생성
function populateSelect($el, start, end, prefix, selected) {
$el.empty().append('<option value="" disabled selected>' + prefix + ' 선택</option>');
for (var i = start; i <= end; i++) {
var val = String(i).padStart(2, '0');
var text = i + (prefix === '연도' ? '' : prefix);
var $opt = $('<option>').val(prefix === '연도' ? i : val).text(text);
if (selected == i) $opt.prop('selected', true);
$el.append($opt);
}
}
function populateDays(year, month, selected) {
var days = month && year ? new Date(year, month, 0).getDate() : 31;
populateSelect($day, 1, days, '일', selected);
}
function resetForm() {
$year.val(''); $month.val(''); $day.val(''); $desc.val('');
$add.show(); $update.hide(); $cancel.hide();
$title.text('새로운 연혁 추가');
$mode.val('insert'); $id.val('');
populateDays();
}
// 초기화
populateSelect($year, 1999, new Date().getFullYear() + 5, '연도');
populateSelect($month, 1, 12, '월');
populateDays();
$year.add($month).on('change', function() {
populateDays($year.val(), $month.val());
});
// 이벤트
$('.timeline').on('click', '.edit-btn', function() {
var $btn = $(this);
$year.val($btn.data('year'));
populateDays($btn.data('year'), $btn.data('month'), $btn.data('day'));
$month.val($btn.data('month'));
$desc.val($btn.data('desc'));
$add.hide(); $update.show(); $cancel.show();
$title.text('연혁 수정');
$mode.val('update'); $id.val($btn.data('id'));
$('html, body').animate({scrollTop: 0}, 500);
});
$('.timeline').on('click', '.delete-btn', function() {
if (confirm('정말로 삭제하시겠습니까?')) {
location.href = '?mode=delete&tl_id=' + $(this).data('id');
}
});
$cancel.on('click', resetForm);
$add.add($update).on('click', function(e) {
if (!$year.val() || !$month.val() || !$day.val() || !$.trim($desc.val())) {
alert('모든 항목을 입력해주세요.');
e.preventDefault();
}
});
});
</script>
<?php include_once(G5_PATH.'/tail.php'); ?>