Copyright, Financeiro, Lançamentos, aprimoramentos de ui

This commit is contained in:
Leonardo
2026-03-21 08:05:40 -03:00
parent 29ed349cf2
commit a89d1f5560
268 changed files with 58870 additions and 1752 deletions
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/AgendaCalendar.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaCalendar.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch, onMounted } from 'vue'
@@ -151,9 +166,12 @@ onMounted(() => {
<template>
<div class="agenda-calendar-wrap">
<div v-if="loading" class="agenda-calendar-loading">
<ProgressSpinner strokeWidth="3" />
<div class="text-sm mt-2" style="color: var(--text-color-secondary);">
Carregando agenda
<div class="flex flex-col gap-2 w-full px-2 py-2">
<Skeleton height="2rem" class="mb-1" />
<div class="grid grid-cols-7 gap-1">
<Skeleton v-for="n in 7" :key="n" height="1.25rem" />
</div>
<Skeleton v-for="n in 8" :key="'row' + n" height="3rem" />
</div>
</div>
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/AgendaClinicCalendar.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaClinicCalendar.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref } from 'vue'
@@ -1,10 +1,26 @@
<!-- src/features/agenda/components/AgendaClinicMosaic.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaClinicMosaic.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch, nextTick } from 'vue'
import FullCalendar from '@fullcalendar/vue3'
import timeGridPlugin from '@fullcalendar/timegrid'
import dayGridPlugin from '@fullcalendar/daygrid'
import listPlugin from '@fullcalendar/list'
import interactionPlugin from '@fullcalendar/interaction'
import ptBrLocale from '@fullcalendar/core/locales/pt-br'
@@ -111,7 +127,7 @@ function gotoDate (date) {
}
function setView (v) {
const target = v === 'week' ? 'timeGridWeek' : (v === 'month' ? 'dayGridMonth' : 'timeGridDay')
const target = v === 'week' ? 'timeGridWeek' : v === 'month' ? 'dayGridMonth' : v === 'list' ? 'listWeek' : 'timeGridDay'
forEachApi(api => api.changeView(target))
}
function setMode () {}
@@ -241,7 +257,7 @@ function emitDebug (col) {
function buildFcOptions (ownerId) {
const base = {
plugins: [timeGridPlugin, dayGridPlugin, interactionPlugin],
plugins: [timeGridPlugin, dayGridPlugin, listPlugin, interactionPlugin],
locale: ptBrLocale,
timeZone: props.timezone,
@@ -269,6 +285,12 @@ function buildFcOptions (ownerId) {
businessHours: props.businessHours,
views: {
timeGridDay: {
dayHeaderFormat: { day: 'numeric', month: 'long', year: 'numeric' },
},
},
height: 'auto',
expandRows: true,
allDaySlot: false,
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/AgendaEventDialog.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaEventDialog.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<template>
<Dialog
v-model:visible="visible"
@@ -457,7 +472,7 @@
:style="{ background: `#${selectedCommitment.bg_color}20`, color: `#${selectedCommitment.bg_color}`, borderColor: `#${selectedCommitment.bg_color}40` }"
>{{ selectedCommitmentName }}</span>
<Tag v-else :value="selectedCommitmentName" severity="info" />
<Tag v-if="isSessionEvent" :value="labelStatusSessao(form.status)" :severity="statusSeverity(form.status)" />
<Tag v-if="isSessionEvent" :value="labelStatusSessao(form.status)" :severity="statusSeverity(form.status)" :class="statusExtraClass(form.status)" />
</div>
<div class="summary-row">
@@ -713,6 +728,13 @@
</FloatLabel>
</div>
<!-- COBRANÇA DA SESSÃO -->
<AgendaEventoFinanceiroPanel
v-if="isSessionEvent && isEdit && eventRow?.id"
:evento="eventRow"
class="mb-3"
/>
<!-- 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>
@@ -1147,8 +1169,10 @@ import InputNumber from 'primevue/inputnumber'
import RadioButton from 'primevue/radiobutton'
import Message from 'primevue/message'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
import { supabase } from '@/lib/supabase/client'
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue'
import AgendaEventoFinanceiroPanel from '@/components/agenda/AgendaEventoFinanceiroPanel.vue'
import { useServices } from '@/features/agenda/composables/useServices'
import { useCommitmentServices } from '@/features/agenda/composables/useCommitmentServices'
import { usePatientDiscounts } from '@/features/agenda/composables/usePatientDiscounts'
@@ -1193,8 +1217,9 @@ const props = defineProps({
newPatientRoute: { type: String, default: '' },
})
const emit = defineEmits(['update:modelValue', 'save', 'delete', 'updateSeriesEvent', 'editSeriesOccurrence'])
const emit = defineEmits(['update:modelValue', 'save', 'delete', 'updateSeriesEvent', 'editSeriesOccurrence', 'updated'])
const confirm = useConfirm()
const toast = useToast()
const router = useRouter()
const route = useRoute()
@@ -1205,6 +1230,7 @@ const visible = computed({
const step = ref(1)
const isEdit = computed(() => !!props.eventRow?.id || !!props.eventRow?.is_occurrence)
const allowBack = computed(() => !props.lockCommitment && !props.presetCommitmentId)
// ── série ─────────────────────────────────────────────────
@@ -1405,6 +1431,48 @@ function isNativeSession (c) {
const form = ref(resetForm())
// ── ConfirmDialog para status sensíveis (cancelado / remarcar) ────────────
const _prevStatus = ref(null)
const _skipStatusWatch = ref(false)
watch(() => form.value?.status, async (newVal, oldVal) => {
if (_skipStatusWatch.value) return
if (!isEdit.value || !form.value?.id) return
if (newVal !== 'cancelado' && newVal !== 'remarcar') return
_prevStatus.value = oldVal
const isCancelar = newVal === 'cancelado'
confirm.require({
header: isCancelar ? 'Cancelar sessão' : 'Remarcar sessão',
message: isCancelar
? 'Tem certeza que deseja cancelar esta sessão? O status será salvo imediatamente.'
: 'Tem certeza que deseja marcar esta sessão para remarcação? O status será salvo imediatamente.',
icon: isCancelar ? 'pi pi-times-circle' : 'pi pi-refresh',
acceptLabel: 'Sim, confirmar',
rejectLabel: 'Não',
acceptSeverity: isCancelar ? 'danger' : 'warn',
accept: async () => {
try {
const { data, error } = await supabase
.from('agenda_eventos')
.update({ status: newVal })
.eq('id', form.value.id)
.select()
.single()
if (error) throw error
toast.add({ severity: 'success', summary: 'Status atualizado', detail: `Sessão marcada como ${labelStatusSessao(newVal)}.`, life: 3000 })
emit('updated', data)
} catch (e) {
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Não foi possível atualizar o status.', life: 4000 })
form.value.status = _prevStatus.value
}
},
reject: () => {
form.value.status = _prevStatus.value
},
})
})
// ── Precificação / Serviços ─────────────────────────────────────────
const { services, getDefaultPrice, load: loadServices } = useServices()
const { loadItems: _csLoadItems, saveItems: saveCommitmentItems, loadItemsOrTemplate: _csLoadItemsOrTemplate } = useCommitmentServices()
@@ -1818,7 +1886,10 @@ watch(
console.log('[AgendaEventDialog] abriu — eventRow:', JSON.parse(JSON.stringify(props.eventRow || {})))
console.log('[AgendaEventDialog] isEdit:', isEdit.value, 'hasSerie:', hasSerie.value)
_skipStatusWatch.value = true
form.value = resetForm()
await nextTick()
_skipStatusWatch.value = false
samePatientConflict.value = null
recorrenciaType.value = 'avulsa'
diasSelecionados.value = []
@@ -2760,21 +2831,28 @@ const googleCalendarUrl = computed(() => {
})
function labelStatusSessao (v) {
const map = { agendado: 'Agendado', realizado: 'Realizado', faltou: 'Faltou', cancelado: 'Cancelado' }
const map = { agendado: 'Agendado', realizado: 'Realizado', faltou: 'Faltou', cancelado: 'Cancelado', remarcar: 'Remarcar' }
return map[v] || '—'
}
function statusSeverity (v) {
if (v === 'agendado') return 'success'
if (v === 'realizado') return 'secondary'
if (v === 'faltou') return 'danger'
if (v === 'agendado') return 'info'
if (v === 'realizado') return 'success'
if (v === 'faltou') return 'warn'
if (v === 'cancelado') return 'danger'
if (v === 'remarcar') return 'secondary' // cor real via classe CSS
return 'secondary'
}
function statusExtraClass (v) {
return v === 'remarcar' ? 'tag-remarcar' : ''
}
</script>
<style scoped>
.agenda-event-composer :deep(.p-dialog-content) { padding: .75rem; }
/* ── tag: remarcar (roxo — sem severity nativo no PrimeVue) ─ */
:deep(.tag-remarcar) { background: #a855f7 !important; color: #fff !important; }
/* ── header dot ─────────────────────────────────── */
.header-dot {
width: 10px; height: 10px; border-radius: 50%;
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/AgendaRightPanel.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaRightPanel.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
const props = defineProps({
@@ -1,3 +1,19 @@
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaToolbar.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch } from 'vue'
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/BloqueioDialog.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/BloqueioDialog.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, watch } from 'vue'
import { supabase } from '@/lib/supabase/client'
@@ -0,0 +1,16 @@
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/ConflictBanner.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
@@ -1,3 +1,19 @@
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/DeterminedCommitmentDialog.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<template>
<Dialog
v-model:visible="visible"
@@ -0,0 +1,16 @@
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/PreviewTimeline.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/ProximosFeriadosCard.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/ProximosFeriadosCard.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -223,8 +238,8 @@ function fmtDate (iso) {
<!-- Lista -->
<div class="px-4 py-3">
<div v-if="loading" class="flex justify-center py-3">
<i class="pi pi-spinner pi-spin opacity-40" />
<div v-if="loading" class="flex flex-col gap-2 py-1">
<Skeleton v-for="n in 3" :key="n" height="2rem" class="rounded" />
</div>
<div v-else-if="!feriadosMes.length" class="text-sm text-[var(--text-color-secondary)] py-1">
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/AgendaToolbar.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/cards/AgendaNextSessionsCardList.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch } from 'vue'
@@ -1,4 +1,19 @@
<!-- src/features/agenda/components/cards/AgendaPulseCardGrid.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/cards/AgendaPulseCardGrid.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed } from 'vue'
@@ -1,5 +1,20 @@
<!-- src/features/agenda/components/dev/AgendaDevDocs.vue
Documentação técnica da Agenda exibida apenas em modo suporte/dev.
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/dev/AgendaDevDocs.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<!-- Documentação técnica da Agenda exibida apenas em modo suporte/dev.
Acessível via SupportDebugBanner botão "Docs". -->
<script setup>
import { ref } from 'vue'