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
@@ -0,0 +1,101 @@
/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/features/medicos/composables/useMedicos.js
|
| Thin wrapper sobre medicosRepository.
| Pattern: composable-blueprint Tipo A (default).
|--------------------------------------------------------------------------
*/
import { ref } from 'vue';
import { listForOwner, getById, create as repoCreate, update as repoUpdate, softDelete as repoSoftDelete } from '@/features/medicos/services/medicosRepository';
export function useMedicos() {
const rows = ref([]);
const loading = ref(false);
const error = ref('');
async function loadForOwner(opts = {}) {
loading.value = true;
error.value = '';
try {
rows.value = await listForOwner(opts);
} catch (e) {
error.value = e?.message || 'Falha ao carregar médicos.';
rows.value = [];
} finally {
loading.value = false;
}
}
async function fetchById(id, opts = {}) {
loading.value = true;
error.value = '';
try {
return await getById(id, opts);
} catch (e) {
error.value = e?.message || 'Falha ao carregar médico.';
return null;
} finally {
loading.value = false;
}
}
async function create(payload) {
loading.value = true;
error.value = '';
try {
const created = await repoCreate(payload);
// Adiciona na lista local mantendo ordenação por nome
if (created.ativo) {
rows.value = [...rows.value, created].sort((a, b) => (a.nome || '').localeCompare(b.nome || ''));
}
return created;
} catch (e) {
error.value = e?.message || 'Falha ao criar médico.';
throw e;
} finally {
loading.value = false;
}
}
async function update(id, patch, opts = {}) {
loading.value = true;
error.value = '';
try {
const updated = await repoUpdate(id, patch, opts);
const idx = rows.value.findIndex((r) => r.id === id);
if (idx >= 0) {
if (updated.ativo) {
rows.value[idx] = { ...rows.value[idx], ...updated };
} else {
rows.value.splice(idx, 1);
}
}
return updated;
} catch (e) {
error.value = e?.message || 'Falha ao atualizar médico.';
throw e;
} finally {
loading.value = false;
}
}
async function softDelete(id, opts = {}) {
loading.value = true;
error.value = '';
try {
await repoSoftDelete(id, opts);
rows.value = rows.value.filter((r) => r.id !== id);
return true;
} catch (e) {
error.value = e?.message || 'Falha ao remover médico.';
throw e;
} finally {
loading.value = false;
}
}
return { rows, loading, error, loadForOwner, fetchById, create, update, softDelete };
}