2

2026. 5. 3. 19:57ใ†์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

<!DOCTYPE html>
<html lang="ko">
<head>
 <meta charset="UTF-8">
 <title></title>
 <style>
  body { font-family: sans-serif; padding: 10px; background-color: #f4f4f9; }
  .controls { margin-bottom: 15px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .controls select, .btn { padding: 5px 12px; border-radius: 4px; border: 1px solid #ccc; cursor: pointer; font-size: 13px; }
  .btn-save { background-color: #4CAF50; color: white; border: none; }
  .btn-load { background-color: #2196F3; color: white; border: none; }
  
  /* ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์Šคํƒ€์ผ */
  #saveMessage { font-size: 12px; color: #4CAF50; font-weight: bold; opacity: 0; transition: opacity 0.3s; }
  
  .table-container { overflow-x: auto; background: white; padding: 10px; border-radius: 8px; box-shadow: 0 1px 5px rgba(0,0,0,0.1); }
  table { border-collapse: collapse; width: 100%; min-width: 1400px; border: 1px solid #ccc}
  th, td { border: 1px solid black; text-align: center; font-size: 11px; height: 35px; }
  th { background-color: #eee; position: sticky; top: 0; z-index: 2; }
  .group-cell { background-color: #f9f9f9; font-weight: bold; position: sticky; left: 0; z-index: 3; width: 80px; }
  .schedule-select { width: 100%; height: 100%; border: none; outline: none; text-align-last: center; cursor: pointer; appearance: none; background: transparent; }
  td { padding: 0; } 
  .s-day { background-color: #c7d32fb8 !important; font-weight: bold; }
  .g-day { background-color: #6e9dcb !important; font-weight: bold; }
  .d-day { background-color: #388e3c !important; font-weight: bold; }
  .off-day { background-color: #d9195985 !important; }
  .leave-day { background-color: #2196F3 !important; color: white !important; font-weight: bold; }
  .unpaid-day { background-color: #795548 !important; color: white !important; font-weight: bold; }
  .holiday-text { color: red !important; font-weight: bold; }
  .holiday-header { color: red !important; background-color: #ffebee !important; }
  .holiday-cell { background-color: #ffebee; }
 </style>
</head>
<body>

 <div class="controls">
  <select id="yearSelect"></select>
  <select id="monthSelect"></select>
  <button class="btn btn-save" onclick="saveToFile()">๐Ÿ’พ </button>
  <button class="btn btn-load" onclick="document.getElementById('fileInput').click()">๐Ÿ“‚ </button>
  <span id="saveMessage">์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!</span>
  <input type="file" id="fileInput" style="display:none" onchange="loadFromFile(event)">
 </div>

 <div class="table-container">
  <table id="scheduleTable">
   <thead>
    <tr id="headerRow">
     <th class="group-cell"> / ๋‚ ์งœ</th>
    </tr>
   </thead>
   <tbody id="scheduleBody"></tbody>
  </table>
 </div>

 <script>
  const holidays = ["2026-01-01", "2026-02-16", "2026-02-17", "2026-02-18", "2026-03-01", "2026-05-01", "2026-05-05", "2026-05-24", "2026-06-06", "2026-07-17", "2026-08-15", "2026-09-24", "2026-09-25", "2026-09-26", "2026-10-03", "2026-10-09", "2026-12-25"];
  const members = [
   { name: '-1', group: 'A' }, { name: '-2', group: 'A' },
   { name: '-1', group: 'B' }, { name: '-2', group: 'B' },
   { name: '-1', group: 'C' }, { name: '-2', group: 'C' },
   { name: '-1', group: 'D' }, { name: '-2', group: 'D' }
  ];

  const basePattern = ['a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a'];
  const baseStartIndices = { 'A': 19, 'b': 4, 'C': 9, 'D': 14 };
  const referenceDate = new Date(2026, 0, 1);
  const workOptions = ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'];

  let currentYear = new Date().getFullYear();
  let currentMonth = new Date().getMonth();

  function setupControls() {
   const ySelect = document.getElementById('yearSelect');
   const mSelect = document.getElementById('monthSelect');
   for (let i = 2025; i <= 2030; i++) {
    const opt = document.createElement('option');
    opt.value = i; opt.innerText = i + "๋…„";
    if (i === currentYear) opt.selected = true;
    ySelect.appendChild(opt);
   }
   for (let i = 0; i < 12; i++) {
    const opt = document.createElement('option');
    opt.value = i; opt.innerText = (i + 1) + "์›”";
    if (i === currentMonth) opt.selected = true;
    mSelect.appendChild(opt);
   }
   ySelect.onchange = () => { currentYear = parseInt(ySelect.value); initTable(); };
   mSelect.onchange = () => { currentMonth = parseInt(mSelect.value); initTable(); };
  }

  function isHoliday(year, month, day) {
   const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
   const date = new Date(year, month, day);
   return holidays.includes(dateStr) || date.getDay() === 0;
  }

  function updateCellColor(td, value, isRedDay) {
   td.classList.remove('s-day', 'g-day', 'd-day', 'off-day', 'leave-day', 'unpaid-day', 'holiday-text');
   if (value.includes('S')) td.classList.add('s-day');
   else if (value.includes('G')) td.classList.add('g-day');
   else if (value.includes('D')) td.classList.add('d-day');
   else if (value === 'ํœด') td.classList.add('off-day');
   else if (value === '์—ฐ') td.classList.add('leave-day');
   else if (value === '๋ฌด') td.classList.add('unpaid-day');
   if(isRedDay) td.classList.add('holiday-text');
  }

  function showSaveMessage() {
   const msg = document.getElementById('saveMessage');
   msg.style.opacity = '1';
   setTimeout(() => { msg.style.opacity = '0'; }, 2000);
  }

  function saveToFile() {
   const data = {};
   const selects = document.querySelectorAll('.schedule-select');
   selects.forEach((sel, idx) => { data[idx] = sel.value; });

   const blob = new Blob([JSON.stringify({year: currentYear, month: currentMonth, data})], {type: "application/json"});
   const url = URL.createObjectURL(blob);
   const a = document.createElement('a');
   a.href = url; 
   a.download = `๋‹ฌ๋ ฅ_${currentYear}_${currentMonth}.json`;
   a.click();
   
   showSaveMessage(); 
  }


  window.addEventListener('keydown', function(e) {
   if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault(); // ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ์ €์žฅ์ฐฝ ๋ฐฉ์ง€
    saveToFile();
   }
  });

  function loadFromFile(event) {
   const file = event.target.files[0];
   if (!file) return;
   const reader = new FileReader();
   reader.onload = function(e) {
    const imported = JSON.parse(e.target.result);
    currentYear = imported.year;
    currentMonth = imported.month;
    document.getElementById('yearSelect').value = currentYear;
    document.getElementById('monthSelect').value = currentMonth;
    initTable(); 

    const selects = document.querySelectorAll('.schedule-select');
    Object.keys(imported.data).forEach(idx => {
     if(selects[idx]) {
      selects[idx].value = imported.data[idx];
      updateCellColor(selects[idx].parentElement, selects[idx].value, isHoliday(currentYear, currentMonth, parseInt(selects[idx].dataset.day)));
     }
    });
   };
   reader.readAsText(file);
  }

  function initTable() {
   const headerRow = document.getElementById('headerRow');
   const tbody = document.getElementById('scheduleBody');
   headerRow.innerHTML = '<th class="group-cell"> / ๋‚ ์งœ</th>';
   tbody.innerHTML = '';
   const lastDay = new Date(currentYear, currentMonth + 1, 0).getDate();

   for (let i = 1; i <= lastDay; i++) {
    const th = document.createElement('th'); th.innerText = i;
    if (isHoliday(currentYear, currentMonth, i)) th.classList.add('holiday-header');
    headerRow.appendChild(th);
   }

   members.forEach(member => {
    const tr = document.createElement('tr');
    const nameTd = document.createElement('td'); nameTd.className = 'group-cell'; nameTd.innerText = member.name;
    tr.appendChild(nameTd);

    for (let d = 1; d <= lastDay; d++) {
     const targetDate = new Date(currentYear, currentMonth, d);
     const diffDays = Math.floor((targetDate - referenceDate) / (1000 * 60 * 60 * 24));
     let patternIdx = (baseStartIndices[member.group] + diffDays) % basePattern.length;
     const currentWork = basePattern[patternIdx < 0 ? patternIdx + basePattern.length : patternIdx];
     
     const td = document.createElement('td');
     const redDay = isHoliday(currentYear, currentMonth, d);
     if (redDay) td.classList.add('holiday-cell');

     const select = document.createElement('select');
     select.className = 'schedule-select';
     select.dataset.day = d; 
     
     workOptions.forEach(opt => {
      const option = document.createElement('option');
      option.value = opt; option.innerText = opt;
      if(opt === currentWork) option.selected = true;
      select.appendChild(option);
     });

     select.onchange = () => updateCellColor(td, select.value, redDay);
     updateCellColor(td, currentWork, redDay);
     td.appendChild(select);
     tr.appendChild(td);
    }
    tbody.appendChild(tr);
   });
  }

  setupControls();
  initTable();
 </script>
</body>
</html>