/* 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 === ''){
// 비활성
}