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/views/pages/patients/PatientIntakeRequestsPage.vue -->
<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/patients/cadastro/recebidos/CadastrosRecebidosPage.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { supabase } from '@/lib/supabase/client'
@@ -19,6 +34,7 @@ const tenantStore = useTenantStore()
const converting = ref(false)
const loading = ref(false)
const hasLoaded = ref(false)
const rows = ref([])
const q = ref('')
@@ -262,6 +278,7 @@ async function fetchIntakes () {
toast.add({ severity: 'error', summary: 'Erro ao carregar', detail: e.message || String(e), life: 3500 })
} finally {
loading.value = false
hasLoaded.value = true
}
}
@@ -404,7 +421,6 @@ onBeforeUnmount(() => { _observer?.disconnect() })
</script>
<template>
<Toast />
<ConfirmDialog />
<!-- Sentinel -->
@@ -499,47 +515,52 @@ onBeforeUnmount(() => { _observer?.disconnect() })
QUICK-STATS
-->
<div class="flex flex-wrap gap-2 px-3 md:px-4 mb-3">
<div
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)] flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow] duration-150 hover:border-indigo-400/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="{ 'border-[var(--primary-color,#6366f1)] shadow-[0_0_0_3px_rgba(99,102,241,0.15)]': statusFilter === '' }"
@click="toggleStatusFilter('')"
>
<div class="text-[1.35rem] font-bold leading-none text-[var(--text-color)]">{{ totals.total }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Total</div>
</div>
<template v-if="loading">
<Skeleton v-for="n in 4" :key="n" height="3.5rem" class="flex-1 min-w-[72px] rounded-md" />
</template>
<template v-else>
<div
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)] flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow] duration-150 hover:border-indigo-400/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="{ 'border-[var(--primary-color,#6366f1)] shadow-[0_0_0_3px_rgba(99,102,241,0.15)]': statusFilter === '' }"
@click="toggleStatusFilter('')"
>
<div class="text-[1.35rem] font-bold leading-none text-[var(--text-color)]">{{ totals.total }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Total</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'new'
? 'border-sky-500 bg-sky-500/5 shadow-[0_0_0_3px_rgba(14,165,233,0.15)]'
: 'border-sky-400/30 bg-sky-500/5 hover:border-sky-400/60'"
@click="toggleStatusFilter('new')"
>
<div class="text-[1.35rem] font-bold leading-none text-sky-500">{{ totals.nNew }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Novos</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'new'
? 'border-sky-500 bg-sky-500/5 shadow-[0_0_0_3px_rgba(14,165,233,0.15)]'
: 'border-sky-400/30 bg-sky-500/5 hover:border-sky-400/60'"
@click="toggleStatusFilter('new')"
>
<div class="text-[1.35rem] font-bold leading-none text-sky-500">{{ totals.nNew }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Novos</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'converted'
? 'border-green-500 bg-green-500/5 shadow-[0_0_0_3px_rgba(34,197,94,0.15)]'
: 'border-green-500/30 bg-green-500/5 hover:border-green-500/50'"
@click="toggleStatusFilter('converted')"
>
<div class="text-[1.35rem] font-bold leading-none text-green-500">{{ totals.nConv }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Convertidos</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'converted'
? 'border-green-500 bg-green-500/5 shadow-[0_0_0_3px_rgba(34,197,94,0.15)]'
: 'border-green-500/30 bg-green-500/5 hover:border-green-500/50'"
@click="toggleStatusFilter('converted')"
>
<div class="text-[1.35rem] font-bold leading-none text-green-500">{{ totals.nConv }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Convertidos</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'rejected'
? 'border-red-500 bg-red-500/5 shadow-[0_0_0_3px_rgba(239,68,68,0.15)]'
: 'border-red-500/30 bg-red-500/5 hover:border-red-500/50'"
@click="toggleStatusFilter('rejected')"
>
<div class="text-[1.35rem] font-bold leading-none text-red-500">{{ totals.nRej }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Rejeitados</div>
</div>
<div
class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
:class="statusFilter === 'rejected'
? 'border-red-500 bg-red-500/5 shadow-[0_0_0_3px_rgba(239,68,68,0.15)]'
: 'border-red-500/30 bg-red-500/5 hover:border-red-500/50'"
@click="toggleStatusFilter('rejected')"
>
<div class="text-[1.35rem] font-bold leading-none text-red-500">{{ totals.nRej }}</div>
<div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 whitespace-nowrap">Rejeitados</div>
</div>
</template>
</div>
<!--
@@ -635,8 +656,21 @@ onBeforeUnmount(() => { _observer?.disconnect() })
CARDS mobile (<md)
-->
<div class="md:hidden mx-3 mb-5">
<div v-if="loading" class="flex justify-center py-10">
<ProgressSpinner />
<div v-if="loading" class="flex flex-col gap-2.5">
<div v-for="n in 5" :key="n" class="rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-3.5 flex flex-col gap-3">
<div class="flex items-center gap-3">
<Skeleton shape="circle" size="2.5rem" />
<div class="flex flex-col gap-1.5 flex-1">
<Skeleton width="55%" height="13px" />
<Skeleton width="40%" height="11px" />
</div>
<Skeleton width="60px" height="22px" border-radius="999px" />
</div>
<div class="flex items-center justify-between">
<Skeleton width="30%" height="11px" />
<Skeleton width="60px" height="28px" border-radius="999px" />
</div>
</div>
</div>
<div v-else-if="filteredRows.length === 0" class="rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] py-10 text-center">
@@ -681,6 +715,10 @@ onBeforeUnmount(() => { _observer?.disconnect() })
</div>
</div>
<div class="px-3 md:px-4 pb-3">
<LoadedPhraseBlock v-if="hasLoaded" />
</div>
<!--
MODAL detalhe
-->