Agenda, Agendador, Configurações
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// src/composables/useDocsHealth.js
|
||||
// Singleton que computa métricas de saúde dos documentos.
|
||||
// Critério de "atenção": mais de 30% dos votos são negativos
|
||||
// (mínimo 3 votos totais para evitar falso-positivo com 1 voto negativo).
|
||||
//
|
||||
// O `countAtencao` é exportado como singleton para uso no menu SaaS:
|
||||
//
|
||||
// import { countAtencao } from '@/composables/useDocsHealth'
|
||||
// // No lugar onde saasMenu() é chamado:
|
||||
// saasMenu(sessionCtx, { mismatchCount, docsAtencaoCount: countAtencao.value })
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const THRESHOLD_NEGATIVO = 0.30 // 30%
|
||||
const MIN_VOTOS = 3 // mínimo de votos para considerar
|
||||
|
||||
// Estado singleton — alimentado pelo SaasDocsPage após load()
|
||||
const _docs = ref([])
|
||||
|
||||
// ── Classificação individual (exportada standalone p/ uso externo) ─
|
||||
export function saudeDocItem (doc) {
|
||||
const total = (doc.votos_util || 0) + (doc.votos_nao_util || 0)
|
||||
if (total < MIN_VOTOS) return 'sem_dados'
|
||||
const pctNeg = (doc.votos_nao_util || 0) / total
|
||||
return pctNeg > THRESHOLD_NEGATIVO ? 'atencao' : 'ok'
|
||||
}
|
||||
|
||||
// ── Singleton reativo — use no menu ou em qualquer lugar ──────
|
||||
export const countAtencao = computed(() =>
|
||||
_docs.value.filter(d => saudeDocItem(d) === 'atencao').length
|
||||
)
|
||||
|
||||
export function useDocsHealth () {
|
||||
|
||||
function setDocs (docs) {
|
||||
_docs.value = docs
|
||||
}
|
||||
|
||||
function saudeDoc (doc) {
|
||||
return saudeDocItem(doc)
|
||||
}
|
||||
|
||||
function pctNegativo (doc) {
|
||||
const total = (doc.votos_util || 0) + (doc.votos_nao_util || 0)
|
||||
if (!total) return 0
|
||||
return Math.round(((doc.votos_nao_util || 0) / total) * 100)
|
||||
}
|
||||
|
||||
// ── Métricas globais ───────────────────────────────────────
|
||||
const totalDocs = computed(() => _docs.value.length)
|
||||
const docsAtencao = computed(() => _docs.value.filter(d => saudeDoc(d) === 'atencao'))
|
||||
const docsOk = computed(() => _docs.value.filter(d => saudeDoc(d) === 'ok'))
|
||||
const docsSemDados = computed(() => _docs.value.filter(d => saudeDoc(d) === 'sem_dados'))
|
||||
|
||||
// Doc mais útil (maior % positivo com mínimo de votos)
|
||||
const docMaisUtil = computed(() => {
|
||||
const comVotos = _docs.value.filter(d =>
|
||||
(d.votos_util || 0) + (d.votos_nao_util || 0) >= MIN_VOTOS
|
||||
)
|
||||
if (!comVotos.length) return null
|
||||
return comVotos.reduce((best, d) => {
|
||||
const pct = (d.votos_util || 0) / ((d.votos_util || 0) + (d.votos_nao_util || 0))
|
||||
const bestPct = (best.votos_util || 0) / ((best.votos_util || 0) + (best.votos_nao_util || 0))
|
||||
return pct > bestPct ? d : best
|
||||
})
|
||||
})
|
||||
|
||||
// ── Ordenação por saúde ────────────────────────────────────
|
||||
// Problemáticas primeiro → ok → sem dados
|
||||
function sortBySaude (lista) {
|
||||
const ordem = { atencao: 0, ok: 1, sem_dados: 2 }
|
||||
return [...lista].sort((a, b) => {
|
||||
const sa = saudeDoc(a)
|
||||
const sb = saudeDoc(b)
|
||||
if (ordem[sa] !== ordem[sb]) return ordem[sa] - ordem[sb]
|
||||
return pctNegativo(b) - pctNegativo(a)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
setDocs,
|
||||
saudeDoc,
|
||||
pctNegativo,
|
||||
totalDocs,
|
||||
docsAtencao,
|
||||
docsOk,
|
||||
docsSemDados,
|
||||
countAtencao,
|
||||
docMaisUtil,
|
||||
sortBySaude,
|
||||
THRESHOLD_NEGATIVO,
|
||||
MIN_VOTOS,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// src/composables/useAjuda.js
|
||||
// Composable singleton para o drawer de ajuda.
|
||||
// - Home: docs da sessão atual + outros docs paginados + FAQ
|
||||
// - Navegação interna com stack (voltar)
|
||||
// - Votação por usuário (útil / não útil)
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { sessionRole, sessionIsSaasAdmin } from '@/app/session'
|
||||
|
||||
const ADMIN_ROLES = ['clinic_admin', 'tenant_admin']
|
||||
|
||||
function isCurrentUserAdmin () {
|
||||
return sessionIsSaasAdmin.value || ADMIN_ROLES.includes(sessionRole.value)
|
||||
}
|
||||
|
||||
// ── Singleton state ────────────────────────────────────────────
|
||||
const cache = new Map() // path → { docs, relatedDocs, faqItens }
|
||||
const docCache = new Map() // id → { docs, relatedDocs, faqItens }
|
||||
|
||||
// Estado da sessão atual
|
||||
const sessionDocs = ref([])
|
||||
const sessionFaq = ref([])
|
||||
const sessionPath = ref('')
|
||||
|
||||
// Estado da home (todos os docs paginados + FAQ global)
|
||||
const allDocs = ref([])
|
||||
const allDocsTotal = ref(0)
|
||||
const allDocsPage = ref(0)
|
||||
const allDocsLoading = ref(false)
|
||||
const ALL_DOCS_PAGE_SIZE = 8
|
||||
|
||||
const globalFaq = ref([])
|
||||
const globalFaqLoading = ref(false)
|
||||
|
||||
// Drawer
|
||||
const drawerOpen = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
// Stack de navegação — cada entrada: { currentDoc, label }
|
||||
const navStack = ref([])
|
||||
// null = home, objeto = doc aberto
|
||||
const currentDoc = ref(null)
|
||||
|
||||
const isHome = computed(() => currentDoc.value === null)
|
||||
const isNavigating = computed(() => navStack.value.length > 0)
|
||||
|
||||
// Votos do usuário { [docId]: true | false | null }
|
||||
const meusVotos = ref({})
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
function normalizePath (path) {
|
||||
return String(path || '').split('?')[0].split('#')[0].replace(/\/$/, '') || '/'
|
||||
}
|
||||
|
||||
async function fetchFaqItensForDocs (docIds) {
|
||||
if (!docIds.length) return []
|
||||
const { data } = await supabase
|
||||
.from('saas_faq_itens')
|
||||
.select('id, doc_id, pergunta, resposta, ordem')
|
||||
.in('doc_id', docIds)
|
||||
.eq('ativo', true)
|
||||
.order('doc_id')
|
||||
.order('ordem')
|
||||
return data || []
|
||||
}
|
||||
|
||||
// ── Fetch por rota (chamado pelo AppLayout ao mudar de rota) ──
|
||||
export async function fetchDocsForPath (rawPath) {
|
||||
const path = normalizePath(rawPath)
|
||||
sessionPath.value = path
|
||||
currentDoc.value = null
|
||||
navStack.value = []
|
||||
// Reseta paginação de outros docs ao mudar de rota
|
||||
allDocs.value = []
|
||||
allDocsPage.value = 0
|
||||
|
||||
if (cache.has(path)) {
|
||||
const cached = cache.get(path)
|
||||
sessionDocs.value = cached.docs
|
||||
sessionFaq.value = cached.faqItens
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo, conteudo, medias, tipo_acesso, docs_relacionados, ordem, categoria, exibir_no_faq, votos_util, votos_nao_util')
|
||||
.eq('pagina_path', path)
|
||||
.eq('ativo', true)
|
||||
.order('ordem')
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const userIsAdmin = isCurrentUserAdmin()
|
||||
const mainDocs = (data || []).filter(d =>
|
||||
d.tipo_acesso === 'usuario' || userIsAdmin
|
||||
)
|
||||
|
||||
const allIds = [...new Set(mainDocs.flatMap(d => d.docs_relacionados || []))]
|
||||
let related = []
|
||||
if (allIds.length) {
|
||||
const { data: relData } = await supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo, pagina_path')
|
||||
.in('id', allIds)
|
||||
.eq('ativo', true)
|
||||
related = relData || []
|
||||
}
|
||||
|
||||
const itens = await fetchFaqItensForDocs(mainDocs.map(d => d.id))
|
||||
|
||||
cache.set(path, { docs: mainDocs, relatedDocs: related, faqItens: itens })
|
||||
sessionDocs.value = mainDocs
|
||||
sessionFaq.value = itens
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao buscar docs:', e?.message || e)
|
||||
sessionDocs.value = []
|
||||
sessionFaq.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Todos os docs paginados (para a home do drawer) ───────────
|
||||
export async function loadAllDocs (page = 0) {
|
||||
allDocsLoading.value = true
|
||||
try {
|
||||
const from = page * ALL_DOCS_PAGE_SIZE
|
||||
const to = from + ALL_DOCS_PAGE_SIZE - 1
|
||||
|
||||
const userIsAdmin = isCurrentUserAdmin()
|
||||
let query = supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo, pagina_path, categoria, votos_util, votos_nao_util', { count: 'exact' })
|
||||
.eq('ativo', true)
|
||||
.order('titulo')
|
||||
.range(from, to)
|
||||
|
||||
if (!userIsAdmin) query = query.eq('tipo_acesso', 'usuario')
|
||||
|
||||
const { data, count, error } = await query
|
||||
if (error) throw error
|
||||
|
||||
// Exclui docs da sessão atual
|
||||
const sessionIds = new Set(sessionDocs.value.map(d => d.id))
|
||||
const filtered = (data || []).filter(d => !sessionIds.has(d.id))
|
||||
|
||||
allDocs.value = page === 0 ? filtered : [...allDocs.value, ...filtered]
|
||||
allDocsTotal.value = count || 0
|
||||
allDocsPage.value = page
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao carregar todos os docs:', e?.message || e)
|
||||
} finally {
|
||||
allDocsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── FAQ global (itens fora da sessão atual) ───────────────────
|
||||
export async function loadGlobalFaq () {
|
||||
globalFaqLoading.value = true
|
||||
try {
|
||||
const userIsAdmin = isCurrentUserAdmin()
|
||||
let q = supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo')
|
||||
.eq('ativo', true)
|
||||
.eq('exibir_no_faq', true)
|
||||
|
||||
if (!userIsAdmin) q = q.eq('tipo_acesso', 'usuario')
|
||||
|
||||
const { data: faqDocs } = await q
|
||||
if (!faqDocs?.length) { globalFaq.value = []; return }
|
||||
|
||||
const sessionIds = new Set(sessionDocs.value.map(d => d.id))
|
||||
const outrosIds = faqDocs.filter(d => !sessionIds.has(d.id)).map(d => d.id)
|
||||
|
||||
if (!outrosIds.length) { globalFaq.value = []; return }
|
||||
|
||||
const itens = await fetchFaqItensForDocs(outrosIds)
|
||||
globalFaq.value = itens.map(item => ({
|
||||
...item,
|
||||
_docTitulo: faqDocs.find(d => d.id === item.doc_id)?.titulo || ''
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao carregar FAQ global:', e?.message || e)
|
||||
globalFaq.value = []
|
||||
} finally {
|
||||
globalFaqLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Votos do usuário ──────────────────────────────────────────
|
||||
export async function loadMeusVotos () {
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) return
|
||||
const { data } = await supabase
|
||||
.from('saas_doc_votos')
|
||||
.select('doc_id, util')
|
||||
.eq('user_id', user.id)
|
||||
meusVotos.value = {}
|
||||
for (const v of (data || [])) meusVotos.value[v.doc_id] = v.util
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao carregar votos:', e?.message || e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function votar (docId, util) {
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('saas_votar_doc', {
|
||||
p_doc_id: docId,
|
||||
p_util: util
|
||||
})
|
||||
if (error) throw error
|
||||
|
||||
const acao = data?.acao
|
||||
meusVotos.value = {
|
||||
...meusVotos.value,
|
||||
[docId]: acao === 'removido' ? null : util
|
||||
}
|
||||
_atualizarContadorLocal(docId, util, acao)
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao votar:', e?.message || e)
|
||||
}
|
||||
}
|
||||
|
||||
function _atualizarContadorLocal (docId, util, acao) {
|
||||
const ajustar = (doc) => {
|
||||
if (!doc || doc.id !== docId) return doc
|
||||
let { votos_util = 0, votos_nao_util = 0 } = doc
|
||||
if (acao === 'registrado') {
|
||||
if (util) votos_util++; else votos_nao_util++
|
||||
} else if (acao === 'removido') {
|
||||
if (util) votos_util = Math.max(0, votos_util - 1)
|
||||
else votos_nao_util = Math.max(0, votos_nao_util - 1)
|
||||
} else if (acao === 'atualizado') {
|
||||
if (util) { votos_util++; votos_nao_util = Math.max(0, votos_nao_util - 1) }
|
||||
else { votos_nao_util++; votos_util = Math.max(0, votos_util - 1) }
|
||||
}
|
||||
return { ...doc, votos_util, votos_nao_util }
|
||||
}
|
||||
sessionDocs.value = sessionDocs.value.map(ajustar)
|
||||
allDocs.value = allDocs.value.map(ajustar)
|
||||
if (currentDoc.value) {
|
||||
currentDoc.value = { ...currentDoc.value, docs: currentDoc.value.docs.map(ajustar) }
|
||||
}
|
||||
cache.forEach((val, key) => {
|
||||
if (val.docs.some(d => d.id === docId)) cache.delete(key)
|
||||
})
|
||||
docCache.delete(docId)
|
||||
}
|
||||
|
||||
// ── Navegação interna ─────────────────────────────────────────
|
||||
export async function navigateToDoc (docId, label = '') {
|
||||
if (!docId) return
|
||||
|
||||
navStack.value.push({ currentDoc: currentDoc.value, label: label || 'Voltar' })
|
||||
loading.value = true
|
||||
try {
|
||||
if (docCache.has(docId)) {
|
||||
currentDoc.value = docCache.get(docId)
|
||||
return
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo, conteudo, medias, tipo_acesso, docs_relacionados, ordem, categoria, exibir_no_faq, votos_util, votos_nao_util')
|
||||
.eq('id', docId)
|
||||
.eq('ativo', true)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) { navStack.value.pop(); return }
|
||||
|
||||
const userIsAdmin = isCurrentUserAdmin()
|
||||
if (data.tipo_acesso !== 'usuario' && !userIsAdmin) {
|
||||
navStack.value.pop(); return
|
||||
}
|
||||
|
||||
const relIds = data.docs_relacionados || []
|
||||
let related = []
|
||||
if (relIds.length) {
|
||||
const { data: relData } = await supabase
|
||||
.from('saas_docs')
|
||||
.select('id, titulo, pagina_path')
|
||||
.in('id', relIds)
|
||||
.eq('ativo', true)
|
||||
related = relData || []
|
||||
}
|
||||
|
||||
const itens = await fetchFaqItensForDocs([data.id])
|
||||
const entry = { docs: [data], relatedDocs: related, faqItens: itens }
|
||||
docCache.set(docId, entry)
|
||||
currentDoc.value = entry
|
||||
} catch (e) {
|
||||
console.warn('[useAjuda] erro ao navegar para doc:', e?.message || e)
|
||||
navStack.value.pop()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
export function navBack () {
|
||||
const prev = navStack.value.pop()
|
||||
if (prev) currentDoc.value = prev.currentDoc
|
||||
}
|
||||
|
||||
export function invalidateAjudaCache (path) {
|
||||
if (path) cache.delete(normalizePath(path))
|
||||
else cache.clear()
|
||||
docCache.clear()
|
||||
}
|
||||
|
||||
// ── Composable público ────────────────────────────────────────
|
||||
export function useAjuda () {
|
||||
const hasAjuda = computed(() => true) // sempre habilitado
|
||||
|
||||
const allDocsHasMore = computed(() =>
|
||||
allDocs.value.length < allDocsTotal.value - sessionDocs.value.length
|
||||
)
|
||||
|
||||
function openDrawer () {
|
||||
drawerOpen.value = true
|
||||
currentDoc.value = null
|
||||
if (!allDocs.value.length) loadAllDocs(0)
|
||||
if (!globalFaq.value.length) loadGlobalFaq()
|
||||
if (!Object.keys(meusVotos.value).length) loadMeusVotos()
|
||||
}
|
||||
|
||||
function closeDrawer () {
|
||||
drawerOpen.value = false
|
||||
navStack.value = []
|
||||
currentDoc.value = null
|
||||
}
|
||||
|
||||
function loadMoreDocs () {
|
||||
loadAllDocs(allDocsPage.value + 1)
|
||||
}
|
||||
|
||||
return {
|
||||
sessionDocs, sessionFaq, sessionPath,
|
||||
allDocs, allDocsTotal, allDocsLoading, allDocsHasMore,
|
||||
globalFaq, globalFaqLoading,
|
||||
currentDoc, isHome, isNavigating, navStack,
|
||||
drawerOpen, loading,
|
||||
hasAjuda,
|
||||
meusVotos,
|
||||
openDrawer, closeDrawer,
|
||||
navigateToDoc, navBack,
|
||||
votar, loadMoreDocs,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// src/composables/useDocsAdmin.js
|
||||
// Estado compartilhado para abrir o dialog de edição de um doc
|
||||
// a partir de outra página (ex: SaasFaqPage → SaasDocsPage).
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
const pendingEditDocId = ref(null)
|
||||
|
||||
export function useDocsAdmin () {
|
||||
function requestEditDoc (docId) {
|
||||
pendingEditDocId.value = docId
|
||||
}
|
||||
|
||||
function consumePendingEdit () {
|
||||
const id = pendingEditDocId.value
|
||||
pendingEditDocId.value = null
|
||||
return id
|
||||
}
|
||||
|
||||
return { pendingEditDocId, requestEditDoc, consumePendingEdit }
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// src/composables/useFeriados.js
|
||||
// Fonte única de verdade para feriados: nacionais (algoritmo) + municipais (Supabase).
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { getFeriadosNacionais } from '@/utils/feriadosBR'
|
||||
|
||||
export function useFeriados () {
|
||||
const ano = ref(new Date().getFullYear())
|
||||
const loading = ref(false)
|
||||
const municipais = ref([]) // linhas da tabela `feriados`
|
||||
|
||||
// ── Nacionais (algoritmo, sem DB) ─────────────────────────
|
||||
const nacionais = computed(() =>
|
||||
getFeriadosNacionais(ano.value).map(f => ({ ...f, tipo: 'nacional' }))
|
||||
)
|
||||
|
||||
// ── Todos juntos, ordenados por data ─────────────────────
|
||||
const todos = computed(() => [
|
||||
...nacionais.value,
|
||||
...municipais.value.map(f => ({ ...f, tipo: f.tipo || 'municipal' }))
|
||||
].sort((a, b) => a.data.localeCompare(b.data)))
|
||||
|
||||
// ── Feriados de um mês (1–12) ─────────────────────────────
|
||||
function doMes (mes) {
|
||||
const m = String(mes).padStart(2, '0')
|
||||
const prefix = `${ano.value}-${m}`
|
||||
return todos.value.filter(f => f.data.startsWith(prefix))
|
||||
}
|
||||
|
||||
// ── Próximos N dias ───────────────────────────────────────
|
||||
function proximos (dias = 30) {
|
||||
const hoje = new Date()
|
||||
const limite = new Date(hoje)
|
||||
limite.setDate(limite.getDate() + dias)
|
||||
const hojeISO = toISO(hoje)
|
||||
const limiteISO = toISO(limite)
|
||||
return todos.value.filter(f => f.data >= hojeISO && f.data <= limiteISO)
|
||||
}
|
||||
|
||||
function toISO (d) {
|
||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
|
||||
}
|
||||
|
||||
// ── Load municipais do Supabase ───────────────────────────
|
||||
async function load (tenantId, year) {
|
||||
if (year) ano.value = year
|
||||
if (!tenantId) return
|
||||
loading.value = true
|
||||
try {
|
||||
// Busca feriados do tenant + feriados globais (tenant_id null, cadastrados pelo SAAS)
|
||||
const { data, error } = await supabase
|
||||
.from('feriados')
|
||||
.select('*')
|
||||
.or(`tenant_id.eq.${tenantId},tenant_id.is.null`)
|
||||
.gte('data', `${ano.value}-01-01`)
|
||||
.lte('data', `${ano.value}-12-31`)
|
||||
.order('data')
|
||||
if (error) throw error
|
||||
municipais.value = data || []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Criar feriado municipal ───────────────────────────────
|
||||
async function criar (payload) {
|
||||
const { data, error } = await supabase
|
||||
.from('feriados')
|
||||
.insert(payload)
|
||||
.select()
|
||||
.single()
|
||||
if (error) throw error
|
||||
municipais.value = [...municipais.value, data].sort((a, b) => a.data.localeCompare(b.data))
|
||||
return data
|
||||
}
|
||||
|
||||
// ── Remover feriado municipal ─────────────────────────────
|
||||
async function remover (id) {
|
||||
const { error } = await supabase.from('feriados').delete().eq('id', id)
|
||||
if (error) throw error
|
||||
municipais.value = municipais.value.filter(f => f.id !== id)
|
||||
}
|
||||
|
||||
// ── Verificar duplicata ───────────────────────────────────
|
||||
function isDuplicata (data, nome) {
|
||||
return todos.value.some(f => f.data === data && f.nome.trim().toLowerCase() === nome.trim().toLowerCase())
|
||||
}
|
||||
|
||||
// ── Converter para eventos do FullCalendar (background) ──
|
||||
function toFcEvents (lista) {
|
||||
return lista.map(f => ({
|
||||
id: `feriado_${f.id || f.data}_${f.nome}`,
|
||||
title: f.nome,
|
||||
start: f.data,
|
||||
allDay: true,
|
||||
display: 'background',
|
||||
color: 'rgba(251, 191, 36, 0.25)',
|
||||
extendedProps: { _feriado: true, tipo: f.tipo }
|
||||
}))
|
||||
}
|
||||
|
||||
const fcEvents = computed(() => toFcEvents(todos.value))
|
||||
|
||||
return {
|
||||
ano,
|
||||
loading,
|
||||
nacionais,
|
||||
municipais,
|
||||
todos,
|
||||
fcEvents,
|
||||
load,
|
||||
criar,
|
||||
remover,
|
||||
doMes,
|
||||
proximos,
|
||||
isDuplicata
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user