Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
+12
-12
@@ -16,24 +16,24 @@
|
||||
*/
|
||||
|
||||
export function pad2(n) {
|
||||
return String(n).padStart(2, '0')
|
||||
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}`
|
||||
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}`
|
||||
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}`;
|
||||
}
|
||||
|
||||
+50
-50
@@ -21,39 +21,39 @@
|
||||
* @param {number} ano
|
||||
* @returns {Date}
|
||||
*/
|
||||
function calcularPascoa (ano) {
|
||||
const a = ano % 19
|
||||
const b = Math.floor(ano / 100)
|
||||
const c = ano % 100
|
||||
const d = Math.floor(b / 4)
|
||||
const e = b % 4
|
||||
const f = Math.floor((b + 8) / 25)
|
||||
const g = Math.floor((b - f + 1) / 3)
|
||||
const h = (19 * a + b - d - g + 15) % 30
|
||||
const i = Math.floor(c / 4)
|
||||
const k = c % 4
|
||||
const l = (32 + 2 * e + 2 * i - h - k) % 7
|
||||
const m = Math.floor((a + 11 * h + 22 * l) / 451)
|
||||
const mes = Math.floor((h + l - 7 * m + 114) / 31)
|
||||
const dia = ((h + l - 7 * m + 114) % 31) + 1
|
||||
return new Date(ano, mes - 1, dia)
|
||||
function calcularPascoa(ano) {
|
||||
const a = ano % 19;
|
||||
const b = Math.floor(ano / 100);
|
||||
const c = ano % 100;
|
||||
const d = Math.floor(b / 4);
|
||||
const e = b % 4;
|
||||
const f = Math.floor((b + 8) / 25);
|
||||
const g = Math.floor((b - f + 1) / 3);
|
||||
const h = (19 * a + b - d - g + 15) % 30;
|
||||
const i = Math.floor(c / 4);
|
||||
const k = c % 4;
|
||||
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
||||
const mes = Math.floor((h + l - 7 * m + 114) / 31);
|
||||
const dia = ((h + l - 7 * m + 114) % 31) + 1;
|
||||
return new Date(ano, mes - 1, dia);
|
||||
}
|
||||
|
||||
function somarDias (data, dias) {
|
||||
const d = new Date(data)
|
||||
d.setDate(d.getDate() + dias)
|
||||
return d
|
||||
function somarDias(data, dias) {
|
||||
const d = new Date(data);
|
||||
d.setDate(d.getDate() + dias);
|
||||
return d;
|
||||
}
|
||||
|
||||
function toISO (data) {
|
||||
const y = data.getFullYear()
|
||||
const m = String(data.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(data.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${d}`
|
||||
function toISO(data) {
|
||||
const y = data.getFullYear();
|
||||
const m = String(data.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(data.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
function feriado (data, nome, movel = false) {
|
||||
return { data: toISO(data), nome, movel }
|
||||
function feriado(data, nome, movel = false) {
|
||||
return { data: toISO(data), nome, movel };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,29 +61,29 @@ function feriado (data, nome, movel = false) {
|
||||
* @param {number} ano
|
||||
* @returns {{ data: string, nome: string, movel: boolean }[]}
|
||||
*/
|
||||
export function getFeriadosNacionais (ano) {
|
||||
const pascoa = calcularPascoa(ano)
|
||||
export function getFeriadosNacionais(ano) {
|
||||
const pascoa = calcularPascoa(ano);
|
||||
|
||||
const lista = [
|
||||
// ── Fixos ───────────────────────────────────────────────
|
||||
feriado(new Date(ano, 0, 1), 'Confraternização Universal'),
|
||||
feriado(new Date(ano, 3, 21), 'Tiradentes'),
|
||||
feriado(new Date(ano, 4, 1), 'Dia do Trabalho'),
|
||||
feriado(new Date(ano, 8, 7), 'Independência do Brasil'),
|
||||
feriado(new Date(ano, 9, 12), 'Nossa Senhora Aparecida'),
|
||||
feriado(new Date(ano, 10, 2), 'Finados'),
|
||||
feriado(new Date(ano, 10, 15), 'Proclamação da República'),
|
||||
feriado(new Date(ano, 11, 25), 'Natal'),
|
||||
const lista = [
|
||||
// ── Fixos ───────────────────────────────────────────────
|
||||
feriado(new Date(ano, 0, 1), 'Confraternização Universal'),
|
||||
feriado(new Date(ano, 3, 21), 'Tiradentes'),
|
||||
feriado(new Date(ano, 4, 1), 'Dia do Trabalho'),
|
||||
feriado(new Date(ano, 8, 7), 'Independência do Brasil'),
|
||||
feriado(new Date(ano, 9, 12), 'Nossa Senhora Aparecida'),
|
||||
feriado(new Date(ano, 10, 2), 'Finados'),
|
||||
feriado(new Date(ano, 10, 15), 'Proclamação da República'),
|
||||
feriado(new Date(ano, 11, 25), 'Natal'),
|
||||
|
||||
// ── Móveis ──────────────────────────────────────────────
|
||||
feriado(somarDias(pascoa, -48), 'Segunda de Carnaval', true),
|
||||
feriado(somarDias(pascoa, -47), 'Carnaval', true),
|
||||
feriado(somarDias(pascoa, -2), 'Sexta-feira Santa', true),
|
||||
feriado(pascoa, 'Páscoa', true),
|
||||
feriado(somarDias(pascoa, 60), 'Corpus Christi', true)
|
||||
]
|
||||
// ── Móveis ──────────────────────────────────────────────
|
||||
feriado(somarDias(pascoa, -48), 'Segunda de Carnaval', true),
|
||||
feriado(somarDias(pascoa, -47), 'Carnaval', true),
|
||||
feriado(somarDias(pascoa, -2), 'Sexta-feira Santa', true),
|
||||
feriado(pascoa, 'Páscoa', true),
|
||||
feriado(somarDias(pascoa, 60), 'Corpus Christi', true)
|
||||
];
|
||||
|
||||
return lista.sort((a, b) => a.data.localeCompare(b.data))
|
||||
return lista.sort((a, b) => a.data.localeCompare(b.data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,7 @@ export function getFeriadosNacionais (ano) {
|
||||
* @param {number} ano
|
||||
* @param {number} mes 1–12
|
||||
*/
|
||||
export function getFeriadosNacionaisDoMes (ano, mes) {
|
||||
const m = String(mes).padStart(2, '0')
|
||||
return getFeriadosNacionais(ano).filter(f => f.data.slice(5, 7) === m)
|
||||
export function getFeriadosNacionaisDoMes(ano, mes) {
|
||||
const m = String(mes).padStart(2, '0');
|
||||
return getFeriadosNacionais(ano).filter((f) => f.data.slice(5, 7) === m);
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
* @param {string} hhmm - Horário no formato "HH:MM"
|
||||
* @returns {string} - Ex: "20260318T100000"
|
||||
*/
|
||||
export function formatGCalDate (date, hhmm = '00:00') {
|
||||
const d = date instanceof Date ? date : new Date(date)
|
||||
const yyyy = d.getFullYear()
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
const [hh, min] = String(hhmm).split(':')
|
||||
return `${yyyy}${mm}${dd}T${String(hh || '00').padStart(2, '0')}${String(min || '00').padStart(2, '0')}00`
|
||||
export function formatGCalDate(date, hhmm = '00:00') {
|
||||
const d = date instanceof Date ? date : new Date(date);
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const [hh, min] = String(hhmm).split(':');
|
||||
return `${yyyy}${mm}${dd}T${String(hh || '00').padStart(2, '0')}${String(min || '00').padStart(2, '0')}00`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,12 +40,14 @@ export function formatGCalDate (date, hhmm = '00:00') {
|
||||
* @param {number} minutes - Minutos a somar
|
||||
* @returns {string} - Ex: "11:00"
|
||||
*/
|
||||
export function addMinutesToHHMM (hhmm, minutes) {
|
||||
const [h, m] = String(hhmm || '00:00').split(':').map(Number)
|
||||
const total = h * 60 + m + (minutes || 0)
|
||||
const endH = Math.floor(total / 60) % 24
|
||||
const endM = total % 60
|
||||
return `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`
|
||||
export function addMinutesToHHMM(hhmm, minutes) {
|
||||
const [h, m] = String(hhmm || '00:00')
|
||||
.split(':')
|
||||
.map(Number);
|
||||
const total = h * 60 + m + (minutes || 0);
|
||||
const endH = Math.floor(total / 60) % 24;
|
||||
const endM = total % 60;
|
||||
return `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,15 +61,15 @@ export function addMinutesToHHMM (hhmm, minutes) {
|
||||
* @param {string} event.end - Data/hora fim no formato YYYYMMDDTHHmmss
|
||||
* @returns {string} URL válida para abrir no Google Calendar
|
||||
*/
|
||||
export function generateGoogleCalendarLink ({ title, description, location, start, end }) {
|
||||
const params = new URLSearchParams({
|
||||
action: 'TEMPLATE',
|
||||
text: title || '',
|
||||
dates: `${start}/${end}`,
|
||||
details: description || '',
|
||||
location: location || '',
|
||||
ctz: 'America/Sao_Paulo',
|
||||
})
|
||||
export function generateGoogleCalendarLink({ title, description, location, start, end }) {
|
||||
const params = new URLSearchParams({
|
||||
action: 'TEMPLATE',
|
||||
text: title || '',
|
||||
dates: `${start}/${end}`,
|
||||
details: description || '',
|
||||
location: location || '',
|
||||
ctz: 'America/Sao_Paulo'
|
||||
});
|
||||
|
||||
return `https://www.google.com/calendar/render?${params.toString()}`
|
||||
return `https://www.google.com/calendar/render?${params.toString()}`;
|
||||
}
|
||||
|
||||
@@ -18,67 +18,60 @@
|
||||
// Menus com `to` em string são extraídos automaticamente.
|
||||
// O menu da Clínica usa route names, então suas páginas são listadas manualmente.
|
||||
|
||||
import therapistMenuRaw from '@/navigation/menus/therapist.menu.js'
|
||||
import saasMenuFn from '@/navigation/menus/saas.menu.js'
|
||||
import therapistMenuRaw from '@/navigation/menus/therapist.menu.js';
|
||||
import saasMenuFn from '@/navigation/menus/saas.menu.js';
|
||||
|
||||
function flattenItems (items = [], groupLabel = '') {
|
||||
const result = []
|
||||
for (const item of items) {
|
||||
const to = item.to
|
||||
if (to && typeof to === 'string' && item.label) {
|
||||
const prefix = groupLabel ? `${groupLabel} · ` : ''
|
||||
result.push({ label: `${prefix}${item.label}`, path: to })
|
||||
function flattenItems(items = [], groupLabel = '') {
|
||||
const result = [];
|
||||
for (const item of items) {
|
||||
const to = item.to;
|
||||
if (to && typeof to === 'string' && item.label) {
|
||||
const prefix = groupLabel ? `${groupLabel} · ` : '';
|
||||
result.push({ label: `${prefix}${item.label}`, path: to });
|
||||
}
|
||||
if (item.items?.length) {
|
||||
result.push(...flattenItems(item.items, item.label || groupLabel));
|
||||
}
|
||||
}
|
||||
if (item.items?.length) {
|
||||
result.push(...flattenItems(item.items, item.label || groupLabel))
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
function flattenMenu (menu = []) {
|
||||
const result = []
|
||||
for (const group of menu) {
|
||||
if (group.items?.length) {
|
||||
result.push(...flattenItems(group.items, group.label))
|
||||
} else if (group.to && typeof group.to === 'string' && group.label) {
|
||||
result.push({ label: group.label, path: group.to })
|
||||
function flattenMenu(menu = []) {
|
||||
const result = [];
|
||||
for (const group of menu) {
|
||||
if (group.items?.length) {
|
||||
result.push(...flattenItems(group.items, group.label));
|
||||
} else if (group.to && typeof group.to === 'string' && group.label) {
|
||||
result.push({ label: group.label, path: group.to });
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
const therapistPages = flattenMenu(therapistMenuRaw)
|
||||
.map(p => ({ ...p, label: `Terapeuta · ${p.label}` }))
|
||||
const therapistPages = flattenMenu(therapistMenuRaw).map((p) => ({ ...p, label: `Terapeuta · ${p.label}` }));
|
||||
|
||||
const saasPages = flattenMenu(saasMenuFn({ isSaasAdmin: true }))
|
||||
.map(p => ({ ...p, label: `SaaS · ${p.label}` }))
|
||||
const saasPages = flattenMenu(saasMenuFn({ isSaasAdmin: true })).map((p) => ({ ...p, label: `SaaS · ${p.label}` }));
|
||||
|
||||
// Clínica usa route names → listagem manual com os paths reais das rotas
|
||||
const clinicPages = [
|
||||
{ label: 'Clínica · Dashboard', path: '/admin' },
|
||||
{ label: 'Clínica · Agenda da Clínica', path: '/admin/agenda/clinica' },
|
||||
{ label: 'Clínica · Compromissos', path: '/admin/agenda/compromissos' },
|
||||
{ label: 'Clínica · Lista de Pacientes', path: '/admin/pacientes' },
|
||||
{ label: 'Clínica · Grupos de Pacientes', path: '/admin/pacientes/grupos' },
|
||||
{ label: 'Clínica · Tags de Pacientes', path: '/admin/pacientes/tags' },
|
||||
{ label: 'Clínica · Link Externo', path: '/admin/pacientes/link-externo' },
|
||||
{ label: 'Clínica · Cadastros Recebidos', path: '/admin/pacientes/cadastro/recebidos' },
|
||||
{ label: 'Clínica · Profissionais', path: '/admin/clinic/professionals' },
|
||||
{ label: 'Clínica · Tipos de Clínica', path: '/admin/clinic/features' },
|
||||
{ label: 'Clínica · Meu Plano', path: '/admin/meu-plano' },
|
||||
{ label: 'Clínica · Segurança', path: '/admin/settings/security' },
|
||||
{ label: 'Clínica · Agendamento Online', path: '/admin/online-scheduling' },
|
||||
]
|
||||
{ label: 'Clínica · Dashboard', path: '/admin' },
|
||||
{ label: 'Clínica · Agenda da Clínica', path: '/admin/agenda/clinica' },
|
||||
{ label: 'Clínica · Compromissos', path: '/admin/agenda/compromissos' },
|
||||
{ label: 'Clínica · Lista de Pacientes', path: '/admin/pacientes' },
|
||||
{ label: 'Clínica · Grupos de Pacientes', path: '/admin/pacientes/grupos' },
|
||||
{ label: 'Clínica · Tags de Pacientes', path: '/admin/pacientes/tags' },
|
||||
{ label: 'Clínica · Link Externo', path: '/admin/pacientes/link-externo' },
|
||||
{ label: 'Clínica · Cadastros Recebidos', path: '/admin/pacientes/cadastro/recebidos' },
|
||||
{ label: 'Clínica · Profissionais', path: '/admin/clinic/professionals' },
|
||||
{ label: 'Clínica · Tipos de Clínica', path: '/admin/clinic/features' },
|
||||
{ label: 'Clínica · Meu Plano', path: '/admin/meu-plano' },
|
||||
{ label: 'Clínica · Segurança', path: '/admin/settings/security' },
|
||||
{ label: 'Clínica · Agendamento Online', path: '/admin/online-scheduling' }
|
||||
];
|
||||
|
||||
const portalPages = [
|
||||
{ label: 'Portal · Dashboard', path: '/portal' },
|
||||
{ label: 'Portal · Minhas Sessões', path: '/portal/sessoes' },
|
||||
]
|
||||
{ label: 'Portal · Dashboard', path: '/portal' },
|
||||
{ label: 'Portal · Minhas Sessões', path: '/portal/sessoes' }
|
||||
];
|
||||
|
||||
export const PAGE_OPTIONS = [
|
||||
...therapistPages,
|
||||
...clinicPages,
|
||||
...saasPages,
|
||||
...portalPages,
|
||||
]
|
||||
export const PAGE_OPTIONS = [...therapistPages, ...clinicPages, ...saasPages, ...portalPages];
|
||||
|
||||
+28
-28
@@ -16,13 +16,13 @@
|
||||
*/
|
||||
|
||||
function toMinutes(hhmm) {
|
||||
const [h, m] = String(hhmm).slice(0, 5).split(':').map(Number)
|
||||
return (h * 60) + m
|
||||
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')}`
|
||||
const h = Math.floor(min / 60);
|
||||
const m = min % 60;
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,34 +33,34 @@ function toHHMM(min) {
|
||||
* 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 passo = Number(regra?.passo_minutos || 60);
|
||||
const offset = Number(regra?.offset_minutos || 0);
|
||||
|
||||
const ativos = (janelas || []).filter(j => j?.ativo !== false)
|
||||
const ativos = (janelas || []).filter((j) => j?.ativo !== false);
|
||||
|
||||
const out = []
|
||||
const out = [];
|
||||
|
||||
for (const j of ativos) {
|
||||
const start = toMinutes(j.hora_inicio)
|
||||
const end = toMinutes(j.hora_fim)
|
||||
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
|
||||
// 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
|
||||
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()
|
||||
// unique + sort
|
||||
return Array.from(new Set(out)).sort();
|
||||
}
|
||||
|
||||
@@ -22,49 +22,59 @@
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'apsi_sys_grp_colors'
|
||||
const STORAGE_KEY = 'apsi_sys_grp_colors';
|
||||
|
||||
/** Cores padrão por nome de grupo de sistema (fallback quando não há cor salva) */
|
||||
export const SYSTEM_GROUP_DEFAULT_COLORS = {
|
||||
'adultos': '#3b82f6',
|
||||
'crianças': '#22c55e',
|
||||
'adolescentes': '#8b5cf6',
|
||||
'casais': '#ec4899',
|
||||
'família': '#f97316',
|
||||
'familias': '#f97316',
|
||||
'famílias': '#f97316',
|
||||
'grupos': '#14b8a6',
|
||||
'idosos': '#64748b',
|
||||
'infantil': '#22c55e',
|
||||
'outro': '#94a3b8',
|
||||
'outros': '#94a3b8',
|
||||
'geral': '#6366f1',
|
||||
'supervisão': '#eab308',
|
||||
'supervisao': '#eab308',
|
||||
}
|
||||
adultos: '#3b82f6',
|
||||
crianças: '#22c55e',
|
||||
adolescentes: '#8b5cf6',
|
||||
casais: '#ec4899',
|
||||
família: '#f97316',
|
||||
familias: '#f97316',
|
||||
famílias: '#f97316',
|
||||
grupos: '#14b8a6',
|
||||
idosos: '#64748b',
|
||||
infantil: '#22c55e',
|
||||
outro: '#94a3b8',
|
||||
outros: '#94a3b8',
|
||||
geral: '#6366f1',
|
||||
supervisão: '#eab308',
|
||||
supervisao: '#eab308'
|
||||
};
|
||||
|
||||
/** Retorna a cor padrão (sem '#' extra) para um grupo de sistema pelo nome */
|
||||
export function getSystemGroupDefaultColor (name) {
|
||||
return SYSTEM_GROUP_DEFAULT_COLORS[String(name || '').trim().toLowerCase()] || '#6366f1'
|
||||
export function getSystemGroupDefaultColor(name) {
|
||||
return (
|
||||
SYSTEM_GROUP_DEFAULT_COLORS[
|
||||
String(name || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
] || '#6366f1'
|
||||
);
|
||||
}
|
||||
|
||||
function readMap () {
|
||||
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') } catch { return {} }
|
||||
function readMap() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function getSysGroupColor (groupId) {
|
||||
if (!groupId) return null
|
||||
return readMap()[groupId] || null
|
||||
export function getSysGroupColor(groupId) {
|
||||
if (!groupId) return null;
|
||||
return readMap()[groupId] || null;
|
||||
}
|
||||
|
||||
export function setSysGroupColor (groupId, color) {
|
||||
if (!groupId) return
|
||||
const map = readMap()
|
||||
if (color) map[groupId] = color.startsWith('#') ? color : `#${color}`
|
||||
else delete map[groupId]
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(map))
|
||||
export function setSysGroupColor(groupId, color) {
|
||||
if (!groupId) return;
|
||||
const map = readMap();
|
||||
if (color) map[groupId] = color.startsWith('#') ? color : `#${color}`;
|
||||
else delete map[groupId];
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(map));
|
||||
}
|
||||
|
||||
export function getAllSysGroupColors () {
|
||||
return readMap()
|
||||
export function getAllSysGroupColors() {
|
||||
return readMap();
|
||||
}
|
||||
|
||||
+31
-31
@@ -22,25 +22,25 @@
|
||||
* - "feature_a,feature_b"
|
||||
* - ["feature_a", "feature_b"] (vue-router pode entregar array)
|
||||
*/
|
||||
export function parseMissingKeys (missing) {
|
||||
if (!missing) return []
|
||||
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)
|
||||
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)
|
||||
// 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
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,26 +48,26 @@ export function parseMissingKeys (missing) {
|
||||
* - 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
|
||||
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()
|
||||
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)
|
||||
if (keys.length) q.set('missing', keys.join(','));
|
||||
if (redirectTo) q.set('redirect', redirectTo);
|
||||
|
||||
const qs = q.toString()
|
||||
return qs ? `/upgrade?${qs}` : '/upgrade'
|
||||
const qs = q.toString();
|
||||
return qs ? `/upgrade?${qs}` : '/upgrade';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user