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,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 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 ( 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
}
}