213 lines
5.2 KiB
Vue
213 lines
5.2 KiB
Vue
<template>
|
|
<div class="control-panel">
|
|
<div class="control-row">
|
|
<span class="control-label">조회 간격:</span>
|
|
<div class="interval-buttons">
|
|
<button
|
|
v-for="min in [1, 2, 3, 4, 5]"
|
|
:key="min"
|
|
:class="['interval-btn', { active: autoRefresh && interval === min }]"
|
|
@click="selectInterval(min)"
|
|
>
|
|
{{ min }}분
|
|
</button>
|
|
</div>
|
|
|
|
<div class="control-divider"></div>
|
|
|
|
<button
|
|
:class="['auto-refresh-btn', { active: autoRefresh }]"
|
|
@click="toggleAutoRefresh"
|
|
>
|
|
{{ autoRefresh ? '⏸ 자동갱신 ON' : '▶ 자동갱신 OFF' }}
|
|
</button>
|
|
|
|
<div class="control-divider"></div>
|
|
|
|
<span class="control-label">특정시간:</span>
|
|
<input
|
|
type="datetime-local"
|
|
v-model="selectedDatetime"
|
|
class="datetime-input"
|
|
:disabled="autoRefresh || fetchState !== 'idle'"
|
|
:class="{ disabled: autoRefresh || fetchState !== 'idle' }"
|
|
/>
|
|
<button
|
|
class="refresh-btn"
|
|
:disabled="autoRefresh || fetchState !== 'idle'"
|
|
:class="{
|
|
disabled: autoRefresh,
|
|
loading: fetchState === 'loading',
|
|
success: fetchState === 'success'
|
|
}"
|
|
@click="doRefresh"
|
|
>
|
|
<span v-if="fetchState === 'loading'" class="loading-spinner"></span>
|
|
{{ buttonText }}
|
|
</button>
|
|
|
|
<div class="last-fetch-info">
|
|
마지막 조회: <span class="last-fetch-time">{{ relativeTime }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
interval: number
|
|
autoRefresh: boolean
|
|
lastFetchTime: Date | null
|
|
fetchState: 'idle' | 'loading' | 'success'
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:interval', value: number): void
|
|
(e: 'update:autoRefresh', value: boolean): void
|
|
(e: 'fetchAt', datetime: string): void
|
|
(e: 'refresh'): void
|
|
}>()
|
|
|
|
// props destructure for template
|
|
const { interval, autoRefresh, fetchState } = toRefs(props)
|
|
|
|
const selectedDatetime = ref('')
|
|
const relativeTime = ref('-')
|
|
|
|
const buttonText = computed(() => {
|
|
switch (props.fetchState) {
|
|
case 'loading': return '조회 중...'
|
|
case 'success': return '✓ 완료'
|
|
default: return '조회'
|
|
}
|
|
})
|
|
|
|
function getCurrentDatetimeLocal(): string {
|
|
const now = new Date()
|
|
const y = now.getFullYear()
|
|
const m = String(now.getMonth() + 1).padStart(2, '0')
|
|
const d = String(now.getDate()).padStart(2, '0')
|
|
const h = String(now.getHours()).padStart(2, '0')
|
|
const min = String(now.getMinutes()).padStart(2, '0')
|
|
return `${y}-${m}-${d}T${h}:${min}`
|
|
}
|
|
|
|
function getRelativeTime(date: Date | null): string {
|
|
if (!date) return '-'
|
|
|
|
const now = new Date()
|
|
const diff = Math.floor((now.getTime() - date.getTime()) / 1000)
|
|
|
|
if (diff < 5) return '방금 전'
|
|
if (diff < 60) return `${diff}초 전`
|
|
if (diff < 3600) return `${Math.floor(diff / 60)}분 전`
|
|
if (diff < 86400) return `${Math.floor(diff / 3600)}시간 전`
|
|
return `${Math.floor(diff / 86400)}일 전`
|
|
}
|
|
|
|
function updateRelativeTime() {
|
|
relativeTime.value = getRelativeTime(props.lastFetchTime)
|
|
}
|
|
|
|
let timeTimer: ReturnType<typeof window.setInterval> | null = null
|
|
|
|
onMounted(() => {
|
|
updateRelativeTime()
|
|
timeTimer = window.setInterval(updateRelativeTime, 1000)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timeTimer) {
|
|
window.clearInterval(timeTimer)
|
|
}
|
|
})
|
|
|
|
watch(() => props.lastFetchTime, () => {
|
|
updateRelativeTime()
|
|
})
|
|
|
|
// 조회간격 선택 → 자동갱신 ON + 특정시간 초기화
|
|
function selectInterval(min: number) {
|
|
selectedDatetime.value = ''
|
|
emit('update:interval', min)
|
|
if (!props.autoRefresh) {
|
|
emit('update:autoRefresh', true)
|
|
}
|
|
emit('refresh')
|
|
}
|
|
|
|
function toggleAutoRefresh() {
|
|
const newValue = !props.autoRefresh
|
|
emit('update:autoRefresh', newValue)
|
|
|
|
if (newValue) {
|
|
// 자동갱신 ON 시 특정시간 초기화 + 즉시 조회
|
|
selectedDatetime.value = ''
|
|
emit('refresh')
|
|
} else {
|
|
// 자동갱신 OFF 시 현재시간을 기본값으로 설정
|
|
selectedDatetime.value = getCurrentDatetimeLocal()
|
|
}
|
|
}
|
|
|
|
function doRefresh() {
|
|
// 자동갱신 OFF일 때만 동작
|
|
if (props.autoRefresh || props.fetchState !== 'idle') return
|
|
|
|
// 특정시간 입력된 경우 → 해당 시간 조회
|
|
if (selectedDatetime.value) {
|
|
emit('fetchAt', selectedDatetime.value.replace('T', ' '))
|
|
} else {
|
|
// 특정시간 미입력 → 현재 시점 조회
|
|
emit('refresh')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.datetime-input.disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.refresh-btn.disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.refresh-btn.loading {
|
|
cursor: wait;
|
|
background: var(--btn-primary-bg);
|
|
}
|
|
|
|
.refresh-btn.success {
|
|
background: var(--success-border);
|
|
border-color: var(--success-border);
|
|
}
|
|
|
|
.loading-spinner {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border: 2px solid #fff;
|
|
border-top-color: transparent;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
margin-right: 6px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.hint-text {
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
font-style: italic;
|
|
margin-left: 8px;
|
|
}
|
|
</style>
|