first commit

This commit is contained in:
Leonardo
2026-02-18 22:36:45 -03:00
parent ec6b6ef53a
commit 676042268b
122 changed files with 26354 additions and 1615 deletions
+24
View File
@@ -0,0 +1,24 @@
// src/utils/dateBR.js
export function pad2(n) {
return String(n).padStart(2, '0')
}
// ISO (YYYY-MM-DD) -> BR (DD-MM-YYYY)
export function isoToBR(iso) {
if (!iso) return ''
const s = String(iso).slice(0, 10)
const [y, m, d] = s.split('-')
if (!y || !m || !d) return ''
return `${pad2(d)}-${pad2(m)}-${y}`
}
// BR (DD-MM-YYYY) -> ISO (YYYY-MM-DD)
export function brToISO(br) {
if (!br) return null
const s = String(br).trim()
const m = s.match(/^(\d{2})-(\d{2})-(\d{4})$/)
if (!m) return null
const [, dd, mm, yyyy] = m
return `${yyyy}-${mm}-${dd}`
}
+51
View File
@@ -0,0 +1,51 @@
// src/utils/slotsGenerator.js
function toMinutes(hhmm) {
const [h, m] = String(hhmm).slice(0, 5).split(':').map(Number)
return (h * 60) + m
}
function toHHMM(min) {
const h = Math.floor(min / 60)
const m = min % 60
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`
}
/**
* Gera lista de horários (HH:MM) a partir:
* - janelas: [{hora_inicio:'08:00', hora_fim:'12:00', ativo:true}]
* - regra: {passo_minutos:60, offset_minutos:30, ativo:true}
*
* Retorna horários de INÍCIO (igual Altegio)
*/
export function gerarSlotsDoDia(janelas, regra) {
const passo = Number(regra?.passo_minutos || 60)
const offset = Number(regra?.offset_minutos || 0)
const ativos = (janelas || []).filter(j => j?.ativo !== false)
const out = []
for (const j of ativos) {
const start = toMinutes(j.hora_inicio)
const end = toMinutes(j.hora_fim)
// encontra o primeiro t >= start que respeita offset
// condição: t % passo == offset (mod passo), mas offset é dentro da hora.
// Implementação simples: alinhar pelo minuto do dia:
// alvo: t ≡ offset (mod passo) quando offset é interpretado no ciclo do passo.
// Para seu uso (passo 60, offset 30), funciona perfeito.
let t = start
const mod = ((t % passo) + passo) % passo
const need = ((offset - mod) + passo) % passo
t = t + need
while (t + 1 <= end) {
// só inclui se dentro do intervalo
if (t >= start && t < end) out.push(toHHMM(t))
t += passo
}
}
// unique + sort
return Array.from(new Set(out)).sort()
}
+58
View File
@@ -0,0 +1,58 @@
// src/utils/upgradeContext.js
/**
* Parse "missing" query param into array of unique feature keys.
* Accepts:
* - "feature_a"
* - "feature_a,feature_b"
* - ["feature_a", "feature_b"] (vue-router pode entregar array)
*/
export function parseMissingKeys (missing) {
if (!missing) return []
const raw = Array.isArray(missing) ? missing.join(',') : String(missing)
const keys = raw
.split(',')
.map(s => s.trim())
.filter(Boolean)
// unique preserving order
const seen = new Set()
const unique = []
for (const k of keys) {
if (!seen.has(k)) {
seen.add(k)
unique.push(k)
}
}
return unique
}
/**
* Parse redirect param. Only allow internal app paths to avoid open-redirect.
* - Must start with "/"
* - Must NOT start with "//"
*/
export function parseRedirectTo (redirect) {
if (!redirect) return null
const s = Array.isArray(redirect) ? redirect[0] : String(redirect)
const trimmed = s.trim()
if (!trimmed) return null
if (!trimmed.startsWith('/')) return null
if (trimmed.startsWith('//')) return null
return trimmed
}
/**
* Build /upgrade URL with missing feature and redirect target.
*/
export function buildUpgradeUrl ({ missingKeys = [], redirectTo = null } = {}) {
const keys = Array.isArray(missingKeys) ? missingKeys.filter(Boolean) : []
const q = new URLSearchParams()
if (keys.length) q.set('missing', keys.join(','))
if (redirectTo) q.set('redirect', redirectTo)
const qs = q.toString()
return qs ? `/upgrade?${qs}` : '/upgrade'
}