Copyright, Financeiro, Lançamentos, aprimoramentos de ui
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
// src/features/agenda/composables/useAgendaClinicEvents.js
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaClinicEvents.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
listClinicEvents,
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
// src/features/agenda/composables/useAgendaClinicStaff.js
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaClinicStaff.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { listTenantStaff } from '../services/agendaRepository'
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaEvents.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
/**
|
||||
* useAgendaEvents.js
|
||||
* src/features/agenda/composables/useAgendaEvents.js
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaLimits.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaPermissions.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaQuery.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
// src/features/agenda/composables/useAgendaSettings.js
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useAgendaSettings.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { getMyAgendaSettings, getMyWorkSchedule } from '../services/agendaRepository'
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/useCommitmentServices.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useCommitmentServices.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// CRUD de commitment_services — itens de serviço vinculados a um evento.
|
||||
// CRUD de recurrence_rule_services — template de serviços de uma regra de recorrência.
|
||||
//
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useDeterminedCommitments.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/useFinancialExceptions.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useFinancialExceptions.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// CRUD sobre a tabela public.financial_exceptions.
|
||||
//
|
||||
// Interface pública:
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/useInsurancePlans.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useInsurancePlans.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// Interface pública:
|
||||
// plans – ref([]) todos os planos do owner (ativos e inativos)
|
||||
// loading – ref(false)
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/usePatientDiscounts.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/usePatientDiscounts.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// CRUD completo sobre a tabela public.patient_discounts.
|
||||
//
|
||||
// Interface pública:
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/useProfessionalPricing.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useProfessionalPricing.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// Carrega a tabela professional_pricing do owner logado e expõe
|
||||
// getPriceFor(commitmentId) → number | null
|
||||
//
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useRecurrence.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
/**
|
||||
* useRecurrence.js
|
||||
* src/features/agenda/composables/useRecurrence.js
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/composables/useServices.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/composables/useServices.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// Interface pública:
|
||||
// services – ref([]) todos os serviços do owner (ativos e inativos)
|
||||
// loading – ref(false)
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
<!-- src/views/pages/agenda/AgendaClinicaPage.vue -->
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/pages/AgendaClinicaPage.vue
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<template>
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
|
||||
<!-- Sentinel -->
|
||||
@@ -107,7 +121,7 @@
|
||||
:slotMinTime="slotMinTime"
|
||||
:slotMaxTime="slotMaxTime"
|
||||
:slotDuration="slotDuration"
|
||||
:slotMinHeight="14"
|
||||
|
||||
:expandRows="false"
|
||||
:businessHours="businessHours"
|
||||
:staff="staffCols"
|
||||
@@ -192,7 +206,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Calendar
|
||||
<DatePicker
|
||||
v-model="miniDate"
|
||||
inline
|
||||
class="ag-mini-cal"
|
||||
@@ -203,7 +217,7 @@
|
||||
<span class="mini-day-num">{{ date.day }}</span>
|
||||
<span v-if="hasMiniEvent(date)" class="mini-day-dot" />
|
||||
</template>
|
||||
</Calendar>
|
||||
</DatePicker>
|
||||
</div>
|
||||
|
||||
<ProximosFeriadosCard
|
||||
@@ -346,7 +360,7 @@
|
||||
<!-- Month Picker -->
|
||||
<Dialog v-model:visible="monthPickerVisible" modal header="Escolher mês" :style="{ width: '420px' }">
|
||||
<div class="p-2">
|
||||
<Calendar
|
||||
<DatePicker
|
||||
v-model="monthPickerDate"
|
||||
view="month"
|
||||
dateFormat="mm/yy"
|
||||
@@ -499,7 +513,7 @@ import { useRouter, useRoute } from 'vue-router'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
|
||||
import Calendar from 'primevue/calendar'
|
||||
import DatePicker from 'primevue/datepicker'
|
||||
|
||||
import AgendaClinicMosaic from '@/features/agenda/components/AgendaClinicMosaic.vue'
|
||||
import AgendaEventDialog from '@/features/agenda/components/AgendaEventDialog.vue'
|
||||
@@ -621,9 +635,10 @@ const onlySessionsOptions = [
|
||||
{ label: 'Tudo', value: false }
|
||||
]
|
||||
const viewOptions = [
|
||||
{ label: 'Dia', value: 'day' },
|
||||
{ label: 'Semana', value: 'week' },
|
||||
{ label: 'Mês', value: 'month' }
|
||||
{ label: 'Dia', value: 'day' },
|
||||
{ label: 'Semana', value: 'week' },
|
||||
{ label: 'Mês', value: 'month' },
|
||||
{ label: 'Lista', value: 'list' }
|
||||
]
|
||||
const timeModeOptions = [
|
||||
{ label: '24h', value: '24' },
|
||||
@@ -2172,12 +2187,41 @@ const workDowSet = computed(() =>
|
||||
new Set(workRules.value.filter(r => r.ativo).map(r => Number(r.dia_semana)))
|
||||
)
|
||||
|
||||
// ── Mini calendário: set de dias da semana atual ─────────────
|
||||
const currentWeekIsoSet = computed(() => {
|
||||
const now = new Date()
|
||||
const monday = new Date(now)
|
||||
monday.setDate(now.getDate() - ((now.getDay() + 6) % 7))
|
||||
monday.setHours(0, 0, 0, 0)
|
||||
const set = new Set()
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(monday)
|
||||
d.setDate(monday.getDate() + i)
|
||||
set.add(`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`)
|
||||
}
|
||||
return set
|
||||
})
|
||||
|
||||
const todayISO = computed(() => {
|
||||
const n = new Date()
|
||||
return `${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,'0')}-${String(n.getDate()).padStart(2,'0')}`
|
||||
})
|
||||
|
||||
// ── Mini calendário: classes por dia ──────────────────────────
|
||||
function miniDayClass (date) {
|
||||
const iso = `${date.year}-${String(date.month + 1).padStart(2,'0')}-${String(date.day).padStart(2,'0')}`
|
||||
if (miniBlockedDaySet.value.has(iso)) return 'mini-day-blocked'
|
||||
const dow = new Date(date.year, date.month, date.day).getDay()
|
||||
return workDowSet.value.has(dow) ? 'mini-day-work' : 'mini-day-off'
|
||||
const classes = []
|
||||
if (currentWeekIsoSet.value.has(iso)) {
|
||||
classes.push('mini-week-hl')
|
||||
if (dow === 1) classes.push('mini-week-hl--start')
|
||||
else if (dow === 0) classes.push('mini-week-hl--end')
|
||||
else classes.push('mini-week-hl--mid')
|
||||
}
|
||||
if (iso === todayISO.value) classes.push('mini-day-today')
|
||||
if (miniBlockedDaySet.value.has(iso)) classes.push('mini-day-blocked')
|
||||
else classes.push(workDowSet.value.has(dow) ? 'mini-day-work' : 'mini-day-off')
|
||||
return classes
|
||||
}
|
||||
|
||||
// ── Mini calendário: bolinhas + bloqueios de dia inteiro ──────
|
||||
@@ -2519,12 +2563,28 @@ function goRecorrencias () { router.push({ name: 'admin-agenda-recorrencias' })
|
||||
width: 100%; min-width: unset; border-radius: 6px;
|
||||
position: relative; display: flex; align-items: center; justify-content: center; aspect-ratio: 1;
|
||||
}
|
||||
:deep(.p-disabled.mini-day-work) { background: color-mix(in srgb, #9ca3af 18%, transparent) !important; opacity: 0.6; }
|
||||
.mini-day-num { display: block; text-align: center; line-height: 1; }
|
||||
.mini-day-dot {
|
||||
position: absolute; bottom: 2px; right: 2px;
|
||||
width: 4px; height: 4px; border-radius: 50%;
|
||||
background: var(--primary-color, #6366f1);
|
||||
}
|
||||
|
||||
/* Semana atual — faixa de fundo contínua seg→dom */
|
||||
:deep(.mini-week-hl) { background: color-mix(in srgb, var(--primary-color, #6366f1) 12%, transparent) !important; border-radius: 0 !important; }
|
||||
:deep(.mini-week-hl--start) { border-radius: 6px 0 0 6px !important; }
|
||||
:deep(.mini-week-hl--end) { border-radius: 0 6px 6px 0 !important; }
|
||||
|
||||
/* Hoje — cartão com borda + sombra */
|
||||
:deep(.mini-day-today) {
|
||||
background: color-mix(in srgb, var(--primary-color, #6366f1) 80%, #00000000) !important;
|
||||
border: 1px solid var(--surface-border) !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06) !important;
|
||||
border-radius: 6px !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<!-- src/features/agenda/pages/AgendaRecorrenciasPage.vue -->
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/pages/AgendaRecorrenciasPage.vue
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
@@ -303,7 +318,6 @@ onMounted(init)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast />
|
||||
|
||||
<!-- ─── Header ─────────────────────────────────────────────────── -->
|
||||
<div class="rr-page mx-3 md:mx-5">
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
<!-- src/views/pages/agenda/AgendaTerapeutaPage.vue -->
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/pages/AgendaTerapeutaPage.vue
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<template>
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
|
||||
<!-- ════ AGENDA TERAPEUTA — Layout 3 colunas ════ -->
|
||||
@@ -117,7 +131,7 @@
|
||||
<Button icon="pi pi-chevron-right" severity="secondary" text class="h-7 w-7 rounded-full" @click="miniNextMonth" />
|
||||
</div>
|
||||
</div>
|
||||
<Calendar
|
||||
<DatePicker
|
||||
v-model="miniDate"
|
||||
inline
|
||||
class="w-full"
|
||||
@@ -128,7 +142,7 @@
|
||||
<span class="mini-day-num">{{ date.day }}</span>
|
||||
<span v-if="hasMiniEvent(date)" class="mini-day-dot" />
|
||||
</template>
|
||||
</Calendar>
|
||||
</DatePicker>
|
||||
</div>
|
||||
|
||||
<div v-if="jornadaHoje" class="border border-[var(--surface-border)] rounded-md bg-[var(--surface-card)] p-3">
|
||||
@@ -140,6 +154,8 @@
|
||||
|
||||
<ProximosFeriadosCard :ownerId="ownerId" :tenantId="clinicTenantId" :workRules="workRules" @bloqueado="refetch" />
|
||||
|
||||
<LoadedPhraseBlock v-if="eventsHasLoaded" />
|
||||
|
||||
<!-- Divisor -->
|
||||
<div class="border-t border-[var(--surface-border)] my-1" />
|
||||
|
||||
@@ -149,10 +165,15 @@
|
||||
<span class="flex items-center gap-1.5 text-[1rem] font-bold uppercase tracking-[0.06em] text-[var(--text-color-secondary)] opacity-65"><i class="pi pi-chart-bar" />Hoje</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="flex flex-col gap-0.5 p-2 rounded-md bg-[var(--surface-ground)] border border-[var(--surface-border)] text-center" v-for="s in todayStats" :key="s.label">
|
||||
<div class="text-[1.25rem] font-bold leading-none text-[var(--text-color)]" :class="{ 'text-green-500': s.cls === 'ag-stat--ok', 'text-red-500': s.cls === 'ag-stat--warn' }">{{ s.value }}</div>
|
||||
<div class="text-[0.65rem] font-semibold uppercase tracking-[0.04em] text-[var(--text-color-secondary)] opacity-70">{{ s.label }}</div>
|
||||
</div>
|
||||
<template v-if="eventsLoading">
|
||||
<Skeleton v-for="n in 4" :key="n" height="3rem" class="rounded-md" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-0.5 p-2 rounded-md bg-[var(--surface-ground)] border border-[var(--surface-border)] text-center" v-for="s in todayStats" :key="s.label">
|
||||
<div class="text-[1.25rem] font-bold leading-none text-[var(--text-color)]" :class="{ 'text-green-500': s.cls === 'ag-stat--ok', 'text-red-500': s.cls === 'ag-stat--warn' }">{{ s.value }}</div>
|
||||
<div class="text-[0.65rem] font-semibold uppercase tracking-[0.04em] text-[var(--text-color-secondary)] opacity-70">{{ s.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +183,12 @@
|
||||
<span class="flex items-center gap-1.5 text-[1rem] font-bold uppercase tracking-[0.06em] text-[var(--text-color-secondary)] opacity-65"><i class="pi pi-list" />Sessões hoje</span>
|
||||
<span class="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-[var(--primary-color,#6366f1)] text-white text-[0.65rem] font-bold">{{ todayEvents.length }}</span>
|
||||
</div>
|
||||
<div v-if="!todayEvents.length" class="flex flex-col items-center justify-center gap-2 py-6 text-[var(--text-color-secondary)] text-sm text-center">
|
||||
<template v-if="eventsLoading">
|
||||
<div class="flex flex-col gap-1.5 mt-1">
|
||||
<Skeleton v-for="n in 3" :key="n" height="3.5rem" class="rounded-md" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="!todayEvents.length" class="flex flex-col items-center justify-center gap-2 py-6 text-[var(--text-color-secondary)] text-sm text-center">
|
||||
<i class="pi pi-sun text-2xl opacity-20" />
|
||||
<span>Nenhuma sessão hoje</span>
|
||||
</div>
|
||||
@@ -318,7 +344,7 @@
|
||||
<Button icon="pi pi-chevron-right" severity="secondary" outlined class="h-8 w-8 rounded-full" @click="goNext" />
|
||||
</div>
|
||||
</div>
|
||||
<Calendar
|
||||
<DatePicker
|
||||
v-model="miniDate"
|
||||
inline
|
||||
class="w-full"
|
||||
@@ -329,7 +355,7 @@
|
||||
<span class="mini-day-num">{{ date.day }}</span>
|
||||
<span v-if="hasMiniEvent(date)" class="mini-day-dot" />
|
||||
</template>
|
||||
</Calendar>
|
||||
</DatePicker>
|
||||
</div>
|
||||
|
||||
<div v-if="jornadaHoje" class="border border-[var(--surface-border)] rounded-md bg-[var(--surface-card)] p-3">
|
||||
@@ -341,6 +367,8 @@
|
||||
|
||||
<ProximosFeriadosCard :ownerId="ownerId" :tenantId="clinicTenantId" :workRules="workRules" @bloqueado="refetch" />
|
||||
|
||||
<LoadedPhraseBlock v-if="eventsHasLoaded" />
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Botão toggle painel (só mobile <xl) -->
|
||||
@@ -377,7 +405,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-[var(--surface-border)] rounded-md bg-[var(--surface-card)] overflow-hidden">
|
||||
<div class="border border-[var(--surface-border)] rounded-md bg-[var(--surface-card)] overflow-hidden shadow-sm agenda-altura">
|
||||
<div v-if="calendarView === 'day' && miniBlockedDaySet.has(currentDateISO)" class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-red-700 bg-red-400/10 border-b border-red-400/25">
|
||||
<i class="pi pi-lock text-xs" /> Dia bloqueado — sessões não permitidas
|
||||
</div>
|
||||
@@ -396,10 +424,15 @@
|
||||
<span class="flex items-center gap-1.5 text-[1rem] font-bold uppercase tracking-[0.06em] text-[var(--text-color-secondary)] opacity-65"><i class="pi pi-chart-bar" />Hoje</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="flex flex-col gap-0.5 p-2 rounded-md bg-[var(--surface-ground)] border border-[var(--surface-border)] text-center" v-for="s in todayStats" :key="s.label">
|
||||
<div class="text-[1.25rem] font-bold leading-none text-[var(--text-color)]" :class="{ 'text-green-500': s.cls === 'ag-stat--ok', 'text-red-500': s.cls === 'ag-stat--warn' }">{{ s.value }}</div>
|
||||
<div class="text-[0.65rem] font-semibold uppercase tracking-[0.04em] text-[var(--text-color-secondary)] opacity-70">{{ s.label }}</div>
|
||||
</div>
|
||||
<template v-if="eventsLoading">
|
||||
<Skeleton v-for="n in 4" :key="n" height="3rem" class="rounded-md" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-0.5 p-2 rounded-md bg-[var(--surface-ground)] border border-[var(--surface-border)] text-center" v-for="s in todayStats" :key="s.label">
|
||||
<div class="text-[1.25rem] font-bold leading-none text-[var(--text-color)]" :class="{ 'text-green-500': s.cls === 'ag-stat--ok', 'text-red-500': s.cls === 'ag-stat--warn' }">{{ s.value }}</div>
|
||||
<div class="text-[0.65rem] font-semibold uppercase tracking-[0.04em] text-[var(--text-color-secondary)] opacity-70">{{ s.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -410,7 +443,12 @@
|
||||
<span class="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-[var(--primary-color,#6366f1)] text-white text-[0.65rem] font-bold">{{ todayEvents.length }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="!todayEvents.length" class="flex flex-col items-center justify-center gap-2 py-6 text-[var(--text-color-secondary)] text-sm text-center">
|
||||
<template v-if="eventsLoading">
|
||||
<div class="flex flex-col gap-1.5 mt-1">
|
||||
<Skeleton v-for="n in 3" :key="n" height="3.5rem" class="rounded-md" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="!todayEvents.length" class="flex flex-col items-center justify-center gap-2 py-6 text-[var(--text-color-secondary)] text-sm text-center">
|
||||
<i class="pi pi-sun text-2xl opacity-20" />
|
||||
<span>Nenhuma sessão hoje</span>
|
||||
</div>
|
||||
@@ -670,7 +708,7 @@
|
||||
<!-- Month Picker -->
|
||||
<Dialog v-model:visible="monthPickerVisible" modal header="Escolher mês" :style="{ width: '420px' }">
|
||||
<div class="p-2">
|
||||
<Calendar v-model="monthPickerDate" view="month" dateFormat="mm/yy" class="w-full" />
|
||||
<DatePicker v-model="monthPickerDate" view="month" dateFormat="mm/yy" class="w-full" />
|
||||
<div class="mt-3 flex justify-end gap-2">
|
||||
<Button label="Cancelar" severity="secondary" outlined class="rounded-full" @click="monthPickerVisible = false" />
|
||||
<Button label="Ir" class="rounded-full" @click="applyMonthPick" />
|
||||
@@ -939,7 +977,7 @@ import { supabase } from '@/lib/supabase/client'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
|
||||
import Calendar from 'primevue/calendar'
|
||||
import DatePicker from 'primevue/datepicker'
|
||||
|
||||
import FullCalendar from '@fullcalendar/vue3'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
@@ -1043,7 +1081,9 @@ const commitmentOptionsNormalized = computed(() => {
|
||||
// settings + events
|
||||
// -----------------------------
|
||||
const { error: settingsError, settings, workRules, load: loadSettings } = useAgendaSettings()
|
||||
const { error: eventsError, rows, loadMyRange, create, update, remove } = useAgendaEvents()
|
||||
const { error: eventsError, rows, loading: eventsLoading, loadMyRange, create, update, remove } = useAgendaEvents()
|
||||
const eventsHasLoaded = ref(false)
|
||||
watch(eventsLoading, (val) => { if (!val) eventsHasLoaded.value = true })
|
||||
|
||||
const {
|
||||
loadAndExpand,
|
||||
@@ -1096,9 +1136,10 @@ const onlySessionsOptions = [
|
||||
{ label: 'Tudo', value: false }
|
||||
]
|
||||
const viewOptions = [
|
||||
{ label: 'Dia', value: 'day' },
|
||||
{ label: 'Dia', value: 'day' },
|
||||
{ label: 'Semana', value: 'week' },
|
||||
{ label: 'Mês', value: 'month' }
|
||||
{ label: 'Mês', value: 'month' },
|
||||
{ label: 'Lista', value: 'list' }
|
||||
]
|
||||
const timeModeOptions = [
|
||||
{ label: '24h', value: '24' },
|
||||
@@ -1327,8 +1368,9 @@ const slotMaxTime = computed(() => {
|
||||
})
|
||||
|
||||
const fcViewName = computed(() => {
|
||||
if (calendarView.value === 'day') return 'timeGridDay'
|
||||
if (calendarView.value === 'week') return 'timeGridWeek'
|
||||
if (calendarView.value === 'day') return 'timeGridDay'
|
||||
if (calendarView.value === 'week') return 'timeGridWeek'
|
||||
if (calendarView.value === 'list') return 'listWeek'
|
||||
return 'dayGridMonth'
|
||||
})
|
||||
|
||||
@@ -1581,7 +1623,7 @@ const _initSlotMax = slotMaxTime.value
|
||||
// NÃO incluímos 'events' no fcOptions — evita que o Vue FC adapter gerencie
|
||||
// a fonte e conflite com o watch que usa getEventSources + addEventSource.
|
||||
const fcOptions = computed(() => ({
|
||||
plugins: [timeGridPlugin, dayGridPlugin, interactionPlugin],
|
||||
plugins: [timeGridPlugin, dayGridPlugin, listPlugin, interactionPlugin],
|
||||
locale: ptBrLocale,
|
||||
timeZone: timezone.value,
|
||||
|
||||
@@ -1602,12 +1644,16 @@ const fcOptions = computed(() => ({
|
||||
slotLabelContent,
|
||||
expandRows: false,
|
||||
height: 'auto',
|
||||
slotMinHeight: 14,
|
||||
|
||||
dayMaxEvents: true,
|
||||
weekends: true,
|
||||
eventMinHeight: 14,
|
||||
|
||||
views: {
|
||||
timeGridDay: {
|
||||
dayHeaderFormat: { day: 'numeric', month: 'long', year: 'numeric' },
|
||||
},
|
||||
},
|
||||
|
||||
businessHours: businessHours.value,
|
||||
|
||||
datesSet: async (arg) => {
|
||||
@@ -2052,13 +2098,42 @@ const currentDateISO = computed(() => {
|
||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
|
||||
})
|
||||
|
||||
// ── Mini calendário: set de dias da semana atual ─────────────
|
||||
const currentWeekIsoSet = computed(() => {
|
||||
const now = new Date()
|
||||
const monday = new Date(now)
|
||||
monday.setDate(now.getDate() - ((now.getDay() + 6) % 7))
|
||||
monday.setHours(0, 0, 0, 0)
|
||||
const set = new Set()
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(monday)
|
||||
d.setDate(monday.getDate() + i)
|
||||
set.add(`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`)
|
||||
}
|
||||
return set
|
||||
})
|
||||
|
||||
const todayISO = computed(() => {
|
||||
const n = new Date()
|
||||
return `${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,'0')}-${String(n.getDate()).padStart(2,'0')}`
|
||||
})
|
||||
|
||||
// ── Mini calendário: classes por dia ──────────────────────────
|
||||
// Prioridade: bloqueado total > dia de trabalho > folga
|
||||
function miniDayClass (date) {
|
||||
const iso = `${date.year}-${String(date.month + 1).padStart(2,'0')}-${String(date.day).padStart(2,'0')}`
|
||||
if (miniBlockedDaySet.value.has(iso)) return 'mini-day-blocked'
|
||||
const dow = new Date(date.year, date.month, date.day).getDay()
|
||||
return workDowSet.value.has(dow) ? 'mini-day-work' : 'mini-day-off'
|
||||
const classes = []
|
||||
if (currentWeekIsoSet.value.has(iso)) {
|
||||
classes.push('mini-week-hl')
|
||||
if (dow === 1) classes.push('mini-week-hl--start')
|
||||
else if (dow === 0) classes.push('mini-week-hl--end')
|
||||
else classes.push('mini-week-hl--mid')
|
||||
}
|
||||
if (iso === todayISO.value) classes.push('mini-day-today')
|
||||
if (miniBlockedDaySet.value.has(iso)) classes.push('mini-day-blocked')
|
||||
else classes.push(workDowSet.value.has(dow) ? 'mini-day-work' : 'mini-day-off')
|
||||
return classes
|
||||
}
|
||||
|
||||
// ── Mini calendário: bolinhas de compromissos + set de dias bloqueados ──
|
||||
@@ -3232,6 +3307,22 @@ onMounted(async () => {
|
||||
:deep(.mini-day-blocked) { background: color-mix(in srgb,#ef4444 20%,transparent) !important; border-radius: 4px; }
|
||||
:deep(.mini-day-work) { }
|
||||
:deep(.mini-day-off) { opacity: 0.45; }
|
||||
:deep(.p-disabled.mini-day-work) { background: color-mix(in srgb, #9ca3af 18%, transparent) !important; opacity: 0.6; }
|
||||
|
||||
/* Semana atual — faixa de fundo contínua seg→dom */
|
||||
:deep(.mini-week-hl) { background: color-mix(in srgb, var(--primary-color, #6366f1) 12%, transparent) !important; border-radius: 0 !important; }
|
||||
:deep(.mini-week-hl--start) { border-radius: 6px 0 0 6px !important; }
|
||||
:deep(.mini-week-hl--end) { border-radius: 0 6px 6px 0 !important; }
|
||||
|
||||
/* Hoje — cartão com borda + sombra */
|
||||
:deep(.mini-day-today) {
|
||||
background: color-mix(in srgb, var(--primary-color, #6366f1) 80%, #00000000) !important;
|
||||
border: 1px solid var(--surface-border) !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06) !important;
|
||||
border-radius: 6px !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<!-- src/features/agenda/pages/AgendamentosRecebidosPage.vue -->
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/pages/AgendamentosRecebidosPage.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'
|
||||
@@ -40,6 +55,7 @@ const statusOpts = [
|
||||
// ── Lista ─────────────────────────────────────────────────
|
||||
const solicitacoes = ref([])
|
||||
const loading = ref(false)
|
||||
const hasLoaded = ref(false)
|
||||
const totalPendentes = ref(0)
|
||||
const totalAutorizados = ref(0)
|
||||
|
||||
@@ -87,6 +103,7 @@ async function load () {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: 'Não foi possível carregar as solicitações.', life: 4000 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
hasLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +285,6 @@ const emptySub = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast />
|
||||
|
||||
<!-- Sentinel -->
|
||||
<div class="h-px" />
|
||||
@@ -563,6 +579,8 @@ const emptySub = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoadedPhraseBlock v-if="hasLoaded && !loading" class="mx-3 md:mx-4 mt-3 mb-2" />
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════
|
||||
Dialog: Recusar
|
||||
═══════════════════════════════════════════════════════ -->
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
<!-- src/features/agenda/pages/CompromissosDeterminados.vue -->
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/pages/CompromissosDeterminados.vue
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<template>
|
||||
<Toast />
|
||||
|
||||
<!-- Sentinel -->
|
||||
<div ref="headerSentinelRef" class="h-px" />
|
||||
@@ -101,29 +115,37 @@
|
||||
<!-- ══════════════════════════════════════
|
||||
Conteúdo principal
|
||||
═══════════════════════════════════════ -->
|
||||
<div class="px-3 md:px-4 pb-5 flex flex-col gap-3">
|
||||
<div class="px-3 md:px-4 pb-5 flex gap-4 items-start">
|
||||
|
||||
<!-- ── Coluna principal ── -->
|
||||
<div class="flex flex-col gap-3 flex-1 min-w-0">
|
||||
|
||||
<!-- Stats row -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div
|
||||
v-for="s in stats"
|
||||
:key="s.label"
|
||||
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] min-w-[80px] flex-1"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<Skeleton v-for="n in 4" :key="n" height="3.5rem" class="flex-1 min-w-[80px] rounded-md" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
class="text-[1.35rem] font-bold leading-none"
|
||||
:class="{
|
||||
'text-green-500': s.cls === 'stat-ok',
|
||||
'text-red-500': s.cls === 'stat-warn',
|
||||
'text-[var(--text-color)]': !s.cls,
|
||||
}"
|
||||
>{{ s.value }}</div>
|
||||
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">{{ s.label }}</div>
|
||||
</div>
|
||||
v-for="s in stats"
|
||||
:key="s.label"
|
||||
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] min-w-[80px] flex-1"
|
||||
>
|
||||
<div
|
||||
class="text-[1.35rem] font-bold leading-none"
|
||||
:class="{
|
||||
'text-green-500': s.cls === 'stat-ok',
|
||||
'text-red-500': s.cls === 'stat-warn',
|
||||
'text-[var(--text-color)]': !s.cls,
|
||||
}"
|
||||
>{{ s.value }}</div>
|
||||
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">{{ s.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Cards grid -->
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-5 2xl:grid-cols-6">
|
||||
<!-- Cards grid (coluna direita — visível só até xl) -->
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 xl:hidden">
|
||||
<div
|
||||
v-for="c in cardsCommitments"
|
||||
:key="c.id"
|
||||
@@ -241,7 +263,8 @@
|
||||
:loading="loading"
|
||||
:paginator="visibleCommitments.length > 10"
|
||||
:rows="10"
|
||||
responsiveLayout="scroll"
|
||||
scrollable
|
||||
scrollHeight="400px"
|
||||
class="p-datatable-sm cmpr-datatable"
|
||||
:rowClass="(r) => isRecent(r) ? 'row-new-highlight' : ''"
|
||||
:filters="filters"
|
||||
@@ -324,6 +347,62 @@
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<LoadedPhraseBlock v-if="hasLoaded" />
|
||||
|
||||
</div><!-- fim coluna principal -->
|
||||
|
||||
<!-- ── Coluna direita: cards de tipos (só xl+) ── -->
|
||||
<div class="hidden xl:flex flex-col gap-2 w-64 flex-shrink-0">
|
||||
<div class="text-xs font-bold uppercase tracking-widest text-[var(--text-color-secondary)] opacity-60 px-1 mb-1">
|
||||
Tipos de compromisso
|
||||
</div>
|
||||
|
||||
<template v-if="loading">
|
||||
<Skeleton v-for="n in 5" :key="n" height="4.5rem" class="rounded-md" />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="c in cardsCommitments"
|
||||
:key="c.id"
|
||||
class="flex flex-col gap-1.5 px-3 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)]"
|
||||
>
|
||||
<!-- Nome com dot de cor -->
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full flex-shrink-0 bg-[var(--surface-border)]"
|
||||
:style="c.bg_color ? { background: `#${c.bg_color}` } : {}"
|
||||
/>
|
||||
<span
|
||||
class="text-[0.85rem] font-semibold truncate"
|
||||
:style="c.bg_color ? { color: `#${c.bg_color}` } : {}"
|
||||
>{{ c.name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Descrição -->
|
||||
<div class="text-[0.72rem] text-[var(--text-color-secondary)] line-clamp-1 pl-4">{{ c.description || '—' }}</div>
|
||||
|
||||
<!-- Tempo total -->
|
||||
<div
|
||||
class="flex items-center gap-1 pl-4 text-[0.72rem] text-[var(--text-color-secondary)]"
|
||||
v-tooltip.bottom="getTotalMinutes(c.id) === 0 ? 'Você ainda não tem nenhum evento desse tipo agendado e concluído para contabilizar tempo/relatórios' : null"
|
||||
>
|
||||
<i class="pi pi-clock text-[0.65rem]" />
|
||||
<span>Tempo total: {{ formatMinutes(getTotalMinutes(c.id)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state coluna direita -->
|
||||
<div
|
||||
v-if="!cardsCommitments.length"
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-md border border-dashed border-[var(--surface-border)] text-center text-[var(--text-color-secondary)]"
|
||||
>
|
||||
<i class="pi pi-list text-2xl opacity-20" />
|
||||
<div class="text-xs">Nenhum compromisso</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
@@ -381,8 +460,9 @@ onMounted(async () => {
|
||||
|
||||
onBeforeUnmount(() => { _observer?.disconnect() })
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const loading = ref(false)
|
||||
const hasLoaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const filters = reactive({
|
||||
global: { value: null, matchMode: 'contains' },
|
||||
@@ -503,6 +583,7 @@ async function fetchAll () {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao carregar.', life: 4500 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
hasLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/services/agenda.service.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
// src/features/agenda/services/agendaClinicRepository.js
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/services/agendaClinicRepository.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
function assertValidTenantId (tenantId) {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
// src/features/agenda/services/agendaMappers.js
|
||||
//
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/services/agendaMappers.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
// Suporta dois tipos de linha:
|
||||
// 1. Evento real (agenda_eventos do banco) — is_occurrence = false/undefined
|
||||
// 2. Ocorrência virtual (gerada por useRecurrence) — is_occurrence = true
|
||||
@@ -117,7 +131,9 @@ function _mapRow (r) {
|
||||
is_exception: r.is_exception ?? (exceptionType != null),
|
||||
|
||||
// financeiro
|
||||
price: r.price ?? null,
|
||||
price: r.price ?? null,
|
||||
billed: r.billed ?? false,
|
||||
billing_contract_id: r.billing_contract_id ?? null,
|
||||
insurance_plan_id: r.insurance_plan_id ?? null,
|
||||
insurance_guide_number: r.insurance_guide_number ?? null,
|
||||
insurance_value: r.insurance_value != null ? Number(r.insurance_value) : null,
|
||||
@@ -224,11 +240,12 @@ export function tituloFallback (tipo) {
|
||||
|
||||
function _statusBgColor (status) {
|
||||
const map = {
|
||||
realizado: '#6b7280',
|
||||
faltou: '#ef4444',
|
||||
cancelado: '#f97316',
|
||||
bloqueado: '#6b7280',
|
||||
remarcado: '#a855f7',
|
||||
agendado: '#3b82f6', // azul
|
||||
realizado: '#22c55e', // verde
|
||||
faltou: '#f97316', // laranja
|
||||
cancelado: '#ef4444', // vermelho
|
||||
remarcar: '#a855f7', // roxo
|
||||
bloqueado: '#6b7280', // cinza
|
||||
}
|
||||
return map[status] ?? null
|
||||
}
|
||||
@@ -237,8 +254,8 @@ function _statusIcon (status, isOccurrence, hasSerie) {
|
||||
if (status === 'realizado') return '✓ '
|
||||
if (status === 'faltou') return '✗ '
|
||||
if (status === 'cancelado') return '∅ '
|
||||
if (status === 'remarcar') return '↺ '
|
||||
if (status === 'bloqueado') return '⊘ '
|
||||
if (status === 'remarcado') return '↺ '
|
||||
if (hasSerie || isOccurrence) return '↻ '
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
// src/features/agenda/services/agendaRepository.js
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Tecnologia aplicada à escuta.
|
||||
| Estrutura para o cuidado.
|
||||
|
|
||||
| Arquivo: src/features/agenda/services/agendaRepository.js
|
||||
| Data: 2026
|
||||
| Local: São Carlos/SP — Brasil
|
||||
|--------------------------------------------------------------------------
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { useTenantStore } from '@/stores/tenantStore'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user