carousel, agenda arquivados, agenda cor, agenda arquivados, grupos pacientes, pacientes arquivados - desativados, sessoes verificadas, ajuste notificações, Prontuario, Agenda Animation, Menu Profile, bagdes Profile, Offline

This commit is contained in:
Leonardo
2026-03-18 09:26:09 -03:00
parent 66f67cd40f
commit d6d2fe29d1
55 changed files with 3655 additions and 1512 deletions
@@ -102,6 +102,21 @@
Este dia é folga na sua jornada. Você ainda pode salvar se necessário.
</Message>
<!-- Restrições de status do paciente -->
<Message v-if="isArchivedPastEdit" severity="warn" class="mb-3" :closable="false">
<i class="pi pi-lock mr-1" />
<b>Paciente arquivado.</b> O histórico de sessões é somente leitura.
</Message>
<Message v-if="isEdit && form.paciente_status === 'Inativo' && isSessionFuture" severity="warn" class="mb-3" :closable="false">
<i class="pi pi-ban mr-1" />
<b>Paciente inativo.</b> Remarcação de sessões está bloqueada.
</Message>
<Message v-if="!isEdit && isSessionEvent && form.paciente_id && !agendaPerms.canCreateSession" severity="error" class="mb-3" :closable="false">
<i class="pi pi-ban mr-1" />
<b>{{ form.paciente_status === 'Arquivado' ? 'Paciente arquivado.' : 'Paciente inativo.' }}</b>
Novos agendamentos estão bloqueados.
</Message>
<!-- Alerta: solicitação pendente neste horário -->
<Message v-if="solicitacaoPendente && isSessionEvent && !isEdit" severity="info" class="mb-3" :closable="false">
<div class="flex items-center justify-between gap-3 w-full flex-wrap">
@@ -182,7 +197,13 @@
/>
<div class="min-w-0 flex-1">
<div class="font-bold text-base truncate">{{ form.paciente_nome }}</div>
<div class="text-xs text-color-secondary">Paciente vinculado</div>
<div class="flex items-center gap-1.5 flex-wrap">
<span class="text-xs text-color-secondary">Paciente vinculado</span>
<span
v-if="form.paciente_status === 'Inativo' || form.paciente_status === 'Arquivado'"
style="display:inline-block;background:#f97316;color:#fff;font-size:9px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;padding:1px 6px;border-radius:3px;line-height:1.5;"
>{{ form.paciente_status === 'Arquivado' ? 'arquivado' : 'desativado' }}</span>
</div>
</div>
<div class="flex gap-1 shrink-0">
<Button v-if="!patientLocked" icon="pi pi-pencil" severity="secondary" outlined size="small" class="rounded-full h-8 w-8" v-tooltip.top="'Trocar'" @click="openPacientePicker" />
@@ -265,10 +286,12 @@
<div class="field-card__body">
<SelectButton
v-model="form.status"
:options="statusOptions"
:options="statusOptionsFiltered"
optionLabel="label"
optionValue="value"
optionDisabled="disabled"
:allowEmpty="false"
:disabled="isArchivedPastEdit"
class="w-full status-select-btn"
/>
</div>
@@ -684,6 +707,7 @@
variant="filled"
rows="3"
autoResize
:disabled="isArchivedPastEdit"
/>
<label for="aed-observacoes-side">Observação</label>
</FloatLabel>
@@ -692,6 +716,11 @@
<!-- Opção de recorrência para sessão SEM série (criação ou avulsa) -->
<template v-if="!hasSerie">
<div class="side-card__title mb-2">Frequência</div>
<Message v-if="isSessionEvent && form.paciente_id && !agendaPerms.canCreateRecurrence" severity="warn" class="mb-3" :closable="false">
<i class="pi pi-ban mr-1" />
<b>{{ form.paciente_status === 'Arquivado' ? 'Paciente arquivado.' : 'Paciente inativo.' }}</b>
Criação de recorrências está bloqueada.
</Message>
<!-- Data de início (= form.dia) com botão Hoje -->
<div class="rec-startdate-row mb-3">
@@ -708,8 +737,12 @@
v-for="f in freqOpcoes"
:key="f.value"
class="freq-chip"
:class="{ 'freq-chip--active': recorrenciaType === f.value }"
@click="recorrenciaType = f.value"
:class="{
'freq-chip--active': recorrenciaType === f.value,
'opacity-40 cursor-not-allowed': f.value !== 'avulsa' && !agendaPerms.canCreateRecurrence
}"
:disabled="f.value !== 'avulsa' && !agendaPerms.canCreateRecurrence"
@click="(!agendaPerms.canCreateRecurrence && f.value !== 'avulsa') ? null : (recorrenciaType = f.value)"
>{{ f.label }}</button>
</div>
@@ -1101,6 +1134,7 @@ import { useServices } from '@/features/agenda/composables/useServices'
import { useCommitmentServices } from '@/features/agenda/composables/useCommitmentServices'
import { usePatientDiscounts } from '@/features/agenda/composables/usePatientDiscounts'
import { useInsurancePlans } from '@/features/agenda/composables/useInsurancePlans'
import { getPatientAgendaPermissions } from '@/composables/usePatientLifecycle'
function patientInitials (nome) {
const parts = String(nome || '').trim().split(/\s+/).filter(Boolean)
@@ -1663,7 +1697,8 @@ const patients = ref([])
const filteredPatients = computed(() => {
const q = String(pacienteSearch.value || '').trim().toLowerCase()
const list = patients.value || []
// Somente pacientes Ativos podem ser selecionados para novos agendamentos
const list = (patients.value || []).filter(p => p.status === 'Ativo')
if (!q) return list
return list.filter(p => {
const nome = String(p.nome || '').toLowerCase()
@@ -2137,6 +2172,39 @@ const pillDeleteMenuItems = computed(() => {
})
function isPast (iso) { return iso ? new Date(iso) < new Date() : false }
// ── Permissões de agenda por status do paciente ───────────────────────────
const agendaPerms = computed(() => getPatientAgendaPermissions(form.value.paciente_status || ''))
// Sessão atual é futura? (para edição: usa inicio_em do evento original)
const isSessionFuture = computed(() => {
if (!isEdit.value) return true
const iso = props.eventRow?.inicio_em
return iso ? new Date(iso) > new Date() : true
})
// Arquivado editando sessão passada → somente leitura
const isArchivedPastEdit = computed(() =>
isEdit.value &&
form.value.paciente_status === 'Arquivado' &&
!isSessionFuture.value
)
// Inativo editando sessão futura → remarcar bloqueado
const isInativoFutureEdit = computed(() =>
isEdit.value &&
form.value.paciente_status === 'Inativo' &&
isSessionFuture.value
)
// StatusOptions com remarcar desabilitado para Inativo
const statusOptionsFiltered = computed(() => [
{ label: 'Agendado', value: 'agendado' },
{ label: 'Realizado', value: 'realizado' },
{ label: 'Faltou', value: 'faltou' },
{ label: 'Cancelado', value: 'cancelado' },
{ label: 'Remarcar', value: 'remarcar', disabled: isInativoFutureEdit.value },
])
function fmtWeekdayShort (iso) { return new Date(iso).toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', '').slice(0, 3) }
function fmtDayNum (iso) { return new Date(iso).getDate() }
function fmtMonthShort (iso) { return new Date(iso).toLocaleDateString('pt-BR', { month: 'short' }).replace('.', '') }
@@ -2411,6 +2479,18 @@ const canSave = computed(() => {
if (!form.value.commitment_id) return false
if (requiresPatient.value && !form.value.paciente_id) return false
if (isSessionEvent.value && billingType.value === 'particular' && commitmentItems.value.length === 0) return false
// ── Restrições por status do paciente ────────────────────
if (isSessionEvent.value && form.value.paciente_status) {
const perms = agendaPerms.value
// Criar sessão avulsa ou com recorrência: bloqueado para Inativo/Arquivado
if (!isEdit.value && !perms.canCreateSession) return false
// Criar recorrência: bloqueado para Inativo/Arquivado
if (!isEdit.value && recorrenciaType.value !== 'avulsa' && !perms.canCreateRecurrence) return false
// Arquivado tentando salvar sessão passada: bloqueado
if (isArchivedPastEdit.value) return false
}
return true
})
@@ -2615,9 +2695,10 @@ function resetForm () {
id: r?.id || null,
owner_id: r?.owner_id || props.ownerId || '',
terapeuta_id: r?.terapeuta_id ?? null,
paciente_id: r?.paciente_id ?? null,
paciente_nome: r?.paciente_nome ?? r?.patient_name ?? '',
paciente_id: r?.paciente_id ?? null,
paciente_nome: r?.paciente_nome ?? r?.patient_name ?? '',
paciente_avatar: r?.paciente_avatar ?? '',
paciente_status: r?.paciente_status ?? '',
commitment_id: r?.determined_commitment_id ?? null,
titulo_custom: r?.titulo_custom || '',
status: r?.status || 'agendado',