<script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// Ввод длительности: два поля — часы и минуты (минуты с шагом 5). Заставлять
// пользователя переводить время в минуты не нужно. Значение компонента —
// минуты (число|null), как в API; часы/минуты раскладываются при вводе и
// собираются обратно при изменении. Оба поля пустые = null («не указано»).
const props = defineProps<{
modelValue: number | null
/** Общая подпись блока; в инлайн-редакторах подпись даёт строка параметров */
label?: string
/** Минимальное суммарное значение в минутах (0 = можно пусто) */
min?: number
}>()
const emit = defineEmits<{ 'update:modelValue': [value: number | null] }>()
const { t } = useI18n()
const hoursText = ref('')
const minutesText = ref('')
function toNumber(text: string): number | null {
const trimmed = text.trim()
if (trimmed === '') return null
const n = Number(trimmed)
return Number.isFinite(n) ? n : null
}
// внешнее значение → поля (загрузка задачи, сброс формы)
watch(
() => props.modelValue,
(v) => {
if (v === null || v === undefined) {
hoursText.value = ''
minutesText.value = ''
return
}
hoursText.value = String(Math.floor(v / 60))
minutesText.value = String(v % 60)
},
{ immediate: true },
)
watch([hoursText, minutesText], () => {
const h = toNumber(hoursText.value)
const m = toNumber(minutesText.value)
if (h === null && m === null) {
emit('update:modelValue', null)
return
}
let total = Math.max(0, h ?? 0) * 60 + Math.max(0, m ?? 0)
const min = props.min ?? 0
if (total < min) total = min
emit('update:modelValue', total)
})
</script>
<template>
<span class="duration-input">
<span v-if="label" class="duration-label">{{ label }}</span>
<span class="duration-fields">
<GnInput
v-model="hoursText"
type="number"
min="0"
class="hours"
:aria-label="t('duration.hours')"
/>
<span class="unit">{{ t('duration.h') }}</span>
<GnInput
v-model="minutesText"
type="number"
min="0"
max="55"
step="5"
class="minutes"
:aria-label="t('duration.minutes')"
/>
<span class="unit">{{ t('duration.min') }}</span>
</span>
</span>
</template>
<style scoped>
.duration-input {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
min-width: 0;
}
.duration-label {
font-size: 0.85em;
color: var(--text-muted, #888);
}
.duration-fields {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
/* GnInput рендерит div.form-group > label > input: убираем вертикальные
отступы формы и задаём компактную ширину полей (переопределение размеров,
стили кита остаются) */
.duration-fields :deep(.form-group) {
margin: 0;
width: auto;
}
.duration-fields :deep(input) {
width: 4rem;
padding: 4px 10px;
}
.unit {
color: var(--text-muted, #888);
font-size: 0.9em;
}
</style>