import dayjs from "dayjs" import "dayjs/locale/pt-br" import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue" import type { CampoDensidade, CampoVariante } from "../../../tipos" import type { PadroesEntradas } from "../tiposEntradas" import { formatarValor, gerarCelulas } from "./calendarioHelpers" dayjs.locale("pt-br") export type EntradaDataHora = PadroesEntradas["dataHora"] export interface CelulaCalendario { dia: number data: dayjs.Dayjs atual: boolean hoje: boolean selecionado: boolean desabilitado: boolean } export const MESES = Array.from({ length: 12 }, (_, i) => { const s = dayjs().month(i).format("MMMM") return s.charAt(0).toUpperCase() + s.slice(1) }) export const diasSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"] /** Função de emit genérica do Vue (compatível com defineComponent) */ type EmitFn = (event: string, payload?: unknown) => void type Props = { value?: string | null opcoes?: EntradaDataHora["opcoes"] modelValue?: string | null modo?: "data" | "dataHora" rotulo?: string placeholder?: string desabilitado?: boolean limpavel?: boolean erro?: boolean mensagensErro?: string | string[] dica?: string dicaPersistente?: boolean densidade?: CampoDensidade variante?: CampoVariante min?: string max?: string } let contadorUid = 0 /** Composable principal do EliEntradaDataHora. */ export function useDataHora(props: Props, emit: EmitFn) { const uid = ++contadorUid const aberto = ref(false) const estiloPopover = ref>({}) const referencia = ref(dayjs()) const diaSelecionado = ref(null) const horaTemp = ref("00") const minutoTemp = ref("00") const opcoesEfetivas = computed( () => props.opcoes ?? { rotulo: props.rotulo ?? "Data", placeholder: props.placeholder ?? "", modo: props.modo ?? "dataHora", limpavel: props.limpavel, erro: props.erro, mensagensErro: props.mensagensErro, dica: props.dica, dicaPersistente: props.dicaPersistente, densidade: props.densidade, variante: props.variante, min: props.min, max: props.max, }, ) const modoEfetivo = computed<"data" | "dataHora">(() => opcoesEfetivas.value.modo ?? "dataHora") const desabilitadoEfetivo = computed(() => Boolean(props.desabilitado)) const effectiveModelValue = computed(() => props.value !== undefined ? (props.value ?? null) : (props.modelValue ?? null), ) const valor = computed(() => effectiveModelValue.value) const mensagensVisiveis = computed(() => { const m = opcoesEfetivas.value.erro ? opcoesEfetivas.value.mensagensErro : null return m ? (Array.isArray(m) ? m : [m]) : [] }) const valorFormatado = computed(() => formatarValor(valor.value, modoEfetivo.value)) const nomeMesAtual = computed(() => MESES[referencia.value.month()]) const anoAtual = computed(() => referencia.value.year()) const mesAtual = computed(() => referencia.value.month()) const listaAnos = computed(() => { const y = referencia.value.year() const min = opcoesEfetivas.value.min ? dayjs(opcoesEfetivas.value.min).year() : dayjs().year() - 80 const max = opcoesEfetivas.value.max ? dayjs(opcoesEfetivas.value.max).year() : dayjs().year() + 20 const anos: number[] = [] for (let i = Math.min(min, y); i <= Math.max(max, y); i++) anos.push(i) return anos }) const celulas = computed(() => gerarCelulas( referencia.value, diaSelecionado.value, opcoesEfetivas.value.min, opcoesEfetivas.value.max, ), ) watch( effectiveModelValue, (v) => { if (v) { const d = dayjs(v) diaSelecionado.value = d.startOf("day") referencia.value = d.startOf("month") horaTemp.value = d.format("HH") minutoTemp.value = d.format("mm") } else { diaSelecionado.value = null horaTemp.value = "00" minutoTemp.value = "00" } }, { immediate: true }, ) function calcularPosicao() { const el = document.getElementById(`eli-dh-campo-${uid}`) if (!el) return const rect = el.getBoundingClientRect() const top = rect.bottom + window.scrollY + 6 let left = rect.left + window.scrollX if (left + 316 > window.innerWidth + window.scrollX - 8) left = rect.right + window.scrollX - 316 estiloPopover.value = { position: "absolute", top: `${top}px`, left: `${left}px` } } function abrirSeHabilitado() { if (desabilitadoEfetivo.value) return aberto.value = !aberto.value if (aberto.value) { emit("foco") emit("focus") nextTick(calcularPosicao) } } function fechar() { if (!aberto.value) return aberto.value = false emit("desfoco") emit("blur") } function navegarMes(delta: number) { referencia.value = referencia.value.add(delta, "month") } function mudarMes(mes: number) { referencia.value = referencia.value.month(mes) } function mudarAno(ano: number) { referencia.value = referencia.value.year(ano) } function selecionarDia(cell: CelulaCalendario) { if (cell.desabilitado) return diaSelecionado.value = cell.data.startOf("day") if (modoEfetivo.value === "data") confirmar() } function emitirValor(iso: string | null) { ;["update:value", "input", "change", "update:modelValue", "alterar"].forEach((e) => { emit(e, iso) }) } function confirmar() { if (!diaSelecionado.value) return const h = Number.parseInt(horaTemp.value, 10) || 0 const m = Number.parseInt(minutoTemp.value, 10) || 0 const r = modoEfetivo.value === "data" ? diaSelecionado.value.startOf("day") : diaSelecionado.value.hour(h).minute(m).second(0) emitirValor(r.format()) fechar() } function limpar() { emitirValor(null) } function onDocClick(e: MouseEvent) { if (!aberto.value) return const alvo = e.target as Node if ( !document.getElementById(`eli-dh-campo-${uid}`)?.contains(alvo) && !document.getElementById(`eli-dh-popover-${uid}`)?.contains(alvo) ) fechar() } onMounted(() => document.addEventListener("click", onDocClick, true)) onBeforeUnmount(() => document.removeEventListener("click", onDocClick, true)) return { uid, aberto, estiloPopover, referencia, diaSelecionado, horaTemp, minutoTemp, opcoesEfetivas, modoEfetivo, desabilitadoEfetivo, valor, valorFormatado, mensagensVisiveis, nomeMesAtual, anoAtual, mesAtual, listaAnos, mudarMes, mudarAno, celulas, abrirSeHabilitado, fechar, navegarMes, selecionarDia, confirmar, limpar, } }