Melissa polish + Prontuario Visao Geral + agenda historico
Sprints B (05-03) e C (05-04) acumulados: - NotificationDrawer/Item redesign (visual mais limpo, ações inline) - Dock pins compose (useMelissaDockPins) + cache store global (melissaCacheStore) - MelissaAgenda: timeline FullCalendar parity + cards resumo, histórico card com useMelissaAgendaHistorico, MelissaEventoPanel ajustado - useFeriados: cache opt-in pra evitar fetch redundante de feriados - PatientProntuario: aba Visão Geral nova; PatientConversationsTab polish - AgendaClinicMosaic / AgendaTerapeutaPage / useAgendaSettings: ajustes de paridade com Melissa - DocumentsListPage: pequenos ajustes - DB migration 20260504000001: fix do trigger pra status 'excluido' nas cancel_notifications Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,10 +25,9 @@ import ptBrLocale from '@fullcalendar/core/locales/pt-br';
|
||||
import { useMelissaEventosRange, useMelissaTodasSessoesPaciente, searchEventosByText } from './composables/useMelissaEventos';
|
||||
import { MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda';
|
||||
import { useMelissaPacientesAside } from './composables/useMelissaPacientesAside';
|
||||
import { useFeriados } from '@/composables/useFeriados';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useAgendaSettings } from '@/features/agenda/composables/useAgendaSettings';
|
||||
import ProximosFeriadosCard from '@/features/agenda/components/ProximosFeriadosCard.vue';
|
||||
import MelissaAgendaHistoricoCard from './MelissaAgendaHistoricoCard.vue';
|
||||
import PatientCreatePopover from '@/components/ui/PatientCreatePopover.vue';
|
||||
import Popover from 'primevue/popover';
|
||||
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
|
||||
@@ -531,11 +530,15 @@ const fcOptions = computed(() => ({
|
||||
eventDrop: (info) => {
|
||||
if (!M) { info.revert?.(); return; }
|
||||
M.persistMoveOrResize(info, 'Sessão movida');
|
||||
// audit_logs grava no AFTER trigger; pequeno delay garante que
|
||||
// a query do histórico já pegue a entrada nova.
|
||||
setTimeout(() => historicoCardRef.value?.refetch(), 700);
|
||||
},
|
||||
// Resize → muda duração da sessão
|
||||
eventResize: (info) => {
|
||||
if (!M) { info.revert?.(); return; }
|
||||
M.persistMoveOrResize(info, 'Duração alterada');
|
||||
setTimeout(() => historicoCardRef.value?.refetch(), 700);
|
||||
},
|
||||
// Click-drag em área vazia → abre dialog pra criar evento novo, com
|
||||
// start/end pré-preenchidos. AgendaEventDialog cuida do resto (seleção
|
||||
@@ -547,8 +550,18 @@ const fcOptions = computed(() => ({
|
||||
eventContent: (arg) => {
|
||||
const ext = arg.event.extendedProps || {};
|
||||
const titulo = arg.event.title || '—';
|
||||
const time = arg.timeText || '';
|
||||
const isSessao = String(ext.tipo || '').toLowerCase() === 'sessao';
|
||||
|
||||
const fmtHour = (d) => {
|
||||
if (!d) return '';
|
||||
const h = d.getHours();
|
||||
const m = d.getMinutes();
|
||||
return m === 0 ? `${h}h` : `${h}:${String(m).padStart(2, '0')}h`;
|
||||
};
|
||||
const range = arg.event.start && arg.event.end
|
||||
? `${fmtHour(arg.event.start)}-${fmtHour(arg.event.end)}`
|
||||
: (arg.timeText || '');
|
||||
|
||||
// Badges só pra sessões — compromissos pessoais/bloqueios/feriados
|
||||
// não têm status nem modalidade relevantes pra exibir.
|
||||
let badgesHtml = '';
|
||||
@@ -567,11 +580,11 @@ const fcOptions = computed(() => ({
|
||||
// Pra eventos não-sessão (compromisso, bloqueio etc.) mantém o
|
||||
// antigo `__meta` com modalidade ou título secundário.
|
||||
const metaFallback = !isSessao ? (ext.modalidade || ext.titulo || '') : '';
|
||||
const titleLine = `<div class="mc-fc-event__title"><span class="mc-fc-event__name">${escHtml(titulo)}</span>${range ? ` <span class="mc-fc-event__hour">(${escHtml(range)})</span>` : ''}</div>`;
|
||||
return {
|
||||
html: `
|
||||
<div class="mc-fc-event">
|
||||
<div class="mc-fc-event__time">${escHtml(time)}</div>
|
||||
<div class="mc-fc-event__title">${escHtml(titulo)}</div>
|
||||
${titleLine}
|
||||
${badgesHtml}
|
||||
${metaFallback ? `<div class="mc-fc-event__meta">${escHtml(metaFallback)}</div>` : ''}
|
||||
</div>
|
||||
@@ -684,6 +697,43 @@ function irParaData(date) {
|
||||
searchDateMatch.value = null;
|
||||
}
|
||||
|
||||
// Card de histórico (audit_logs) — ref pra disparar refetch após
|
||||
// mutações; handler que abre o evento clicado pelo id.
|
||||
const historicoCardRef = ref(null);
|
||||
async function onHistoricoOpen({ id }) {
|
||||
if (!id) return;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
.select('*, patients!agenda_eventos_patient_id_fkey(nome_completo, status, avatar_url)')
|
||||
.eq('id', id)
|
||||
.maybeSingle();
|
||||
if (error || !data) {
|
||||
// Evento pode ter sido deletado depois da entrada — fail-soft.
|
||||
return;
|
||||
}
|
||||
// Foca no dia + emite seleção pro MelissaLayout abrir o panel.
|
||||
const ev = {
|
||||
...data,
|
||||
patient_id: data.patient_id,
|
||||
paciente_nome: data.patients?.nome_completo || '',
|
||||
paciente_status: data.patients?.status || '',
|
||||
paciente_avatar: data.patients?.avatar_url || '',
|
||||
startH: new Date(data.inicio_em).getHours() + new Date(data.inicio_em).getMinutes() / 60,
|
||||
endH: new Date(data.fim_em).getHours() + new Date(data.fim_em).getMinutes() / 60,
|
||||
label: data.patients?.nome_completo || data.titulo || data.titulo_custom || '—'
|
||||
};
|
||||
if (data.inicio_em) {
|
||||
fcApi()?.gotoDate(data.inicio_em);
|
||||
refDate.value = new Date(data.inicio_em);
|
||||
}
|
||||
emit('select-evento', ev);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[onHistoricoOpen]', e);
|
||||
}
|
||||
}
|
||||
|
||||
function onSelecionarResultado(ev) {
|
||||
if (!ev?.inicio_em) return;
|
||||
fcApi()?.gotoDate(ev.inicio_em);
|
||||
@@ -764,12 +814,15 @@ const miniRefDate = ref(new Date());
|
||||
|
||||
// ── Feriados (nacionais via algoritmo + municipais/personalizados via DB)
|
||||
// + Jornada semanal (workRules) pra marcar dias fechados em cinza no mini-cal.
|
||||
// Pattern espelha AgendaTerapeutaPage:113,129-134,1143.
|
||||
// IMPORTANTE: declarado APÓS miniRefDate porque o watch abaixo lê
|
||||
// miniRefDate.value durante o setup (rastreio de dependências).
|
||||
// Reusa as refs do composable injetado M — antes essa página instanciava
|
||||
// novamente useFeriados() e useAgendaSettings(), gerando duplicação de
|
||||
// queries (feriados municipais + agenda_configuracoes + agenda_regras).
|
||||
const tenantStore = useTenantStore();
|
||||
const { todos: feriadosTodos, load: loadFeriados, ano: feriadosAno, fcEvents: feriadoFcEvents } = useFeriados();
|
||||
const { workRules, load: loadAgendaSettings } = useAgendaSettings();
|
||||
const feriadosTodos = M.feriados;
|
||||
const feriadoFcEvents = M.feriadoFcEvents;
|
||||
const feriadosAno = M.feriadosAno;
|
||||
const loadFeriados = M.loadFeriadosBase;
|
||||
const workRules = M.workRules;
|
||||
|
||||
// Set de dias da semana ativos (0=dom..6=sáb). Fallback seg-sex se sem regras
|
||||
// — mesmo default do AgendaTerapeutaPage:370.
|
||||
@@ -779,15 +832,10 @@ const workDowSet = computed(() => {
|
||||
return new Set([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const tid = tenantStore.activeTenantId || tenantStore.tenantId || tenantStore.tenant?.id || null;
|
||||
if (tid) loadFeriados(tid, new Date().getFullYear());
|
||||
// workRules é por owner_id (RLS), não precisa do tenant
|
||||
loadAgendaSettings();
|
||||
});
|
||||
|
||||
// Recarrega feriados quando mini-cal navega pra outro ano (municipais variam).
|
||||
// Nacionais são puro algoritmo — recomputam automático no useFeriados via `ano`.
|
||||
// Carga inicial de feriados/settings já é feita pelo useMelissaAgenda no
|
||||
// mount (watch immediate em clinicTenantId + loadSettings paralelo). Não
|
||||
// duplicamos aqui — só mantemos o reload de feriados quando o mini-cal
|
||||
// navega pra outro ano (municipais variam por ano).
|
||||
watch(
|
||||
() => miniRefDate.value.getFullYear(),
|
||||
(novoAno) => {
|
||||
@@ -1080,6 +1128,21 @@ function abrirSessoesPaciente() {
|
||||
verTodasSessoes.value = true;
|
||||
fetchTodasSessoes(pacienteSelecionadoId.value);
|
||||
}
|
||||
|
||||
// API pública pro MelissaLayout (botão "Sessões" do MelissaEventoPanel):
|
||||
// seleciona o paciente e abre o overlay "Todas as sessões" no mesmo
|
||||
// fluxo do .ma-dock-actions. Importante: setar pacienteSelecionadoId
|
||||
// ANTES de verTodasSessoes — o watch logo abaixo reseta verTodasSessoes
|
||||
// quando pacienteSelecionadoId muda, então fazemos a ordem inversa.
|
||||
function openSessoesPaciente(patientId) {
|
||||
if (!patientId) return;
|
||||
const id = String(patientId);
|
||||
if (pacienteSelecionadoId.value !== id) {
|
||||
pacienteSelecionadoId.value = id;
|
||||
}
|
||||
verTodasSessoes.value = true;
|
||||
fetchTodasSessoes(id);
|
||||
}
|
||||
function voltarParaPeriodo() {
|
||||
verTodasSessoes.value = false;
|
||||
resetTodasSessoes();
|
||||
@@ -1125,6 +1188,14 @@ function abrirProntuarioPaciente() {
|
||||
prontuarioPatient.value = { ...p };
|
||||
prontuarioOpen.value = true;
|
||||
}
|
||||
// API pública pra MelissaLayout chamar via ref (botão "Editar paciente"
|
||||
// do MelissaEventoPanel). Abre o PatientCadastroDialog já no modo edição.
|
||||
function openEditPatient(patientId) {
|
||||
if (!patientId) return;
|
||||
editPatientId.value = String(patientId);
|
||||
cadastroFullDialog.value = true;
|
||||
}
|
||||
|
||||
function editarPacienteSelecionado() {
|
||||
if (!pacienteSelecionadoId.value) return;
|
||||
editPatientId.value = String(pacienteSelecionadoId.value);
|
||||
@@ -1171,7 +1242,9 @@ function openProntuario(patient) {
|
||||
defineExpose({
|
||||
refetch: refetchEventosFc,
|
||||
openProntuario,
|
||||
setView
|
||||
setView,
|
||||
openSessoesPaciente,
|
||||
openEditPatient
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1887,6 +1960,14 @@ defineExpose({
|
||||
@bloqueado="onFeriadoBloqueado"
|
||||
/>
|
||||
|
||||
<!-- Histórico de ações na agenda (audit_logs) — útil pra
|
||||
rastrear movimentações recentes. Click na entrada
|
||||
abre o evento (se ainda existe). -->
|
||||
<MelissaAgendaHistoricoCard
|
||||
ref="historicoCardRef"
|
||||
@open-evento="onHistoricoOpen"
|
||||
/>
|
||||
|
||||
</aside>
|
||||
</Teleport>
|
||||
</div>
|
||||
@@ -3210,22 +3291,26 @@ html.app-dark .ma-tsearch__result--date .ma-tsearch__result-sub {
|
||||
color: var(--m-text);
|
||||
font-family: inherit;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event__time) {
|
||||
font-size: 0.6rem;
|
||||
color: var(--m-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event__title) {
|
||||
/* Mesmo tamanho/peso da .ma-pat__name (lista de pacientes) pra
|
||||
alinhar a hierarquia visual entre aside e calendário. */
|
||||
alinhar a hierarquia visual entre aside e calendário.
|
||||
Nome + hora em linha única; ellipsis corta o nome antes da hora. */
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event__name) {
|
||||
font-weight: 500;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event__hour) {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
color: var(--m-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event__meta) {
|
||||
font-size: 0.6rem;
|
||||
@@ -3495,6 +3580,11 @@ html:not(.app-dark) .ma-cal__fc :deep(.mc-fc-event__badge--modal.is-presencial)
|
||||
background: var(--m-bg-medium) !important;
|
||||
border-color: var(--m-border) !important;
|
||||
border-radius: 12px !important;
|
||||
min-height: 158px;
|
||||
/* flex: 0 0 auto — toma o tamanho natural do conteúdo (incluindo
|
||||
expansão da confirmação inline) e NÃO encolhe quando o histórico
|
||||
crescer. O histórico (flex: 1 abaixo) absorve o restante. */
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
:deep(.ma-w-feriados .border-b) {
|
||||
border-color: var(--m-border);
|
||||
|
||||
Reference in New Issue
Block a user