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;
}
}
-