melissa/agenda: view lista 2 anos + selector SelectButton + sticky header + visual inativo
View lista: 'listWeek' -> custom 'listAll' (duration { years: 2 },
centrada em hoje via gotoDate(hoje - 1 ano) no setView). Antes a lista
mostrava so 7 dias e ocultava 3 das 4 ocorrencias semanais — agora cobre
passado + presente + futuro numa varredura. Cap MAX_RANGE_DAYS=730 do
loadAndExpand bate exato com 2 anos.
Banner showRecurrenceHint: aparece quando ha virtuais visiveis em
day/week/month (nao mostra em listAll). Texto curto + botao "Ver na
lista" que chama setView('lista').
Sticky day header (.fc-list-day): adicionado position:relative + z-index 3
+ bg opaco. Sem isso, .fc-event passava POR CIMA do header conforme
scroll (stacking context da row de evento ganhava do cushion sem z-index).
View selector: botoes manuais (.ma-cal__view-btn) -> PrimeVue SelectButton.
Visual herdado do tema, menos CSS custom.
Visual evento inativo: classNames=['ma-evt--inactive-patient'] em fcEvents
quando paciente_status === 'Arquivado'|'Inativo'. CSS aplica borda
tracejada + opacidade 0.58 (italico em list view). Mantem a cor do
commitment pra preservar contexto.
FC touch defaults: adiciona spread de FC_TOUCH_DEFAULTS (utility commitada
antes) — paridade touch <-> mouse, tap dispara select na hora em vez de
exigir long-press de 1000ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import listPlugin from '@fullcalendar/list';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import ptBrLocale from '@fullcalendar/core/locales/pt-br';
|
||||
import { FC_TOUCH_DEFAULTS } from '@/features/agenda/utils/fcDefaults';
|
||||
import { useMelissaEventosRange, useMelissaTodasSessoesPaciente } from './composables/useMelissaEventos';
|
||||
import { MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda';
|
||||
import { useMelissaPacientesAside } from './composables/useMelissaPacientesAside';
|
||||
@@ -244,7 +245,11 @@ const VIEW_MAP = {
|
||||
dia: 'timeGridDay',
|
||||
semana: 'timeGridWeek',
|
||||
mes: 'dayGridMonth',
|
||||
lista: 'listWeek'
|
||||
// listAll é view custom (configurada em fcOptions.views) — cobre 2 anos
|
||||
// (1 ano antes + 1 ano depois de hoje). Substituiu listWeek que mostrava
|
||||
// só 7 dias e escondia recorrências semanais/quinzenais futuras. Cap do
|
||||
// loadAndExpand é 730d (MAX_RANGE_DAYS), 2 anos cai exatamente no limite.
|
||||
lista: 'listAll'
|
||||
};
|
||||
|
||||
const fcRef = ref(null);
|
||||
@@ -384,6 +389,11 @@ const fcEvents = computed(() => {
|
||||
const evPid = ev.patient_id || ev.paciente_id;
|
||||
if (onlySess && !evPid) continue;
|
||||
if (pacienteId && evPid !== pacienteId) continue;
|
||||
// Eventos cujo paciente foi arquivado/desativado ganham classe
|
||||
// visual ("inativo") — borda tracejada + opacidade reduzida no CSS
|
||||
// abaixo. Mantem a cor do commitment pra nao perder contexto.
|
||||
const pStatus = ev.paciente_status;
|
||||
const isInactivePatient = pStatus === 'Arquivado' || pStatus === 'Inativo';
|
||||
out.push({
|
||||
id: ev.id,
|
||||
title: ev.label,
|
||||
@@ -392,6 +402,7 @@ const fcEvents = computed(() => {
|
||||
backgroundColor: `${ev.color}26`, // ~15% opacity
|
||||
borderColor: ev.color,
|
||||
textColor: 'white',
|
||||
classNames: isInactivePatient ? ['ma-evt--inactive-patient'] : undefined,
|
||||
extendedProps: ev
|
||||
});
|
||||
}
|
||||
@@ -403,6 +414,19 @@ const fcEvents = computed(() => {
|
||||
return out;
|
||||
});
|
||||
|
||||
// Hint pra trocar pra view 'lista' quando há ocorrências de recorrência
|
||||
// visíveis em view dia/semana/mês. Cada view dessas cobre janela curta
|
||||
// (1d / 7d / ~35d) — séries semanais com 4+ ocorrências sempre extrapolam.
|
||||
// Lista cobre 2 anos centrada no usuário, então mostra passado/presente/futuro.
|
||||
// Não mostra no modo "Ver todas" (já tá em modo lista paralela) nem na própria
|
||||
// view lista. Some quando nenhum virtual aparece (sem recorrência ativa visível).
|
||||
const showRecurrenceHint = computed(() => {
|
||||
if (calendarView.value === 'lista') return false;
|
||||
if (verTodasSessoes.value) return false;
|
||||
if (!eventosSemana.value?.length) return false;
|
||||
return eventosSemana.value.some((ev) => ev.is_occurrence);
|
||||
});
|
||||
|
||||
// ── slotMinTime / slotMaxTime baseado em timeMode ─────────────
|
||||
// 24: 00–24h. 12: 06–18h. my: range das workRules (snap em 30min).
|
||||
function _hhmmToMin(t) {
|
||||
@@ -492,11 +516,22 @@ function slotLabelContent(arg) {
|
||||
|
||||
const fcOptions = computed(() => ({
|
||||
plugins: [timeGridPlugin, dayGridPlugin, listPlugin, interactionPlugin],
|
||||
...FC_TOUCH_DEFAULTS,
|
||||
locale: ptBrLocale,
|
||||
headerToolbar: false, // toolbar é nossa (custom glass)
|
||||
initialView: VIEW_MAP[calendarView.value] || 'timeGridWeek',
|
||||
initialDate: refDate.value,
|
||||
nowIndicator: true,
|
||||
// View custom "listAll": list view cobrindo 2 anos. Usada quando user clica
|
||||
// o toggle "Lista" — exibe passadas + presentes + futuras numa varredura só.
|
||||
// setView('lista') faz gotoDate(hoje - 1 ano) pra centrar.
|
||||
views: {
|
||||
listAll: {
|
||||
type: 'list',
|
||||
duration: { years: 2 },
|
||||
buttonText: 'Lista'
|
||||
}
|
||||
},
|
||||
// Drag/resize/select habilitam apenas com M (composable disponível) —
|
||||
// standalone (sem M) fica readonly por compat (preview puro).
|
||||
editable: !!M,
|
||||
@@ -696,6 +731,14 @@ function goToday() { fcApi()?.today(); }
|
||||
function setView(v) {
|
||||
calendarView.value = v;
|
||||
fcApi()?.changeView(VIEW_MAP[v]);
|
||||
// Lista cobre 2 anos — abrimos centrado: pula pra (hoje - 1 ano) pra
|
||||
// mostrar passado + presente + futuro de uma vez. Outras views mantém
|
||||
// o refDate atual (datesSet sincroniza viewStart/End normalmente).
|
||||
if (v === 'lista') {
|
||||
const umAnoAtras = new Date();
|
||||
umAnoAtras.setFullYear(umAnoAtras.getFullYear() - 1);
|
||||
fcApi()?.gotoDate(umAnoAtras);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Menu de Bloqueio (toolbar) ─────────────────────────────────
|
||||
@@ -1574,16 +1617,21 @@ defineExpose({
|
||||
@bloqueio="onActionsBloqueio"
|
||||
/>
|
||||
|
||||
<div class="ma-cal__view ma-cal__view--xl-only flex bg-[var(--m-bg-soft)] border border-[var(--m-border)] rounded-[10px] p-0.5 flex-shrink-0 max-[1279px]:hidden">
|
||||
<button
|
||||
v-for="opt in [{v:'dia',l:'Dia'},{v:'semana',l:'Semana'},{v:'mes',l:'Mês'},{v:'lista',l:'Lista'}]"
|
||||
:key="opt.v"
|
||||
class="ma-cal__view-btn bg-transparent border-0 text-[var(--m-text-muted)] px-3 py-[5px] rounded-lg text-[0.75rem] font-medium [font-family:inherit] cursor-pointer transition-all duration-[140ms] hover:text-[var(--m-text)] focus-visible:outline-2 focus-visible:outline-[var(--m-accent)] focus-visible:outline-offset-2"
|
||||
:class="{ 'is-active': calendarView === opt.v }"
|
||||
@click="setView(opt.v)"
|
||||
>
|
||||
{{ opt.l }}
|
||||
</button>
|
||||
<div class="ma-cal__view ma-cal__view--xl-only flex-shrink-0 max-[1279px]:hidden">
|
||||
<SelectButton
|
||||
:model-value="calendarView"
|
||||
:options="[
|
||||
{ v: 'dia', l: 'Dia' },
|
||||
{ v: 'semana', l: 'Semana' },
|
||||
{ v: 'mes', l: 'Mês' },
|
||||
{ v: 'lista', l: 'Lista' }
|
||||
]"
|
||||
option-value="v"
|
||||
option-label="l"
|
||||
:allow-empty="false"
|
||||
size="small"
|
||||
@update:model-value="(v) => v && setView(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1662,6 +1710,32 @@ defineExpose({
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Aviso quando há ocorrências de recorrência visíveis E o user
|
||||
não está na view 'lista'. Cobre o caso "sessão recorrente
|
||||
com count=4 — aparece só na semana atual; outras 3 ficam
|
||||
fora do range visível". Botão troca pra view lista, que
|
||||
cobre 2 anos (passado + futuro) numa varredura só. -->
|
||||
<Transition name="ma-patient-banner">
|
||||
<div
|
||||
v-if="showRecurrenceHint"
|
||||
class="ma-cal__recurrence-hint flex items-center gap-2.5 px-3.5 py-1.5 border-b border-[var(--m-border)] bg-[color-mix(in_srgb,var(--m-accent)_6%,var(--m-bg-medium))] text-[var(--m-text)] text-[0.78rem] flex-shrink-0"
|
||||
role="status"
|
||||
>
|
||||
<i class="pi pi-refresh text-[var(--m-accent)] text-[0.74rem] flex-shrink-0" />
|
||||
<span class="min-w-0 flex-1 leading-[1.3] text-[var(--m-text-muted)]">
|
||||
Há sessões recorrentes visíveis — pode haver mais fora deste período.
|
||||
</span>
|
||||
<button
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-[5px] rounded-lg border border-[var(--m-border)] bg-[var(--m-bg-soft)] text-[var(--m-text)] text-[0.74rem] font-medium [font-family:inherit] cursor-pointer transition-[background-color,border-color,color] duration-[140ms] flex-shrink-0 hover:bg-[var(--m-bg-soft-hover)] hover:border-[var(--m-border-strong)] focus-visible:outline-2 focus-visible:outline-[var(--m-accent)] focus-visible:outline-offset-2"
|
||||
v-tooltip.bottom="'Lista cobre 2 anos centrada em hoje'"
|
||||
@click="setView('lista')"
|
||||
>
|
||||
<i class="pi pi-list" />
|
||||
<span>Ver na lista</span>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- FullCalendar — view selecionada via :key pra re-mount limpo -->
|
||||
<div class="ma-cal__fc">
|
||||
<!-- Overlay leve enquanto eventos carregam. Não bloqueia interação,
|
||||
@@ -2015,18 +2089,14 @@ defineExpose({
|
||||
/* ═══ COL 2: Calendar central ════════════════════════════════ */
|
||||
/* .ma-cal*, .ma-cal__toolbar, .ma-cal__nav*, .ma-cal__btn (versão ghost
|
||||
da toolbar), .ma-cal__icon, .ma-cal__period, .ma-cal__right, .ma-cal__view*
|
||||
migraram pra Tailwind no template. Aqui ficam SO os state modifiers:
|
||||
.is-today-active (botao Hoje desabilitado) e .is-active do view-btn. */
|
||||
migraram pra Tailwind no template. Aqui fica SO o state modifier
|
||||
.is-today-active (botao Hoje desabilitado). O view selector agora e
|
||||
PrimeVue SelectButton — visual herdado do tema do projeto. */
|
||||
.ma-cal__btn.is-today-active {
|
||||
opacity: 0.45;
|
||||
cursor: default;
|
||||
}
|
||||
.ma-cal__btn.is-today-active:hover { background: transparent; }
|
||||
.ma-cal__view-btn.is-active {
|
||||
background: var(--m-bg-soft-hover);
|
||||
color: var(--m-text);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* .ma-cal__loading migrou pra Tailwind utilities no template. */
|
||||
.ma-loading-fade-enter-active,
|
||||
@@ -2171,6 +2241,16 @@ defineExpose({
|
||||
.ma-cal__fc :deep(.fc-list-day-cushion) {
|
||||
background: var(--m-bg-soft);
|
||||
}
|
||||
/* Sticky day header em listAll precisa de z-index + bg opaco — sem isso,
|
||||
conforme o user dá scroll, .fc-list-event passa POR CIMA do header
|
||||
(stacking context da row de evento vence o cushion sem z-index). */
|
||||
.ma-cal__fc :deep(.fc-list-day) {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
.ma-cal__fc :deep(.fc-list-day > *) {
|
||||
background: var(--m-bg-medium);
|
||||
}
|
||||
.ma-cal__fc :deep(.fc-list-event:hover td) {
|
||||
background: var(--m-bg-soft);
|
||||
}
|
||||
@@ -2197,6 +2277,20 @@ defineExpose({
|
||||
.ma-cal__fc :deep(.fc-event-main) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Eventos de paciente Inativo/Arquivado — borda tracejada + opacidade reduzida.
|
||||
Mantem a cor do commitment (contexto preservado) e funciona em todas as
|
||||
views (day/week/month/list) porque ataca .fc-event direto. */
|
||||
.ma-cal__fc :deep(.fc-event.ma-evt--inactive-patient) {
|
||||
opacity: 0.58;
|
||||
border-left-style: dashed;
|
||||
}
|
||||
.ma-cal__fc :deep(.fc-list-event.ma-evt--inactive-patient) {
|
||||
opacity: 0.58;
|
||||
}
|
||||
.ma-cal__fc :deep(.fc-list-event.ma-evt--inactive-patient td) {
|
||||
font-style: italic;
|
||||
}
|
||||
.ma-cal__fc :deep(.mc-fc-event) {
|
||||
padding: 4px 6px;
|
||||
color: var(--m-text);
|
||||
@@ -2393,7 +2487,7 @@ html:not(.app-dark) .ma-cal__fc :deep(.mc-fc-event__badge--modal.is-presencial)
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Lista (listWeek) */
|
||||
/* Lista (listAll — view custom 2 anos centrada em hoje) */
|
||||
.ma-cal__fc :deep(.fc-list-day-text),
|
||||
.ma-cal__fc :deep(.fc-list-day-side-text) {
|
||||
color: var(--m-text);
|
||||
|
||||
Reference in New Issue
Block a user