Files
chore/frontend/src/components/shared/TimeSelector.vue
Ryan Kegel 127378797c
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
Refactored frontend directory
2026-04-25 00:40:15 -04:00

162 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 // 023 (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>