From 5f7b4e19e8af9fbfcd55e4248ff1a999858e0234 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 9 Feb 2026 21:50:03 -0300 Subject: [PATCH] =?UTF-8?q?Aplica=C3=A7=C3=A3o=20Funcionando?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/cta-button/cta-button.ts | 2 +- src/app/pages/resumo/resumo.html | 14 +-- src/app/pages/resumo/resumo.ts | 99 +++++++++++++++++---- src/server.ts | 2 +- src/styles.scss | 28 +++--- 5 files changed, 103 insertions(+), 42 deletions(-) diff --git a/src/app/components/cta-button/cta-button.ts b/src/app/components/cta-button/cta-button.ts index de6ea5a..8b54119 100644 --- a/src/app/components/cta-button/cta-button.ts +++ b/src/app/components/cta-button/cta-button.ts @@ -2,11 +2,11 @@ import { Component, EventEmitter, Output, Input } from '@angular/core'; @Component({ selector: 'app-cta-button', + standalone: true, templateUrl: './cta-button.html', styleUrls: ['./cta-button.scss'] }) export class CtaButtonComponent { - @Input() label: string = 'COMEÇAR AGORA'; @Input() width: string = '250px'; diff --git a/src/app/pages/resumo/resumo.html b/src/app/pages/resumo/resumo.html index 7464d04..cb43f63 100644 --- a/src/app/pages/resumo/resumo.html +++ b/src/app/pages/resumo/resumo.html @@ -102,14 +102,14 @@ + [value]="macrophonySearch" + (input)="onMacrophonySearch($any($event.target).value)" />
@@ -120,7 +120,7 @@
@@ -428,8 +428,8 @@ + [value]="group.search" + (input)="onGroupedSearch(group, $any($event.target).value)" />
@@ -439,7 +439,7 @@
diff --git a/src/app/pages/resumo/resumo.ts b/src/app/pages/resumo/resumo.ts index 6901ad5..81dbf0b 100644 --- a/src/app/pages/resumo/resumo.ts +++ b/src/app/pages/resumo/resumo.ts @@ -11,7 +11,6 @@ import { HostBinding } from '@angular/core'; import { isPlatformBrowser, CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import Chart from 'chart.js/auto'; import type { ChartConfiguration, ChartData, ScriptableContext, TooltipItem } from 'chart.js'; @@ -70,7 +69,7 @@ type GroupedTableState = { key: string; label: string; table: TableState; @Component({ standalone: true, - imports: [CommonModule, FormsModule], + imports: [CommonModule], templateUrl: './resumo.html', styleUrls: ['./resumo.scss'] }) @@ -500,15 +499,32 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { // Métodos obrigatórios para o template funcionar toggleMacrophonyCompact() { this.macrophonyCompact = !this.macrophonyCompact; } - onMacrophonySearch() { this.macrophonyPage = 1; this.updateMacrophonyView(); } - onMacrophonyPageSizeChange() { this.macrophonyPage = 1; this.updateMacrophonyView(); } + onMacrophonySearch(value?: string) { + if (typeof value === 'string') this.macrophonySearch = value; + this.macrophonyPage = 1; + this.updateMacrophonyView(); + } + onMacrophonyPageSizeChange(value?: number | string) { + if (value !== undefined && value !== null) { + const parsed = Number(value); + if (Number.isFinite(parsed) && parsed > 0) { + this.macrophonyPageSize = parsed; + } + } + this.macrophonyPage = 1; + this.updateMacrophonyView(); + } isMacrophonyOpen(key: string) { return this.macrophonyOpen.has(key); } toggleMacrophonyGroup(key: string) { if (this.macrophonyOpen.has(key)) this.macrophonyOpen.delete(key); else this.macrophonyOpen.add(key); } openMacrophonyDetail(g: MacrophonyGroup) { this.macrophonyDetailGroup = g; this.macrophonyDetailOpen = true; } closeMacrophonyDetail() { this.macrophonyDetailOpen = false; this.macrophonyDetailGroup = null; } goToMacrophonyPage(p: number) { this.macrophonyPage = p; this.updateMacrophonyView(); } - onGroupedSearch(g: GroupedTableState) { g.page = 1; this.updateGroupView(g); } + onGroupedSearch(g: GroupedTableState, value?: string) { + if (typeof value === 'string') g.search = value; + g.page = 1; + this.updateGroupView(g); + } toggleGroupedCompact(g: GroupedTableState) { g.compact = !g.compact; } exportGroupedCsv(g: GroupedTableState, file: string) { this.exportCsv(g.table, file); } isGroupedOpen(g: GroupedTableState, key: string) { return g.open.has(key); } @@ -1037,21 +1053,72 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { if (!isPlatformBrowser(this.platformId)) return; const rows = table.data ?? []; const columns = table.columns ?? []; - const header = columns.map((c) => c.label); - const body = rows.map((row) => - columns.map((column) => { - const value = this.formatCell(column, row); - const escaped = String(value).replace(/"/g, '""'); - return `"${escaped}"`; - }) - ); + const generatedAt = new Date().toLocaleString('pt-BR'); + const escapeHtml = (value: string) => + value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); - const csv = [header.join(';'), ...body.map((line) => line.join(';'))].join('\n'); - const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8;' }); + const headerHtml = columns + .map((column) => `${escapeHtml(column.label)}`) + .join(''); + + const bodyHtml = rows + .map((row, index) => { + const cells = columns + .map((column) => { + const value = this.formatCell(column, row); + const toneClass = column.tone ? this.getToneClass(column.value(row)) : ''; + const alignClass = column.align === 'right' ? 'text-right' : column.align === 'center' ? 'text-center' : ''; + const classes = [alignClass, toneClass].filter(Boolean).join(' '); + return `${escapeHtml(String(value))}`; + }) + .join(''); + return `${cells}`; + }) + .join(''); + + const html = ` + + + + + + +
${escapeHtml(table.label || 'Resumo')}
+
Exportado em ${escapeHtml(generatedAt)} | Total de linhas: ${rows.length}
+ + + ${headerHtml} + + + ${bodyHtml} + +
+ +`; + + const blob = new Blob([`\uFEFF${html}`], { type: 'application/vnd.ms-excel;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = `${filename}.csv`; + a.download = `${filename}.xls`; a.click(); URL.revokeObjectURL(url); } diff --git a/src/server.ts b/src/server.ts index 7896ef8..2582640 100644 --- a/src/server.ts +++ b/src/server.ts @@ -9,7 +9,7 @@ import { join } from 'node:path'; import { Readable } from 'node:stream'; const browserDistFolder = join(import.meta.dirname, '../browser'); -const apiBaseUrl = (process.env['API_BASE_URL'] || 'http://backend:8080').replace(/\/+$/, ''); +const apiBaseUrl = (process.env['API_BASE_URL'] || 'http://localhost:5298').replace(/\/+$/, ''); const app = express(); const angularApp = new AngularNodeAppEngine(); diff --git a/src/styles.scss b/src/styles.scss index f4c8f3e..55b2fe5 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -17,7 +17,13 @@ } body { - background-color: var(--bg-body); + min-height: 100vh; + background: + radial-gradient(900px 420px at 20% 10%, rgba(227, 61, 207, 0.1), transparent 60%), + radial-gradient(820px 380px at 80% 30%, rgba(227, 61, 207, 0.06), transparent 60%), + linear-gradient(180deg, #ffffff 0%, #f5f5f7 70%); + background-attachment: fixed; + background-repeat: no-repeat; color: var(--text-main); font-family: var(--font-sans); -webkit-font-smoothing: antialiased; @@ -84,31 +90,20 @@ select.form-control-sm { /* Empurra o conteúdo pra baixo do header fixo */ .app-main.has-header { position: relative; - padding-top: 84px; /* altura segura p/ header (mobile/desktop) */ - background: - radial-gradient(900px 420px at 20% 10%, rgba(227, 61, 207, 0.1), transparent 60%), - radial-gradient(820px 380px at 80% 30%, rgba(227, 61, 207, 0.06), transparent 60%), - linear-gradient(180deg, #ffffff 0%, #f5f5f7 70%); + padding-top: 76px; /* evita vão visual entre o header fixo e o conteúdo */ + background: transparent; } @media (max-width: 600px) { .app-main.has-header { - padding-top: 96px; + padding-top: 88px; } } /* Ajuste para monitores grandes: elimina o "vão" visual entre header e corpo. */ @media (min-width: 1400px) { .app-main.has-header { - padding-top: 72px; - } - - .container-geral, - .container-geral-responsive, - .container-fat, - .container-mureg, - .container-troca { - margin-top: 14px !important; + padding-top: 76px; } } @@ -326,4 +321,3 @@ app-header .modal-card .btn-secondary:hover { box-shadow: none !important; } } -