248 lines
6.7 KiB
TypeScript
248 lines
6.7 KiB
TypeScript
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<Record<string, string>>({})
|
|
const referencia = ref(dayjs())
|
|
const diaSelecionado = ref<dayjs.Dayjs | null>(null)
|
|
const horaTemp = ref("00")
|
|
const minutoTemp = ref("00")
|
|
|
|
const opcoesEfetivas = computed<EntradaDataHora["opcoes"]>(
|
|
() =>
|
|
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>(() => Boolean(props.desabilitado))
|
|
|
|
const effectiveModelValue = computed<string | null>(() =>
|
|
props.value !== undefined ? (props.value ?? null) : (props.modelValue ?? null),
|
|
)
|
|
|
|
const valor = computed(() => effectiveModelValue.value)
|
|
|
|
const mensagensVisiveis = computed<string[]>(() => {
|
|
const m = opcoesEfetivas.value.erro ? opcoesEfetivas.value.mensagensErro : null
|
|
return m ? (Array.isArray(m) ? m : [m]) : []
|
|
})
|
|
|
|
const valorFormatado = computed<string>(() => 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<CelulaCalendario[]>(() =>
|
|
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,
|
|
}
|
|
}
|