자주사용하는 것들 - 시간선택기 UI(날짜/시/분)

2026.01.28 13:58
/* timepick 컴포넌트 범위만 */
.timepick, .timepick *{
  font-size:12px !important;
  line-height:1.4 !important;
  box-sizing:border-box;
}

/* 공통 row/item 구조 */
.timepick .row{
  display:flex;
  gap:8px;
  flex-wrap:wrap;
  align-items:center;
  margin-bottom:8px;
}
.timepick .item{
  display:flex;
  align-items:center;
  gap:6px;
  flex:1 1 220px;
  min-width:0;
}
.timepick label{
  color:#666;
  white-space:nowrap;
  min-width:72px;
  font-weight:400;
}
.timepick select,
.timepick input{
  height:30px !important;
  padding:2px 8px !important;
  min-width:0;
  width:100%;
  border:1px solid rgba(0,0,0,.18);
  border-radius:8px;
  background:#fff;
}

/* 예약박스 */
.timepick .tp-reserve{
  border:1px dashed rgba(0,0,0,.18);
  border-radius:12px;
  padding:10px 10px;
  background:rgba(0,0,0,.01);
  margin-bottom:8px;
}

/* 선택(placeholder)처럼 보이게 */
.timepick select option[value=""]{
  color:#999;
}
#reserveBox{
  border:1px dashed rgba(0,0,0,.18);
  border-radius:12px;
  padding:10px 10px;
  background:rgba(0,0,0,.01);
  margin-bottom:8px;
}

<div class="timepick" data-timepick
     data-default="NOW"
     data-hours="9-22"
     data-minutes="00,30">
  <!-- 즉시/예약 -->
  <div class="row">
    <div class="item">
      <label>시간선택</label>
      <select class="tp-mode">
        <option value="NOW">즉시</option>
        <option value="RESERVE">예약</option>
      </select>
    </div>
  </div>

  <!-- 예약 박스 -->
  <div class="tp-reserve" style="display:none;">
    <div class="row">
      <div class="item">
        <label>예약날짜</label>
        <input type="date" class="tp-date" value="">
      </div>
      <div class="item">
        <label>예약시간</label>
        <select class="tp-hour"></select>
      </div>
      <div class="item">
        <label>예약분</label>
        <select class="tp-min"></select>
      </div>
    </div>
  </div>

  <!-- ✅ 최종 결과값 -->
  <input type="hidden" class="tp-value" name="ReserveAt" value="">
</div>
(function(){
  function pad2(n){ return String(n).padStart(2,'0'); }

  function parseRange(s, fallbackMin, fallbackMax){
    const m = String(s||'').match(/^\s*(\d+)\s*-\s*(\d+)\s*$/);
    if(!m) return {min:fallbackMin, max:fallbackMax};
    let a = parseInt(m[1],10), b = parseInt(m[2],10);
    if(!isFinite(a)||!isFinite(b)) return {min:fallbackMin, max:fallbackMax};
    if(a>b){ const t=a; a=b; b=t; }
    return {min:a, max:b};
  }

  function parseList(s, fallbackArr){
    const arr = String(s||'').split(',').map(x=>x.trim()).filter(Boolean);
    return arr.length ? arr : fallbackArr;
  }

  function buildReserveAt(dateVal, hh, mm){
    if(!dateVal || !hh || !mm) return '';
    // YYYY-MM-DD HH:MM:00
    return `${dateVal} ${hh}:${mm}:00`;
  }

  function initTimepick(root){
    if(!root || root.dataset.bound === '1') return;
    root.dataset.bound = '1';

    const modeEl = root.querySelector('.tp-mode');
    const boxEl  = root.querySelector('.tp-reserve');
    const dateEl = root.querySelector('.tp-date');
    const hourEl = root.querySelector('.tp-hour');
    const minEl  = root.querySelector('.tp-min');
    const valEl  = root.querySelector('.tp-value');

    if(!modeEl || !boxEl || !dateEl || !hourEl || !minEl || !valEl) return;

    // 옵션
    const defMode = root.dataset.default || 'NOW';
    const hours   = parseRange(root.dataset.hours, 9, 22);
    const mins    = parseList(root.dataset.minutes, ['00','30']);

    // hour 옵션 채우기
    hourEl.innerHTML = '';
    const opt0 = document.createElement('option');
    opt0.value = ''; opt0.textContent = '선택';
    hourEl.appendChild(opt0);
    for(let h=hours.min; h<=hours.max; h++){
      const o=document.createElement('option');
      o.value = pad2(h);
      o.textContent = pad2(h) + '시';
      hourEl.appendChild(o);
    }

    // min 옵션 채우기
    minEl.innerHTML = '';
    const optM0 = document.createElement('option');
    optM0.value = ''; optM0.textContent = '선택';
    minEl.appendChild(optM0);
    mins.forEach(m=>{
      const o=document.createElement('option');
      o.value = m;
      o.textContent = m;
      minEl.appendChild(o);
    });

    function sync(){
      const mode = modeEl.value || 'NOW';
      const isReserve = (mode === 'RESERVE');
      boxEl.style.display = isReserve ? 'block' : 'none';

      if(!isReserve){
        valEl.value = ''; // 즉시면 ReserveAt 비움
      }else{
        valEl.value = buildReserveAt(dateEl.value, hourEl.value, minEl.value);
      }

      // 필요하면 외부에서 감지 가능하게 이벤트 발사
      root.dispatchEvent(new CustomEvent('timepick:change', {
        bubbles:true,
        detail:{ mode, reserveAt: valEl.value }
      }));
    }

    // 기본값 세팅
    modeEl.value = (defMode === 'RESERVE') ? 'RESERVE' : 'NOW';
    sync();

    // 이벤트
    modeEl.addEventListener('change', sync);
    dateEl.addEventListener('input', sync);
    hourEl.addEventListener('change', sync);
    minEl.addEventListener('change', sync);
  }

  document.querySelectorAll('[data-timepick]').forEach(initTimepick);
})();
const timepick = root.querySelector('[data-timepick]');
const reserveAt = timepick?.querySelector('.tp-value')?.value || '';
const sendType = timepick?.querySelector('.tp-mode')?.value || 'NOW';

const mode = timepick?.querySelector('.tp-mode')?.value || 'NOW';
const reserveAt = timepick?.querySelector('.tp-value')?.value || '';

if(mode === 'RESERVE' && reserveAt === ''){
  // 비활성
}



댓글 0

아직 댓글이 없습니다.