+ Menu Hover no Layout Rail, Twilio, Sms, Email, Templates, LNovo Layout Configurações

This commit is contained in:
Leonardo
2026-03-25 08:39:45 -03:00
parent 53a4980396
commit 3f1786c9bf
59 changed files with 2553 additions and 1106 deletions
@@ -208,9 +208,9 @@ onMounted(async () => {
</div>
<!-- Linha 1: icon + título + botão -->
<div class="relative z-[1] flex items-center gap-3">
<div class="relative z-1 flex items-center gap-3">
<div class="flex items-center gap-2.5 flex-1 min-w-0">
<div class="cfg-subheader__icon grid place-items-center w-10 h-10 rounded-md flex-shrink-0" style="background: color-mix(in srgb, #10b981 15%, transparent); color: #059669">
<div class="cfg-subheader__icon grid place-items-center w-10 h-10 rounded-md shrink-0" style="background: color-mix(in srgb, #10b981 15%, transparent); color: #059669">
<i class="pi pi-wallet text-lg" />
</div>
<div class="min-w-0">
@@ -218,13 +218,13 @@ onMounted(async () => {
<div class="text-[0.78rem] text-[var(--text-color-secondary)] mt-0.5">Resumo e visão geral do período</div>
</div>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<div class="flex items-center gap-2 shrink-0">
<Button label="Ver lançamentos" icon="pi pi-list" severity="secondary" outlined class="rounded-full hidden sm:flex" @click="goToLancamentos" />
</div>
</div>
<!-- Linha 2: quick stats -->
<div class="relative z-[1] mt-2.5">
<div class="relative z-1 mt-2.5">
<template v-if="summaryLoading">
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2.5">
<div v-for="n in 4" :key="n" class="flex flex-col gap-1.5 px-4 py-2.5 rounded-md border border-[var(--surface-border)]">
@@ -249,7 +249,7 @@ onMounted(async () => {
<div class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border border-amber-500/25 bg-amber-500/5">
<div class="text-[1.35rem] font-bold leading-none text-amber-500">{{ fmtBRL(totalPendente) }}</div>
<div class="flex items-center gap-1.5 text-[0.7rem] text-amber-600/80 font-semibold">
<span class="h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse flex-shrink-0" />
<span class="h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse shrink-0" />
Pendente
</div>
</div>
@@ -281,7 +281,7 @@ onMounted(async () => {
-->
<section class="dash-card rounded-md">
<div class="dash-card__head gap-2.5 p-2.5">
<i class="pi pi-chart-bar cfg-subheader__icon w-10 h-10 rounded-md flex-shrink-0" />
<i class="pi pi-chart-bar cfg-subheader__icon w-10 h-10 rounded-md shrink-0" />
<div>
<div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Receita × Despesa</div>
<div class="dash-card__sub">Comparativo dos últimos 6 meses</div>
@@ -303,7 +303,7 @@ onMounted(async () => {
-->
<section class="dash-card rounded-md">
<div class="dash-card__head gap-2.5 p-2.5">
<i class="pi pi-calendar cfg-subheader__icon w-10 h-10 rounded-md flex-shrink-0" />
<i class="pi pi-calendar cfg-subheader__icon w-10 h-10 rounded-md shrink-0" />
<div>
<div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Projeção de Caixa</div>
<div class="dash-card__sub">Cobranças em aberto próximos 6 meses</div>
@@ -320,7 +320,7 @@ onMounted(async () => {
<div v-else class="flex flex-col gap-1.5 pt-1">
<div v-for="row in cashflowRows" :key="row.mes_label" class="flex items-center gap-3 px-3 py-2.5 rounded-md bg-[var(--surface-ground,#f8fafc)] hover:bg-[var(--surface-hover,#f1f5f9)] transition-colors duration-100">
<span class="font-bold text-[0.8rem] uppercase tracking-wide text-[var(--text-color)] min-w-[3.5rem] flex-shrink-0">{{ row.mes_label }}</span>
<span class="font-bold text-[0.8rem] uppercase tracking-wide text-[var(--text-color)] min-w-[3.5rem] shrink-0">{{ row.mes_label }}</span>
<div class="flex items-center gap-2 flex-1 flex-wrap text-[0.8rem]">
<span class="flex items-center gap-1 text-emerald-600 font-semibold">
<i class="pi pi-arrow-up-right text-xs" />
@@ -334,7 +334,7 @@ onMounted(async () => {
<span class="text-[var(--text-color-secondary)] opacity-30">·</span>
<span class="font-bold" :class="Number(row.saldo_projetado) >= 0 ? 'text-emerald-600' : 'text-red-500'"> saldo {{ fmtBRL(row.saldo_projetado) }} </span>
</div>
<Tag :value="row.count_registros + ' cobranças'" severity="secondary" class="ml-auto text-xs flex-shrink-0" />
<Tag :value="row.count_registros + ' cobranças'" severity="secondary" class="ml-auto text-xs shrink-0" />
</div>
</div>
</div>
@@ -345,12 +345,12 @@ onMounted(async () => {
-->
<section class="dash-card rounded-md shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary-color)_7%,transparent)]">
<div class="dash-card__head gap-2.5 p-2.5">
<i class="pi pi-list cfg-subheader__icon w-10 h-10 rounded-md flex-shrink-0" />
<i class="pi pi-list cfg-subheader__icon w-10 h-10 rounded-md shrink-0" />
<div class="flex-1">
<div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Últimos lançamentos</div>
<div class="dash-card__sub">Cobranças e receitas recentes</div>
</div>
<button class="flex items-center gap-1 bg-transparent border-none cursor-pointer text-xs font-semibold text-[var(--primary-color,#6366f1)] p-0 flex-shrink-0" @click="goToLancamentos">
<button class="flex items-center gap-1 bg-transparent border-none cursor-pointer text-xs font-semibold text-[var(--primary-color,#6366f1)] p-0 shrink-0" @click="goToLancamentos">
Ver todos <i class="pi pi-arrow-right text-xs" />
</button>
</div>
@@ -302,11 +302,11 @@ onBeforeUnmount(() => {
<div class="absolute w-72 h-72 top-0 -left-16 rounded-full blur-[60px] bg-indigo-500/[0.09]" />
</div>
<div class="relative z-[1] px-3 pt-2.5 pb-2">
<div class="relative z-1 px-3 pt-2.5 pb-2">
<!-- Linha 1: brand + actions -->
<div class="flex items-center gap-3">
<div class="flex items-center gap-2.5 flex-1 min-w-0">
<div class="cfg-subheader__icon grid place-items-center w-10 h-10 rounded-md flex-shrink-0" style="background: color-mix(in srgb, #10b981 15%, transparent); color: #059669">
<div class="cfg-subheader__icon grid place-items-center w-10 h-10 rounded-md shrink-0" style="background: color-mix(in srgb, #10b981 15%, transparent); color: #059669">
<i class="pi pi-wallet text-base" />
</div>
<div class="min-w-0">
@@ -316,13 +316,13 @@ onBeforeUnmount(() => {
</div>
<!-- Ações desktop -->
<div class="hidden sm:flex items-center gap-2 flex-shrink-0">
<div class="hidden sm:flex items-center gap-2 shrink-0">
<Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" v-tooltip.top="'Recarregar'" @click="applyFilters" />
<Button label="Lançamento manual" icon="pi pi-plus" class="rounded-full" @click="openManualDlg" />
</div>
<!-- Mobile -->
<div class="flex sm:hidden items-center gap-1 flex-shrink-0">
<div class="flex sm:hidden items-center gap-1 shrink-0">
<Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" @click="applyFilters" />
<Button icon="pi pi-plus" class="h-9 w-9 rounded-full" @click="openManualDlg" />
</div>
@@ -349,7 +349,7 @@ onBeforeUnmount(() => {
>
<div class="text-[1.25rem] font-bold leading-none text-amber-500">{{ fmtBRL(summary.totalPending) }}</div>
<div class="flex items-center gap-1.5 text-[0.7rem] text-amber-600/80 font-semibold">
<span class="h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse flex-shrink-0" />
<span class="h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse shrink-0" />
Pendente
<span class="ml-auto font-bold bg-amber-500/10 rounded-full px-1.5">{{ summary.countByStatus.pending ?? 0 }}</span>
</div>
@@ -397,12 +397,12 @@ onBeforeUnmount(() => {
-->
<section class="dash-card rounded-md">
<div class="dash-card__head gap-2.5 p-2.5">
<i class="pi pi-filter cfg-subheader__icon w-10 h-10 rounded-md flex-shrink-0" />
<i class="pi pi-filter cfg-subheader__icon w-10 h-10 rounded-md shrink-0" />
<div class="flex-1 min-w-0">
<div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Filtros</div>
<div class="dash-card__sub">Refine por status, tipo, paciente ou período</div>
</div>
<Button v-if="hasActiveFilters" label="Limpar" icon="pi pi-filter-slash" severity="danger" outlined size="small" class="rounded-full ml-auto flex-shrink-0" @click="clearFilters" />
<Button v-if="hasActiveFilters" label="Limpar" icon="pi pi-filter-slash" severity="danger" outlined size="small" class="rounded-full ml-auto shrink-0" @click="clearFilters" />
</div>
<div class="px-4 py-3">
<div class="flex flex-col sm:flex-row sm:flex-wrap items-start sm:items-end gap-3">
@@ -428,7 +428,7 @@ onBeforeUnmount(() => {
<Select id="fin-filter-patient" v-model="filterPatient" :options="patients" optionLabel="nome_completo" filter :filterFields="['nome_completo']" showClear class="w-full">
<template #option="{ option }">
<div class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full flex-shrink-0" :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--surface-border)' }" />
<span class="h-2 w-2 rounded-full shrink-0" :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--surface-border)' }" />
<span>{{ option.nome_completo }}</span>
</div>
</template>
@@ -452,7 +452,7 @@ onBeforeUnmount(() => {
Erro de carregamento
-->
<div v-if="error" class="rounded-md border border-red-500/30 bg-red-500/5 px-4 py-3 flex items-center gap-3 text-red-600">
<i class="pi pi-exclamation-triangle flex-shrink-0" />
<i class="pi pi-exclamation-triangle shrink-0" />
<span class="text-[1rem]">{{ error }}</span>
<Button icon="pi pi-refresh" severity="danger" text size="small" class="ml-auto" @click="applyFilters" />
</div>
@@ -497,7 +497,7 @@ onBeforeUnmount(() => {
<!-- Linha 1: paciente + valor + status -->
<div class="flex items-start gap-2.5">
<div
class="h-8 w-8 rounded-full flex-shrink-0 grid place-items-center text-white text-xs font-bold mt-0.5"
class="h-8 w-8 rounded-full shrink-0 grid place-items-center text-white text-xs font-bold mt-0.5"
:style="rec.patients?.identification_color ? { background: rec.patients.identification_color } : { background: 'var(--primary-color, #6366f1)' }"
>
{{ rec.patients?.nome_completo?.[0]?.toUpperCase() ?? '?' }}
@@ -510,7 +510,7 @@ onBeforeUnmount(() => {
{{ rec.agenda_eventos ? fmtDateTime(rec.agenda_eventos.inicio_em) : 'Lançamento manual' }}
</div>
</div>
<div class="flex flex-col items-end gap-1 flex-shrink-0">
<div class="flex flex-col items-end gap-1 shrink-0">
<span class="font-bold text-[var(--text-color)]">{{ fmtBRL(rec.final_amount) }}</span>
<Tag :value="STATUS_CFG[rec.status]?.label ?? rec.status" :severity="STATUS_CFG[rec.status]?.severity" class="text-xs" />
</div>
@@ -547,7 +547,7 @@ onBeforeUnmount(() => {
<section class="hidden md:block dash-card rounded-md shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary-color)_7%,transparent)]">
<!-- Header -->
<div class="dash-card__head gap-2.5 p-2.5">
<i class="pi pi-table cfg-subheader__icon w-10 h-10 rounded-md flex-shrink-0" />
<i class="pi pi-table cfg-subheader__icon w-10 h-10 rounded-md shrink-0" />
<div class="flex-1">
<div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Registros</div>
<div class="dash-card__sub">Lista completa de cobranças e lançamentos</div>
@@ -584,7 +584,7 @@ onBeforeUnmount(() => {
<template #body="{ data }">
<div class="flex items-center gap-2">
<div
class="h-6 w-6 rounded-full flex-shrink-0 grid place-items-center text-white text-[0.6rem] font-bold"
class="h-6 w-6 rounded-full shrink-0 grid place-items-center text-white text-[0.6rem] font-bold"
:style="data.patients?.identification_color ? { background: data.patients.identification_color } : { background: 'var(--primary-color, #6366f1)' }"
>
{{ data.patients?.nome_completo?.[0]?.toUpperCase() ?? '?' }}
@@ -688,7 +688,7 @@ onBeforeUnmount(() => {
<div class="font-semibold text-sm text-[var(--text-color)] truncate">{{ payDlgRecord.patients?.nome_completo ?? '—' }}</div>
<div class="text-[0.75rem] text-[var(--text-color-secondary)] mt-0.5">Vencimento: {{ fmtDate(payDlgRecord.due_date) }}</div>
</div>
<div class="text-right flex-shrink-0">
<div class="text-right shrink-0">
<div class="text-lg font-bold text-[var(--text-color)]">{{ fmtBRL(payDlgRecord.final_amount) }}</div>
<div v-if="payDlgRecord.discount_amount > 0" class="text-[0.7rem] text-[var(--text-color-secondary)] line-through">
{{ fmtBRL(payDlgRecord.amount) }}
@@ -745,7 +745,7 @@ onBeforeUnmount(() => {
<Select v-model="manualForm.patient" :options="patients" optionLabel="nome_completo" filter :filterFields="['nome_completo']" showClear placeholder="Selecionar paciente..." class="w-full">
<template #option="{ option }">
<div class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full flex-shrink-0" :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--surface-border)' }" />
<span class="h-2 w-2 rounded-full shrink-0" :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--surface-border)' }" />
<span>{{ option.nome_completo }}</span>
</div>
</template>