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:
@@ -309,7 +309,6 @@ function buildFcOptions(ownerId) {
|
||||
const nome = ext.paciente_nome || '';
|
||||
const obs = ext.observacoes || '';
|
||||
const title = arg.event.title || '';
|
||||
const timeText = arg.timeText || '';
|
||||
const pacienteStatus = ext.paciente_status || '';
|
||||
|
||||
const esc = (s) =>
|
||||
@@ -326,21 +325,28 @@ function buildFcOptions(ownerId) {
|
||||
return (p[0][0] + p[p.length - 1][0]).toUpperCase();
|
||||
};
|
||||
|
||||
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 || '';
|
||||
|
||||
const avatarHtml = avatarUrl ? `<img src="${esc(avatarUrl)}" class="ev-avatar ev-avatar-img" />` : nome ? `<div class="ev-avatar ev-avatar-initials">${esc(initials(nome))}</div>` : '';
|
||||
|
||||
const obsHtml = obs ? `<div class="ev-obs">${esc(obs)}</div>` : '';
|
||||
const timeHtml = timeText ? `<div class="ev-time">${esc(timeText)}</div>` : '';
|
||||
const titleLine = `<div class="ev-title"><span class="ev-name">${esc(title)}</span>${range ? ` <span class="ev-hour">(${esc(range)})</span>` : ''}</div>`;
|
||||
const statusBadge =
|
||||
pacienteStatus === 'Inativo' || pacienteStatus === 'Arquivado'
|
||||
? `<span style="display:inline-block;background:#f97316;color:#fff;font-size:9px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;padding:1px 5px;border-radius:3px;line-height:1.4;margin-top:2px;">${pacienteStatus === 'Arquivado' ? 'paciente arquivado' : 'paciente desativado'}</span>`
|
||||
? `<span class="ev-badge">${pacienteStatus === 'Arquivado' ? 'paciente arquivado' : 'paciente desativado'}</span>`
|
||||
: '';
|
||||
|
||||
return {
|
||||
html: `<div class="ev-custom">
|
||||
${avatarHtml}
|
||||
<div class="ev-body">
|
||||
${timeHtml}
|
||||
<div class="ev-title">${esc(title)}</div>
|
||||
${titleLine}
|
||||
${statusBadge}
|
||||
${obsHtml}
|
||||
</div>
|
||||
@@ -475,20 +481,34 @@ function buildFcOptions(ownerId) {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ev-time {
|
||||
font-size: 10px;
|
||||
opacity: 0.8;
|
||||
.ev-title {
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ev-title {
|
||||
font-size: 11px;
|
||||
.ev-name {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.ev-hour {
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
opacity: 0.75;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.ev-badge {
|
||||
display: inline-block;
|
||||
background: #f97316;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
line-height: 1.4;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.ev-obs {
|
||||
font-size: 10px;
|
||||
|
||||
@@ -16,20 +16,53 @@
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import { getMyAgendaSettings, getMyWorkSchedule } from '../services/agendaRepository';
|
||||
import { useMelissaCacheStore, MELISSA_CACHE_TTL } from '@/stores/melissaCacheStore';
|
||||
|
||||
// opts.cache (opt-in): habilita stale-while-revalidate via melissaCacheStore.
|
||||
// Default false pra preservar comportamento de páginas de configuração que
|
||||
// editam settings/workRules (esperam ver mudança imediata após salvar).
|
||||
export function useAgendaSettings(opts = {}) {
|
||||
const useCache = !!opts.cache;
|
||||
const cache = useCache ? useMelissaCacheStore() : null;
|
||||
|
||||
export function useAgendaSettings() {
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
const settings = ref(null);
|
||||
const workRules = ref([]); // [{ dia_semana, hora_inicio, hora_fim }]
|
||||
|
||||
async function _doFetch() {
|
||||
const [cfg, rules] = await Promise.all([getMyAgendaSettings(), getMyWorkSchedule()]);
|
||||
settings.value = cfg;
|
||||
workRules.value = rules;
|
||||
if (cache) {
|
||||
// Cache key inclui owner_id da config — invalida automaticamente
|
||||
// se o usuário trocar (multi-tenant ou alternar entre staff).
|
||||
const key = cfg?.owner_id || 'anon';
|
||||
cache.set('agendaSettings', { settings: cfg, workRules: rules }, key);
|
||||
}
|
||||
return { settings: cfg, workRules: rules };
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (cache) {
|
||||
// Sem owner_id ainda, key vira 'anon' — pega qualquer cache
|
||||
// do mesmo escopo (que normalmente é o user logado).
|
||||
const cached = cache.get('agendaSettings', undefined, MELISSA_CACHE_TTL.agendaSettings);
|
||||
if (cached) {
|
||||
settings.value = cached.settings;
|
||||
workRules.value = cached.workRules;
|
||||
_doFetch().catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[useAgendaSettings] revalidate', e);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const [cfg, rules] = await Promise.all([getMyAgendaSettings(), getMyWorkSchedule()]);
|
||||
settings.value = cfg;
|
||||
workRules.value = rules;
|
||||
await _doFetch();
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao carregar configurações da agenda.';
|
||||
settings.value = null;
|
||||
|
||||
@@ -742,7 +742,6 @@ const fcOptions = computed(() => ({
|
||||
const nome = ext.paciente_nome || '';
|
||||
const obs = ext.observacoes || '';
|
||||
const title = arg.event.title || '';
|
||||
const timeText = arg.timeText || '';
|
||||
const pacienteStatus = ext.paciente_status || '';
|
||||
|
||||
const esc = (s) =>
|
||||
@@ -759,21 +758,28 @@ const fcOptions = computed(() => ({
|
||||
return (p[0][0] + p[p.length - 1][0]).toUpperCase();
|
||||
};
|
||||
|
||||
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 || '';
|
||||
|
||||
const avatarHtml = avatarUrl ? `<img src="${esc(avatarUrl)}" class="ev-avatar ev-avatar-img" />` : nome ? `<div class="ev-avatar ev-avatar-initials">${esc(initials(nome))}</div>` : '';
|
||||
|
||||
const obsHtml = obs ? `<div class="ev-obs">${esc(obs)}</div>` : '';
|
||||
const timeHtml = timeText ? `<div class="ev-time">${esc(timeText)}</div>` : '';
|
||||
const titleLine = `<div class="ev-title"><span class="ev-name">${esc(title)}</span>${range ? ` <span class="ev-hour">(${esc(range)})</span>` : ''}</div>`;
|
||||
const inativoBadge =
|
||||
pacienteStatus === 'Inativo' || pacienteStatus === 'Arquivado'
|
||||
? `<span style="display:inline-block;background:#f97316;color:#fff;font-size:9px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;padding:1px 5px;border-radius:3px;line-height:1.4;margin-top:2px;">${pacienteStatus === 'Arquivado' ? 'paciente arquivado' : 'paciente desativado'}</span>`
|
||||
? `<span class="ev-badge">${pacienteStatus === 'Arquivado' ? 'paciente arquivado' : 'paciente desativado'}</span>`
|
||||
: '';
|
||||
|
||||
return {
|
||||
html: `<div class="ev-custom">
|
||||
${avatarHtml}
|
||||
<div class="ev-body">
|
||||
${timeHtml}
|
||||
<div class="ev-title">${esc(title)}</div>
|
||||
${titleLine}
|
||||
${inativoBadge}
|
||||
${obsHtml}
|
||||
</div>
|
||||
@@ -3424,20 +3430,34 @@ onBeforeUnmount(() => {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ev-time {
|
||||
font-size: 10px;
|
||||
opacity: 0.8;
|
||||
.ev-title {
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ev-title {
|
||||
font-size: 11px;
|
||||
.ev-name {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.ev-hour {
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
opacity: 0.75;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.ev-badge {
|
||||
display: inline-block;
|
||||
background: #f97316;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
line-height: 1.4;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.ev-obs {
|
||||
font-size: 10px;
|
||||
|
||||
Reference in New Issue
Block a user