Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
162 lines
4.5 KiB
Vue
162 lines
4.5 KiB
Vue
<template>
|
||
<div class="time-selector">
|
||
<!-- Hour column -->
|
||
<div class="time-col">
|
||
<button class="arrow-btn" @click="incrementHour" aria-label="Increase hour">▲</button>
|
||
<div class="time-value">{{ displayHour }}</div>
|
||
<button class="arrow-btn" @click="decrementHour" aria-label="Decrease hour">▼</button>
|
||
</div>
|
||
|
||
<div class="time-separator">:</div>
|
||
|
||
<!-- Minute column -->
|
||
<div class="time-col">
|
||
<button class="arrow-btn" @click="incrementMinute" aria-label="Increase minute">▲</button>
|
||
<div class="time-value">{{ displayMinute }}</div>
|
||
<button class="arrow-btn" @click="decrementMinute" aria-label="Decrease minute">▼</button>
|
||
</div>
|
||
|
||
<!-- AM/PM toggle -->
|
||
<button class="ampm-btn" @click="toggleAmPm">{{ period }}</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
|
||
interface TimeValue {
|
||
hour: number // 0–23 (24h)
|
||
minute: number // 0, 15, 30, or 45
|
||
}
|
||
|
||
const props = defineProps<{ modelValue: TimeValue }>()
|
||
const emit = defineEmits<{ (e: 'update:modelValue', val: TimeValue): void }>()
|
||
|
||
const MINUTES = [0, 15, 30, 45]
|
||
|
||
// ── display helpers ──────────────────────────────────────────────────────────
|
||
|
||
const period = computed(() => (props.modelValue.hour < 12 ? 'AM' : 'PM'))
|
||
|
||
const displayHour = computed(() => {
|
||
const h = props.modelValue.hour % 12
|
||
return String(h === 0 ? 12 : h)
|
||
})
|
||
|
||
const displayMinute = computed(() => String(props.modelValue.minute).padStart(2, '0'))
|
||
|
||
// ── mutations ────────────────────────────────────────────────────────────────
|
||
|
||
function incrementHour() {
|
||
const next = (props.modelValue.hour + 1) % 24
|
||
emit('update:modelValue', { hour: next, minute: props.modelValue.minute })
|
||
}
|
||
|
||
function decrementHour() {
|
||
const next = (props.modelValue.hour - 1 + 24) % 24
|
||
emit('update:modelValue', { hour: next, minute: props.modelValue.minute })
|
||
}
|
||
|
||
function incrementMinute() {
|
||
const idx = MINUTES.indexOf(props.modelValue.minute)
|
||
if (idx === MINUTES.length - 1) {
|
||
// Wrap minute to 0 and carry into next hour
|
||
const nextHour = (props.modelValue.hour + 1) % 24
|
||
emit('update:modelValue', { hour: nextHour, minute: 0 })
|
||
} else {
|
||
emit('update:modelValue', { hour: props.modelValue.hour, minute: MINUTES[idx + 1] })
|
||
}
|
||
}
|
||
|
||
function decrementMinute() {
|
||
const idx = MINUTES.indexOf(props.modelValue.minute)
|
||
if (idx === 0) {
|
||
// Wrap minute to 45 and borrow from previous hour
|
||
const prevHour = (props.modelValue.hour - 1 + 24) % 24
|
||
emit('update:modelValue', { hour: prevHour, minute: 45 })
|
||
} else {
|
||
emit('update:modelValue', { hour: props.modelValue.hour, minute: MINUTES[idx - 1] })
|
||
}
|
||
}
|
||
|
||
function toggleAmPm() {
|
||
const next = props.modelValue.hour < 12 ? props.modelValue.hour + 12 : props.modelValue.hour - 12
|
||
emit('update:modelValue', { hour: next, minute: props.modelValue.minute })
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.time-selector {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
user-select: none;
|
||
}
|
||
|
||
.time-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.15rem;
|
||
}
|
||
|
||
.arrow-btn {
|
||
width: 1.8rem;
|
||
height: 1.8rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: transparent;
|
||
border: 1px solid var(--kebab-menu-border, #bcc1c9);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.75rem;
|
||
color: var(--primary, #667eea);
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.arrow-btn:hover {
|
||
background: var(--menu-item-hover-bg, rgba(102, 126, 234, 0.08));
|
||
}
|
||
|
||
.time-value {
|
||
width: 1.8rem;
|
||
height: 1.8rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--secondary, #7257b3);
|
||
border: 1.5px solid var(--kebab-menu-border, #bcc1c9);
|
||
border-radius: 6px;
|
||
background: var(--modal-bg, #fff);
|
||
}
|
||
|
||
.time-separator {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--secondary, #7257b3);
|
||
padding-bottom: 0.1rem;
|
||
align-self: center;
|
||
}
|
||
|
||
.ampm-btn {
|
||
width: 2.2rem;
|
||
height: 1.8rem;
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
border: 1.5px solid var(--btn-primary, #667eea);
|
||
border-radius: 6px;
|
||
background: var(--btn-primary, #667eea);
|
||
color: #fff;
|
||
cursor: pointer;
|
||
margin-left: 0.35rem;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.ampm-btn:hover {
|
||
opacity: 0.85;
|
||
}
|
||
</style>
|