티스토리 뷰

WEB/JavaScript

6주 카테고리 달력

silverline79 2026. 3. 4. 07:47
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>6주 고정형 일정 관리 달력</title>
    <style>
        :root { 
            --primary: #4a90e2; 
            --today-bg: #fff9c4; 
            --border-color: #eee;
        }
        body { font-family: 'Malgun Gothic', sans-serif; display: flex; justify-content: center; padding: 20px; background: #f8f9fa; }
        
        .calendar-container { 
            width: 450px; 
            background: #fff; 
            padding: 20px; 
            border-radius: 15px; 
            box-shadow: 0 8px 30px rgba(0,0,0,0.08); 
        }

        /* 헤더 설정 */
        .calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
        .nav-group { display: flex; align-items: center; gap: 5px; }
        select { padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; }
        button { cursor: pointer; border: none; border-radius: 4px; padding: 5px 10px; background: var(--primary); color: white; }
        button:hover { opacity: 0.8; }

        /* 요일 및 그리드 */
        .weekdays { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; font-weight: bold; padding-bottom: 10px; color: #555; }
        .calendar-grid { 
            display: grid; 
            grid-template-columns: repeat(7, 1fr); 
            gap: 2px; 
            border-top: 1px solid var(--border-color);
        }

        .day { 
            aspect-ratio: 1 / 1; 
            display: flex; 
            flex-direction: column;
            align-items: center; 
            justify-content: center; 
            cursor: pointer; 
            font-size: 14px;
            border-bottom: 1px solid var(--border-color);
            position: relative;
        }
        .day:hover { background: #f0f7ff; }
        .day.today { background: var(--today-bg); font-weight: bold; border-radius: 4px; }
        .day.other-month { color: #ccc; cursor: default; } /* 이전/다음달 날짜 색상 */

        /* 일정 점 표시 */
        .has-event::after {
            content: '';
            position: absolute;
            bottom: 6px;
            width: 5px;
            height: 5px;
            background: #ff4d4d;
            border-radius: 50%;
        }

        /* 하단 일정 리스트 */
        .event-section { margin-top: 20px; border-top: 2px solid #333; padding-top: 15px; min-height: 150px; }
        .event-section h3 { font-size: 16px; margin: 0 0 10px 0; color: #333; }
        .event-item { display: flex; align-items: center; margin-bottom: 8px; list-style: none; }
        .badge { 
            padding: 2px 6px; border-radius: 3px; color: white; font-size: 11px; margin-right: 8px; font-weight: bold; 
        }
        .bg-전제 { background: #ff9800; }
        .bg-지역 { background: #4caf50; }
        .no-event { color: #999; font-size: 14px; list-style: none; }
    </style>
</head>
<body>

<div class="calendar-container">
    <div class="calendar-header">
        <div class="nav-group">
            <button id="prevMonth">&lt;</button>
            <select id="yearSelect"></select>
            <select id="monthSelect"></select>
            <button id="nextMonth">&gt;</button>
        </div>
        <button onclick="goToToday()" style="background: #666;">오늘</button>
    </div>

    <div class="weekdays">
        <div style="color:red">일</div><div>월</div><div>화</div><div>수</div><div>목</div><div>금</div><div style="color:blue">토</div>
    </div>
    
    <div id="calendarGrid" class="calendar-grid"></div>

    <div class="event-section">
        <h3 id="selectedDateTitle">날짜 선택</h3>
        <ul id="eventList" style="padding:0"></ul>
    </div>
</div>

<script>
    // 1. 데이터 설정
    const scheduleData = {
        "2026-03-03": [{category: "전제", content: "프로젝트 킥오프"}, {category: "지역", content: "나주 출장"}],
        "2026-03-15": [{category: "지역", content: "강원지사 미팅"}],
        "2026-04-01": [{category: "전제", content: "만우절 이벤트"}]
    };

    let viewDate = new Date();

    function init() {
        const yearSelect = document.getElementById('yearSelect');
        const monthSelect = document.getElementById('monthSelect');
        const curYear = new Date().getFullYear();
        
        for (let i = curYear - 5; i <= curYear + 5; i++) {
            yearSelect.add(new Option(i + "년", i));
        }
        for (let i = 0; i < 12; i++) {
            monthSelect.add(new Option((i + 1) + "월", i));
        }

        renderCalendar();
        showEvents(formatDate(new Date()));
    }

    function formatDate(date) {
        return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
    }

    // 2. 6주(42일) 고정 렌더링 로직
    function renderCalendar() {
        const grid = document.getElementById('calendarGrid');
        const year = viewDate.getFullYear();
        const month = viewDate.getMonth();

        document.getElementById('yearSelect').value = year;
        document.getElementById('monthSelect').value = month;
        grid.innerHTML = '';

        // 달력의 시작점 계산 (이전 달의 남은 날짜 포함)
        const startOfMonth = new Date(year, month, 1);
        const startDay = startOfMonth.getDay(); // 0(일) ~ 6(토)
        
        // 달력 첫 칸에 들어갈 날짜 계산 (첫날 - 요일수)
        const calendarStartDate = new Date(year, month, 1 - startDay);
        const todayStr = formatDate(new Date());

        // 무조건 42회 반복 (7일 * 6주)
        for (let i = 0; i < 42; i++) {
            const currentLoopDate = new Date(calendarStartDate);
            currentLoopDate.setDate(calendarStartDate.getDate() + i);
            
            const dateStr = formatDate(currentLoopDate);
            const dayDiv = document.createElement('div');
            dayDiv.className = 'day';
            
            // 현재 월이 아닌 날짜 처리
            if (currentLoopDate.getMonth() !== month) {
                dayDiv.classList.add('other-month');
            } else {
                // 현재 월인 경우에만 클릭 및 일정 표시 활성화
                if (dateStr === todayStr) dayDiv.classList.add('today');
                if (scheduleData[dateStr]) dayDiv.classList.add('has-event');
                dayDiv.onclick = () => showEvents(dateStr);
            }

            dayDiv.innerText = currentLoopDate.getDate();
            grid.appendChild(dayDiv);
        }
    }

    function showEvents(dateStr) {
        document.getElementById('selectedDateTitle').innerText = dateStr;
        const listEl = document.getElementById('eventList');
        const events = scheduleData[dateStr] || [];
        
        if (events.length > 0) {
            listEl.innerHTML = events.map(ev => `
                <li class="event-item">
                    <span class="badge bg-${ev.category}">${ev.category}</span>
                    <span>${ev.content}</span>
                </li>
            `).join('');
        } else {
            listEl.innerHTML = `<li class="no-event">표시할 일정이 없습니다.</li>`;
        }
    }

    function goToToday() { viewDate = new Date(); renderCalendar(); showEvents(formatDate(viewDate)); }
    document.getElementById('yearSelect').onchange = (e) => { viewDate.setFullYear(e.target.value); renderCalendar(); };
    document.getElementById('monthSelect').onchange = (e) => { viewDate.setMonth(e.target.value); renderCalendar(); };
    document.getElementById('prevMonth').onclick = () => { viewDate.setMonth(viewDate.getMonth() - 1); renderCalendar(); };
    document.getElementById('nextMonth').onclick = () => { viewDate.setMonth(viewDate.getMonth() + 1); renderCalendar(); };

    init();
</script>
</body>
</html>
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함