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
@@ -18,18 +18,19 @@
<script setup>
import { ref, watch } from 'vue';
import { useToast } from 'primevue/usetoast';
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
import { useInsurancePlans } from '@/features/insurance/composables/useInsurancePlans';
const props = defineProps({
modelValue: { type: Boolean, default: false },
// ownerId mantido por compat — repository sempre injeta owner_id = auth.uid() logado.
// Nos fluxos atuais (AgendaEventDialog), o usuário logado já é o owner.
ownerId: { type: String, default: '' },
initialName: { type: String, default: '' }
});
const emit = defineEmits(['update:modelValue', 'created']);
const toast = useToast();
const tenantStore = useTenantStore();
const insuranceStore = useInsurancePlans();
const visible = ref(props.modelValue);
watch(() => props.modelValue, (v) => { visible.value = v; });
@@ -56,29 +57,25 @@ const canSave = () => !!form.value.name?.trim();
async function onSave() {
if (!canSave()) return;
const ownerId = props.ownerId || (await supabase.auth.getUser()).data?.user?.id;
const tid = tenantStore.activeTenantId || tenantStore.tenantId;
if (!ownerId || !tid) {
toast.add({ severity: 'error', summary: 'Sem contexto', detail: 'Owner ou tenant ausentes.', life: 3500 });
return;
}
saving.value = true;
try {
const payload = {
owner_id: ownerId,
tenant_id: tid,
name: form.value.name.trim().slice(0, 120),
default_value: form.value.default_value != null ? Number(form.value.default_value) : null,
notes: form.value.notes?.trim().slice(0, 500) || null,
active: true
};
const { data, error } = await supabase.from('insurance_plans').insert(payload).select().single();
if (error) throw error;
// Repository injeta owner_id + tenant_id, sanitiza, e faz uniqueness check.
const data = await insuranceStore.create({
name: form.value.name,
default_value: form.value.default_value,
notes: form.value.notes
});
toast.add({ severity: 'success', summary: 'Convênio criado', life: 2200 });
emit('created', data);
visible.value = false;
} catch (e) {
toast.add({ severity: 'error', summary: 'Falha ao criar convênio', detail: e?.message || 'Erro inesperado', life: 4000 });
const isDup = /existe um convênio/i.test(e?.message || '');
toast.add({
severity: isDup ? 'warn' : 'error',
summary: isDup ? 'Nome em uso' : 'Falha ao criar convênio',
detail: e?.message || 'Erro inesperado',
life: 4000
});
} finally {
saving.value = false;
}