M1: features/medicos + features/insurance + ComponentCadastroRapido refactor

Modulo 1 da Fase 1 de padronizacao. Novos features/medicos (services
+ composable useMedicos) e features/insurance (idem). 3 cadastros
rapidos (medicos, convenios, ComponentCadastroRapido + Insurance
PlanQuickCreateDialog) migrados pra usar os composables novos —
zero supabase.from() em UI components. TEST_ACCOUNTS extraido pra
src/config/devTestAccounts.js. Topbar ganhou switcher de layout
+ atalhos M1 via novo useTopbarDevMenuExtras. M1.6 MelissaLayout
90 imports deferida pra sessao dedicada (memoria padronizacao_sweep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-21 04:19:57 -03:00
parent f94a4ae97f
commit 27467bbb68
17 changed files with 901 additions and 223 deletions
+134
View File
@@ -0,0 +1,134 @@
/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/composables/useTopbarDevMenuExtras.js
|
| Extras DEV-only que aparecem dentro do mesmo botão "sliders" do topbar
| (junto com o switcher de planos do useTopbarPlanMenu). Adiciona:
|
| 1) Switcher de layout (rail | melissa) — UPDATE em user_settings +
| localStorage + hard reload pra router decidir redirect.
|
| 2) Atalhos rápidos pra testar M1.3 (ComponentCadastroRapido nos
| diversos callers) e M1.1/M1.2 (CadastroRapidoMedico/Convenio).
|
| Visibilidade: assume que o caller só renderiza o menu se `showPlanDevMenu`
| já estiver true (DEV + permissão). Não duplica essa lógica aqui.
|
| Uso em AppTopbar.vue / MelissaLayout.vue:
| const { devExtrasModel } = useTopbarDevMenuExtras();
| const combinedDevMenuModel = computed(() => [
| ...planMenuModel.value,
| { separator: true },
| ...devExtrasModel.value
| ]);
| <Menu :model="combinedDevMenuModel" ... />
|--------------------------------------------------------------------------
*/
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import { supabase } from '@/lib/supabase/client';
import { useLayout } from '@/layout/composables/layout';
export function useTopbarDevMenuExtras() {
const router = useRouter();
const toast = useToast();
const { layoutConfig } = useLayout();
const currentVariant = computed(() => layoutConfig.variant || 'classic');
const isRailLike = computed(() => currentVariant.value === 'rail' || currentVariant.value === 'classic');
const isMelissa = computed(() => currentVariant.value === 'melissa');
async function setLayoutAndReload(variant) {
try {
const { data, error: authErr } = await supabase.auth.getUser();
if (authErr) throw authErr;
const uid = data?.user?.id;
if (!uid) throw new Error('Sem sessão.');
const { error } = await supabase
.from('user_settings')
.upsert(
{
user_id: uid,
layout_variant: variant,
updated_at: new Date().toISOString()
},
{ onConflict: 'user_id' }
);
if (error) throw error;
// Fast path: router.beforeEach/guards lêem localStorage antes do fetch
try {
localStorage.setItem('layout_variant', variant);
} catch (_) {
// ignore (Safari private mode etc.)
}
toast.add({ severity: 'success', summary: `Layout: ${variant}`, life: 1200 });
// Hard reload pra router redirecionar pra raiz correta
setTimeout(() => window.location.assign('/'), 250);
} catch (e) {
toast.add({
severity: 'error',
summary: 'Erro ao trocar layout',
detail: e?.message || 'Falha desconhecida.',
life: 4500
});
}
}
function goto(path) {
// router.push em vez de location.assign — mantém SPA + dev tools abertos.
// Se o guard redirecionar (ex: Melissa mode bloqueia /therapist/...),
// o usuário verá o redirect — sinal de que precisa trocar layout primeiro.
router.push(path).catch(() => {});
}
const devExtrasModel = computed(() => [
// ─── Layout ────────────────────────────────────────
{ label: 'Layout (DEV)', icon: 'pi pi-th-large', disabled: true },
{
label: isRailLike.value ? 'Rail (atual)' : 'Rail',
icon: isRailLike.value ? 'pi pi-check' : 'pi pi-bars',
disabled: isRailLike.value,
command: () => setLayoutAndReload('rail')
},
{
label: isMelissa.value ? 'Melissa (atual)' : 'Melissa',
icon: isMelissa.value ? 'pi pi-check' : 'pi pi-window-maximize',
disabled: isMelissa.value,
command: () => setLayoutAndReload('melissa')
},
{ separator: true },
// ─── Atalhos pra testar Módulo 1 ───────────────────
{ label: 'Testar Módulo 1 (DEV)', icon: 'pi pi-bullseye', disabled: true },
{
label: '→ Cadastro Paciente (M1.1 + M1.2)',
icon: 'pi pi-user-plus',
command: () => goto('/therapist/patients/cadastro')
},
{
label: '→ Lista de Pacientes (M1.3-E)',
icon: 'pi pi-list',
command: () => goto('/therapist/patients')
},
{
label: '→ Melissa Agenda (M1.3-B)',
icon: 'pi pi-calendar',
command: () => goto('/melissa/agenda')
},
{
label: '→ Melissa Pacientes (M1.3-B)',
icon: 'pi pi-users',
command: () => goto('/melissa/pacientes')
}
]);
return { devExtrasModel };
}