고수님 도와주세요. ㅠㅠ

고수님 도와주세요. ㅠㅠ

QA

고수님 도와주세요. ㅠㅠ

본문

저는 제미나이한테 조직도를 만들어 달라고 부탁했는데 잘 만들어서 디비에 저장까지 되는 구현했는데 문제는 ㅠㅠ 헤더와 푸터가 제대로 못 불러옵니다. 

그림보는 바 같이 이렇게 됩니다. 자기 말론 인코딩이 못 불러와서 이렇게 된다고 하더군요 저도 코딩 지식이 기본 밖에 모르는 사람이라 저거 때문에 몇시간째 수정하고 있는데도 안되어서 결국엔 여기까지 와서 질문을 남깁니다. ㅠㅠㅠ

3067840266_1752458343.3524.png

 

제 코드는 

<?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군데 보입니다)

그누보드 커스텀 페이지에서, 헤더/푸터 불러오는 코드 형태는 다음과 같습니다. 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'); ?>
답변을 작성하시기 전에 로그인 해주세요.
전체 129,103 | RSS
QA 내용 검색

회원로그인

(주)에스아이알소프트 / 대표:홍석명 / (06211) 서울특별시 강남구 역삼동 707-34 한신인터밸리24 서관 1402호 / E-Mail: admin@sir.kr
사업자등록번호: 217-81-36347 / 통신판매업신고번호:2014-서울강남-02098호 / 개인정보보호책임자:김민섭(minsup@sir.kr)
© SIRSOFT