diff --git a/public/linegestao-logo.png b/public/linegestao-logo.png new file mode 100644 index 0000000..54ca585 Binary files /dev/null and b/public/linegestao-logo.png differ diff --git a/public/logo.png b/public/logo.png index dc9fdf6..511e4e7 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/app/components/header/header.html b/src/app/components/header/header.html index 359699f..cc76c2f 100644 --- a/src/app/components/header/header.html +++ b/src/app/components/header/header.html @@ -9,16 +9,24 @@ -
- -
-
- LineGestão + Line Gestão +
+
Line
+
{{ headerLogoSubtitle }}
+
+
+ + {{ clientTenantDisplayNameAbbrev }} +
+
+
-
+
@@ -209,8 +217,11 @@ -
-
LineGestão
+ Line Gestão +
+
Line
+
Gestão
+
diff --git a/src/app/components/header/header.scss b/src/app/components/header/header.scss index 313a733..2c891af 100644 --- a/src/app/components/header/header.scss +++ b/src/app/components/header/header.scss @@ -10,6 +10,9 @@ $text-main: #111827; $text-muted: #6b7280; $bg-light: #f9fafb; $border-color: #e5e7eb; +$logo-primary-blue-light: #004dcc; +$logo-primary-purple-dark: #6a0dad; +$logo-secondary-grey: #757575; /* Utils */ * { box-sizing: border-box; } @@ -39,15 +42,68 @@ $border-color: #e5e7eb; } .logo-area { - display: flex; align-items: center; gap: 10px; text-decoration: none; color: #111827; - .logo-icon { - width: 36px; height: 36px; background: conic-gradient(from 210deg, #2f6bff, #7c3aed, #ec4899, #f59e0b, #22c55e, #2f6bff); - color: #fff; border-radius: 50%; display: grid; place-items: center; font-size: 18px; box-shadow: 0 6px 14px rgba(47, 107, 255, 0.2); - } - .logo-text { - font-size: 19px; font-weight: 700; letter-spacing: -0.5px; - .highlight { background: linear-gradient(90deg, #2f6bff 0%, #7c3aed 55%, #ec4899 100%); -webkit-background-clip: text; background-clip: text; color: transparent; } - } + display: flex; align-items: center; gap: 14px; text-decoration: none; color: #111827; min-width: 0; +} + +.logo-symbol { + width: 48px; + height: 48px; + object-fit: contain; + flex: 0 0 auto; + filter: drop-shadow(0 8px 14px rgba(106, 13, 173, 0.2)); +} + +.lg-wordmark { + display: inline-flex; + flex-direction: column; + line-height: 0.92; + user-select: none; + -webkit-font-smoothing: antialiased; + text-rendering: geometricPrecision; + min-width: 0; + --scale: 0.34; +} + +.lg-wordmark__line { + font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 800; + font-size: calc(96px * var(--scale)); + letter-spacing: -0.02em; + white-space: nowrap; + background: linear-gradient( + 180deg, + #c8c3ff 0%, + #7a6cff 26%, + #4b3fe6 52%, + #2b21c8 74%, + #120a78 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-stroke: 0; + text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12); +} + +.lg-wordmark__movel { + font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 700; + font-size: calc(34px * var(--scale)); + letter-spacing: -0.01em; + white-space: nowrap; + margin-left: calc(0.33em * var(--scale)); + margin-top: calc(-6px * var(--scale)); + background: linear-gradient( + 180deg, + #6f7f96 0%, + #4b5b72 48%, + #2f3d52 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-stroke: 0; + text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12); } .nav-links { display: flex; align-items: center; justify-content: center; gap: 22px; flex: 1; } @@ -66,6 +122,58 @@ $border-color: #e5e7eb; @media (max-width: 900px) { .nav-links { display: none; } } .logged-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; } +.client-header-context { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + max-width: min(390px, 38vw); + padding-right: 2px; +} + +.client-chip { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + max-width: 100%; + padding: 6px 12px 6px 8px; + border-radius: 999px; + border: 1px solid rgba(28, 56, 201, 0.2); + background: linear-gradient(135deg, #ffffff 0%, #f5f8ff 55%, #eef2ff 100%); + box-shadow: 0 8px 20px rgba(17, 24, 39, 0.08); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.client-chip:hover { + transform: translateY(-1px); + box-shadow: 0 10px 22px rgba(17, 24, 39, 0.1); +} + +.client-chip__icon { + width: 28px; + height: 28px; + min-width: 28px; + border-radius: 999px; + display: grid; + place-items: center; + color: #fff; + background: linear-gradient(135deg, #2653d9 0%, #6a0dad 100%); + box-shadow: 0 4px 10px rgba(38, 83, 217, 0.28); + font-size: 12px; +} + +.client-chip__name { + font-size: 14px; + font-weight: 800; + color: #0f172a; + line-height: 1.15; + max-width: min(290px, 27vw); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + @media (min-width: 1200px) { .header-inner.container { max-width: none; @@ -637,7 +745,7 @@ $border-color: #e5e7eb; /* SIDE MENU */ .menu-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1050; } .side-menu { - position: fixed; top: 0; left: 0; height: 100vh; width: 260px; background: #fff; box-shadow: 4px 0 20px rgba(0,0,0,0.1); transform: translateX(-100%); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); z-index: 1100; display: flex; flex-direction: column; + position: fixed; top: 0; left: 0; height: 100vh; width: 280px; background: #fff; box-shadow: 4px 0 20px rgba(0,0,0,0.1); transform: translateX(-100%); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); z-index: 1100; display: flex; flex-direction: column; &.open { transform: translateX(0); } } .side-menu-header { padding: 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid $border-color; } @@ -655,7 +763,74 @@ $border-color: #e5e7eb; transition: color 0.2s ease; &:hover { color: $primary; } } -.side-logo { display: flex; align-items: center; gap: 10px; text-decoration: none; color: $text-main; font-weight: 700; .side-logo-icon { width: 32px; height: 32px; background: $primary; color: #fff; border-radius: 50%; display: grid; place-items: center; } } +.side-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + color: $text-main; + min-width: 0; +} + +.side-logo-symbol { + width: 42px; + height: 42px; + object-fit: contain; + flex: 0 0 auto; + filter: drop-shadow(0 4px 9px rgba(106, 13, 173, 0.2)); +} + +.side-wordmark { + display: inline-flex; + flex-direction: row; + align-items: baseline; + gap: 6px; + line-height: 1; + min-width: 0; + --scale: 0.23; +} + +.side-wordmark__line { + font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 800; + font-size: calc(92px * var(--scale)); + letter-spacing: -0.02em; + white-space: nowrap; + background: linear-gradient( + 180deg, + #c8c3ff 0%, + #7a6cff 26%, + #4b3fe6 52%, + #2b21c8 74%, + #120a78 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; +} + +.side-wordmark__movel { + font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 800; + font-size: calc(92px * var(--scale)); + letter-spacing: -0.012em; + white-space: nowrap; + margin-left: 0; + margin-top: 0; + background: linear-gradient( + 180deg, + #aeb8c7 0%, + #6b778d 50%, + #3f4b60 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; + position: relative; + top: 0; +} .side-menu-body { padding: 16px; display: flex; flex-direction: column; gap: 4px; } .side-item { padding: 10px 12px; border-radius: 8px; color: $text-main; text-decoration: none; font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 10px; @@ -686,17 +861,46 @@ $border-color: #e5e7eb; .logo-area { min-width: 0; + } - .logo-text { - font-size: 16px; - white-space: nowrap; - } + .lg-wordmark { + --scale: 0.29; + } + + .logo-symbol { + width: 42px; + height: 42px; } .logged-actions { gap: 8px; } + .client-header-context { + max-width: min(310px, 34vw); + } + + .client-chip { + gap: 8px; + padding: 5px 10px 5px 6px; + } + + .client-chip__icon { + width: 24px; + height: 24px; + min-width: 24px; + font-size: 11px; + } + + .client-chip__name { + font-size: 13px; + max-width: min(240px, 24vw); + } + + .side-wordmark { + --scale: 0.21; + } + .notifications-dropdown { right: 0; width: min(360px, calc(100vw - 24px)); @@ -769,17 +973,15 @@ $border-color: #e5e7eb; .logo-area { gap: 8px; min-width: 0; + } - .logo-icon { - width: 32px; - height: 32px; - font-size: 16px; - } + .logo-symbol { + width: 36px; + height: 36px; + } - .logo-text { - font-size: 14px; - line-height: 1; - } + .lg-wordmark { + --scale: 0.22; } /* Header público (Home/Login/Register): mantém logo visível e CTA fixo à direita */ @@ -788,13 +990,15 @@ $border-color: #e5e7eb; min-width: 0; } - .header-inner > .logo-area .logo-text { + .header-inner > .logo-area .lg-wordmark { display: block; - font-size: 14px; - line-height: 1; white-space: nowrap; } + .header-inner > .logo-area .lg-wordmark { + --scale: 0.2; + } + .header-inner > .header-actions { margin-left: auto; flex: 0 0 auto; @@ -808,8 +1012,12 @@ $border-color: #e5e7eb; } /* Header logado: mantém nome visível, porém menor para smartphone */ - .left-logged .logo-area .logo-text { - font-size: 13px; + .left-logged .logo-area .lg-wordmark { + --scale: 0.2; + } + + .client-header-context { + display: none; } .logged-actions { @@ -1112,16 +1320,13 @@ $border-color: #e5e7eb; gap: 6px; } - .header-inner > .logo-area .logo-icon { - width: 30px; - height: 30px; - font-size: 15px; + .header-inner > .logo-area .logo-symbol { + width: 32px; + height: 32px; } - .header-inner > .logo-area .logo-text { - display: block; - font-size: 13px; - letter-spacing: -0.25px; + .header-inner > .logo-area .lg-wordmark { + --scale: 0.18; } .header-inner > .header-actions .btn-login-header { @@ -1134,10 +1339,8 @@ $border-color: #e5e7eb; gap: 6px; } - .left-logged .logo-area .logo-text { - display: block; - font-size: 12px; - letter-spacing: -0.2px; + .left-logged .logo-area .lg-wordmark { + --scale: 0.175; } .logged-actions { diff --git a/src/app/components/header/header.ts b/src/app/components/header/header.ts index bfb9051..0d78ae7 100644 --- a/src/app/components/header/header.ts +++ b/src/app/components/header/header.ts @@ -2,6 +2,7 @@ import { Component, HostListener, Inject, ElementRef, ViewChild, AfterViewInit, import { RouterLink, Router, NavigationEnd } from '@angular/router'; import { CommonModule, isPlatformBrowser } from '@angular/common'; import { PLATFORM_ID } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { filter } from 'rxjs/operators'; import { AuthService } from '../../services/auth.service'; import { NotificationsService, NotificationDto } from '../../services/notifications.service'; @@ -10,6 +11,7 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractContro import { HttpErrorResponse } from '@angular/common/http'; import { CustomSelectComponent } from '../custom-select/custom-select'; import { Subscription } from 'rxjs'; +import { environment } from '../../../environments/environment'; import { confirmActionModal, confirmDeletionWithTyping, showDeletionWarning } from '../../utils/destructive-confirmation'; @Component({ @@ -33,6 +35,9 @@ export class Header implements AfterViewInit, OnDestroy { isHome = false; isSysAdmin = false; canViewAll = false; + clientTenantDisplayName = ''; + private clientTenantNameTenantId: string | null = null; + private readonly baseApi: string; notifications: NotificationDto[] = []; notificationsLoading = false; notificationsError = false; @@ -97,10 +102,14 @@ export class Header implements AfterViewInit, OnDestroy { private authService: AuthService, private notificationsService: NotificationsService, private usersService: UsersService, + private http: HttpClient, private fb: FormBuilder, private hostElement: ElementRef, @Inject(PLATFORM_ID) private platformId: object ) { + const raw = (environment.apiUrl || '').replace(/\/+$/, ''); + this.baseApi = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`; + this.createUserForm = this.fb.group( { nome: ['', [Validators.required, Validators.minLength(2)]], @@ -205,12 +214,22 @@ export class Header implements AfterViewInit, OnDestroy { if (!isPlatformBrowser(this.platformId)) { this.isSysAdmin = false; this.canViewAll = false; + this.clientTenantDisplayName = ''; + this.clientTenantNameTenantId = null; return; } const isSysAdmin = this.authService.hasRole('sysadmin'); const isGestor = this.authService.hasRole('gestor'); this.isSysAdmin = isSysAdmin; this.canViewAll = isSysAdmin || isGestor; + + if (!this.isClientHeader) { + this.clientTenantDisplayName = ''; + this.clientTenantNameTenantId = null; + return; + } + + this.ensureClientTenantName(); } toggleMenu() { @@ -435,6 +454,22 @@ export class Header implements AfterViewInit, OnDestroy { return this.notifications.filter(n => !n.lida).length; } + get isClientHeader(): boolean { + return this.isLoggedHeader && !this.canViewAll; + } + + get headerLogoSubtitle(): string { + return this.isClientHeader ? 'Gestão Empresas' : 'Gestão'; + } + + get headerLogoAriaLabel(): string { + return `Line ${this.headerLogoSubtitle}`; + } + + get clientTenantDisplayNameAbbrev(): string { + return this.abbreviateClientTenantName(this.clientTenantDisplayName); + } + get notificationsVisible() { return this.notificationsView === 'lidas' ? this.notifications.filter(n => n.lida) @@ -1130,4 +1165,66 @@ export class Header implements AfterViewInit, OnDestroy { private getHeaderElement(): HTMLElement | null { return this.hostElement.nativeElement.querySelector('.app-header'); } + + private ensureClientTenantName() { + const profile = this.authService.currentUserProfile; + const tenantId = String(profile?.tenantId || '').trim(); + + if (!tenantId) { + this.clientTenantDisplayName = ''; + this.clientTenantNameTenantId = null; + return; + } + + if (this.clientTenantNameTenantId === tenantId && this.clientTenantDisplayName) { + return; + } + + this.clientTenantNameTenantId = tenantId; + this.clientTenantDisplayName = ''; + + this.http.get(`${this.baseApi}/lines/clients`).subscribe({ + next: (clients) => { + const list = (clients || []) + .map((x) => String(x ?? '').trim()) + .filter((x) => !!x && x.localeCompare('RESERVA', 'pt-BR', { sensitivity: 'base' }) !== 0); + + this.clientTenantDisplayName = list[0] || this.resolveFallbackClientTenantName(); + }, + error: () => { + this.clientTenantDisplayName = this.resolveFallbackClientTenantName(); + }, + }); + } + + private resolveFallbackClientTenantName(): string { + const profileName = String(this.authService.currentUserProfile?.nome || '').trim(); + if (profileName) return profileName; + return 'Cliente'; + } + + private abbreviateClientTenantName(value: string): string { + const name = (value || '').trim(); + if (!name) return 'Cliente'; + + const maxLen = 30; + if (name.length <= maxLen) return name; + + const parts = name.split(/\s+/).filter(Boolean); + if (parts.length === 1) { + return `${parts[0].slice(0, maxLen - 1)}…`; + } + + const first = parts[0]; + const last = parts[parts.length - 1]; + let candidate = `${first} ${last}`; + + if (candidate.length <= maxLen) return candidate; + + const shortFirst = `${first.slice(0, Math.max(3, maxLen - (last.length + 3)))}.`; + candidate = `${shortFirst} ${last}`; + if (candidate.length <= maxLen) return candidate; + + return `${name.slice(0, maxLen - 1)}…`; + } } diff --git a/src/app/pages/dashboard/dashboard.html b/src/app/pages/dashboard/dashboard.html index c76e8b0..bb9943d 100644 --- a/src/app/pages/dashboard/dashboard.html +++ b/src/app/pages/dashboard/dashboard.html @@ -354,15 +354,6 @@ Ativas {{ statusResumo.ativos | number:'1.0-0' }}
-
- - Demais Linhas - {{ clientDemaisLinhas | number:'1.0-0' }} -
-
- Total - {{ statusResumo.total | number:'1.0-0' }} -
diff --git a/src/app/pages/dashboard/dashboard.scss b/src/app/pages/dashboard/dashboard.scss index f3a2dd7..80bfabf 100644 --- a/src/app/pages/dashboard/dashboard.scss +++ b/src/app/pages/dashboard/dashboard.scss @@ -206,11 +206,11 @@ } .hero-label { - font-size: 12px; + font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); - letter-spacing: 0.03em; + letter-spacing: 0.02em; } .hero-value { diff --git a/src/app/pages/dashboard/dashboard.ts b/src/app/pages/dashboard/dashboard.ts index 3062b8e..a4eeade 100644 --- a/src/app/pages/dashboard/dashboard.ts +++ b/src/app/pages/dashboard/dashboard.ts @@ -1250,13 +1250,6 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy { if (this.isCliente) { const overview = this.clientOverview; const cards: KpiCard[] = [ - { - key: 'linhas_total', - title: 'Total de Linhas', - value: this.formatInt(overview.totalLinhas), - icon: 'bi bi-sim-fill', - hint: 'Base do cliente', - }, { key: 'linhas_ativas', title: 'Linhas Ativas', @@ -1432,10 +1425,10 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy { // 1. Status Pie if (this.chartStatusPie?.nativeElement) { const chartLabels = this.isCliente - ? ['Ativas', 'Demais linhas'] + ? ['Ativas'] : ['Ativos', 'Perda/Roubo', 'Bloq 120d', 'Reservas', 'Outros']; const chartData = this.isCliente - ? [this.statusResumo.ativos, this.clientDemaisLinhas] + ? [this.statusResumo.ativos] : [ this.statusResumo.ativos, this.statusResumo.perdaRoubo, @@ -1444,7 +1437,7 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy { this.statusResumo.outras, ]; const chartColors = this.isCliente - ? [palette.status.ativos, '#cbd5e1'] + ? [palette.status.ativos] : [ palette.status.ativos, palette.status.blocked, @@ -1870,10 +1863,6 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy { return Number.isNaN(n) ? null : n; } - get clientDemaisLinhas(): number { - return Math.max(0, (this.statusResumo.total ?? 0) - (this.statusResumo.ativos ?? 0)); - } - trackByKpiKey = (_: number, item: KpiCard) => item.key; private getPalette() { diff --git a/src/app/pages/geral/geral.html b/src/app/pages/geral/geral.html index c08a725..fbcf476 100644 --- a/src/app/pages/geral/geral.html +++ b/src/app/pages/geral/geral.html @@ -1704,7 +1704,7 @@ Linha {{ detailData.linha || '-' }} -
+
Cliente {{ detailData.cliente || '-' }}
@@ -1712,6 +1712,10 @@ Usuário {{ detailData.usuario || '-' }}
+
+ Centro de Custos +   +
Item {{ detailData.item }} @@ -1724,6 +1728,10 @@ Tipo de Chip {{ detailData.tipoDeChip || '-' }}
+
+ Franquia Line + {{ formatFranquia(detailData.franquiaLine) }} +
diff --git a/src/app/pages/login/login.html b/src/app/pages/login/login.html index d3ae823..c08e1ad 100644 --- a/src/app/pages/login/login.html +++ b/src/app/pages/login/login.html @@ -4,9 +4,12 @@
- diff --git a/src/app/pages/login/login.scss b/src/app/pages/login/login.scss index 1e6f7e6..8969f2b 100644 --- a/src/app/pages/login/login.scss +++ b/src/app/pages/login/login.scss @@ -62,14 +62,86 @@ margin-bottom: 32px; .brand-logo { - display: flex; - align-items: center; - gap: 10px; - font-size: 20px; - font-weight: 700; - color: var(--text-main); - - i { color: var(--brand-blue); font-size: 24px; } + display: inline-flex; + align-items: center; + gap: 12px; + min-width: 0; + } + + .login-logo-symbol { + width: 54px; + height: 54px; + object-fit: contain; + flex: 0 0 auto; + filter: drop-shadow(0 8px 14px rgba(106, 13, 173, 0.2)); + } + + .login-wordmark { + display: inline-flex; + flex-direction: column; + line-height: 0.92; + min-width: 0; + --scale: 0.31; + } + + .login-wordmark__line { + font-family: "Poppins", "Nunito", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 800; + font-size: calc(96px * var(--scale)); + letter-spacing: -0.02em; + white-space: nowrap; + background: linear-gradient( + 180deg, + #c8c3ff 0%, + #7a6cff 26%, + #4b3fe6 52%, + #2b21c8 74%, + #120a78 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .login-wordmark__movel { + font-family: "Poppins", "Nunito", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-weight: 700; + font-size: calc(34px * var(--scale)); + letter-spacing: -0.01em; + white-space: nowrap; + margin-left: calc(0.33em * var(--scale)); + margin-top: calc(-6px * var(--scale)); + background: linear-gradient( + 180deg, + #aeb8c7 0%, + #6b778d 50%, + #3f4b60 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + @media (max-width: 1366px) { + .login-logo-symbol { + width: 48px; + height: 48px; + } + + .login-wordmark { + --scale: 0.27; + } + } + + @media (max-width: 576px) { + .login-logo-symbol { + width: 44px; + height: 44px; + } + + .login-wordmark { + --scale: 0.24; + } } } @@ -527,4 +599,4 @@ animation: fadeInUp 0.6s ease-out forwards; opacity: 0; transform: translateY(20px); -} \ No newline at end of file +} diff --git a/src/assets/linegestao-logo.png b/src/assets/linegestao-logo.png new file mode 100644 index 0000000..54ca585 Binary files /dev/null and b/src/assets/linegestao-logo.png differ