Copyright, Financeiro, Lançamentos, aprimoramentos de ui
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user