fix: make timepicker working with custom options

This commit is contained in:
Shariq Ansari 2025-08-26 17:02:44 +05:30
parent bff1b6156f
commit eada826503
2 changed files with 58 additions and 72 deletions

View File

@ -17,6 +17,7 @@
:value="timeValue"
:placeholder="placeholder"
@change="(e) => emitUpdate(e.target.value)"
@keydown.enter.prevent="(e) => emitUpdate(e.target.value)"
>
<template #prefix v-if="$slots.prefix">
<slot name="prefix" />
@ -29,13 +30,13 @@
</template>
<template #body>
<div
class="mt-2 min-w-40 max-h-72 overflow-hidden overflow-y-auto divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none [&>div]:focus-visible:ring-0"
class="mt-2 min-w-40 max-h-72 overflow-hidden overflow-y-auto divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5"
:class="{
'mt-2': ['bottom', 'left', 'right'].includes(placement),
'ml-2': placement == 'right-start',
}"
>
<MenuItems class="p-1" ref="menu">
<MenuItems class="p-1 focus-visible:outline-none" ref="menu">
<MenuItem
v-for="option in options()"
:key="option.value"
@ -67,6 +68,7 @@
<script setup>
import { TextInput } from 'frappe-ui'
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
import { allTimeSlots } from '@/components/Calendar/utils'
import { MenuItems, MenuItem } from '@headlessui/vue'
import { ref, computed, watch } from 'vue'
@ -91,6 +93,10 @@ const props = defineProps({
type: String,
default: 'bottom',
},
customOptions: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['update:modelValue'])
@ -107,34 +113,49 @@ const timeValue = computed(() => {
time = time.substring(0, 5)
}
if (timeMap[time]) {
time = timeMap[time]
} else {
const [hour, minute] = time.split(':')
const ampm = hour >= 12 ? 'pm' : 'am'
const formattedHour = hour % 12 || 12
time = `${formattedHour}:${minute} ${ampm}`
}
// Try to find a matching option (value is always in 24h format HH:MM)
const match = options().find((o) => o.value === time)
if (match) return match.label.split(' (')[0]
// Fallback: format manually if the value isn't part of provided options
const [hourStr, minute] = time.split(':')
if (hourStr !== undefined && minute !== undefined) {
const hourNum = parseInt(hourStr)
if (!isNaN(hourNum)) {
const ampm = hourNum >= 12 ? 'pm' : 'am'
const formattedHour = hourNum % 12 || 12
return `${formattedHour}:${minute} ${ampm}`
}
}
return time
})
const options = () => {
let timeOptions = []
for (const [key, value] of Object.entries(timeMap)) {
timeOptions.push({
label: value,
value: key,
onClick: () => emitUpdate(key),
isSelected: () => {
let isSelected = isSelectedOrNearestOption()
return isSelected?.value === key && !isSelected?.isNearest
},
})
const _options = props.customOptions.length
? props.customOptions
: allTimeSlots()
for (const option of _options) {
timeOptions.push(timeObj(option.label, option.value))
}
return timeOptions
}
function timeObj(label, value) {
return {
label,
value,
onClick: () => emitUpdate(value),
isSelected: () => {
let isSelected = isSelectedOrNearestOption()
return isSelected?.value === value && !isSelected?.isNearest
},
}
}
const menu = ref(null)
watch(
@ -165,7 +186,7 @@ function convertTo24HourFormat(time) {
function isSelectedOrNearestOption() {
const selectedTime = timeValue.value
const selectedOption = options().find(
(option) => option.label === selectedTime,
(option) => option.label.split(' (')[0] === selectedTime,
)
if (selectedOption) {
@ -219,55 +240,4 @@ function updateScroll(el) {
}
}
}
const timeMap = {
'00:00': '12:00 am',
'00:30': '12:30 am',
'01:00': '1:00 am',
'01:30': '1:30 am',
'02:00': '2:00 am',
'02:30': '2:30 am',
'03:00': '3:00 am',
'03:30': '3:30 am',
'04:00': '4:00 am',
'04:30': '4:30 am',
'05:00': '5:00 am',
'05:30': '5:30 am',
'06:00': '6:00 am',
'06:30': '6:30 am',
'07:00': '7:00 am',
'07:30': '7:30 am',
'08:00': '8:00 am',
'08:30': '8:30 am',
'09:00': '9:00 am',
'09:30': '9:30 am',
'10:00': '10:00 am',
'10:30': '10:30 am',
'11:00': '11:00 am',
'11:30': '11:30 am',
'12:00': '12:00 pm',
'12:30': '12:30 pm',
'13:00': '1:00 pm',
'13:30': '1:30 pm',
'14:00': '2:00 pm',
'14:30': '2:30 pm',
'15:00': '3:00 pm',
'15:30': '3:30 pm',
'16:00': '4:00 pm',
'16:30': '4:30 pm',
'17:00': '5:00 pm',
'17:30': '5:30 pm',
'18:00': '6:00 pm',
'18:30': '6:30 pm',
'19:00': '7:00 pm',
'19:30': '7:30 pm',
'20:00': '8:00 pm',
'20:30': '8:30 pm',
'21:00': '9:00 pm',
'21:30': '9:30 pm',
'22:00': '10:00 pm',
'22:30': '10:30 pm',
'23:00': '11:00 pm',
'23:30': '11:30 pm',
}
</script>

View File

@ -0,0 +1,16 @@
export function allTimeSlots() {
const out = []
for (let h = 0; h < 24; h++) {
for (const m of [0, 15, 30, 45]) {
const hh = String(h).padStart(2, '0')
const mm = String(m).padStart(2, '0')
const ampm = h >= 12 ? 'pm' : 'am'
const hour12 = h % 12 === 0 ? 12 : h % 12
out.push({
value: `${hh}:${mm}`,
label: `${hour12}:${mm} ${ampm}`,
})
}
}
return out
}