Feat: Deploy de Implementações/Ajustes
This commit is contained in:
parent
59c2cb828e
commit
851f8d6fc2
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 152 KiB |
|
|
@ -9,16 +9,24 @@
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a routerLink="/dashboard" class="logo-area" (click)="closeMenu()">
|
<a routerLink="/dashboard" class="logo-area" (click)="closeMenu()">
|
||||||
<div class="logo-icon">
|
<img src="linegestao-logo.png" alt="Line Gestão" class="logo-symbol" />
|
||||||
<i class="bi bi-layers-fill"></i>
|
<div class="lg-wordmark" [attr.aria-label]="headerLogoAriaLabel">
|
||||||
</div>
|
<div class="lg-wordmark__line">Line</div>
|
||||||
<div class="logo-text">
|
<div class="lg-wordmark__movel">{{ headerLogoSubtitle }}</div>
|
||||||
Line<span class="highlight">Gestão</span>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logged-actions">
|
<div class="logged-actions">
|
||||||
|
<div class="client-header-context" *ngIf="isClientHeader">
|
||||||
|
<div class="client-chip" [title]="clientTenantDisplayName || 'Cliente'">
|
||||||
|
<span class="client-chip__icon" aria-hidden="true">
|
||||||
|
<i class="bi bi-building"></i>
|
||||||
|
</span>
|
||||||
|
<span class="client-chip__name">{{ clientTenantDisplayNameAbbrev }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="notifications-menu" [class.open]="notificationsOpen" (click)="$event.stopPropagation()">
|
<div class="notifications-menu" [class.open]="notificationsOpen" (click)="$event.stopPropagation()">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -184,7 +192,7 @@
|
||||||
<button type="button" class="options-item" (click)="goToProfile()">
|
<button type="button" class="options-item" (click)="goToProfile()">
|
||||||
<i class="bi bi-person-circle"></i> Perfil
|
<i class="bi bi-person-circle"></i> Perfil
|
||||||
</button>
|
</button>
|
||||||
<div class="divider"></div>
|
<div class="divider" *ngIf="isSysAdmin"></div>
|
||||||
<button type="button" class="options-item" *ngIf="isSysAdmin" (click)="openCreateUserModal()">
|
<button type="button" class="options-item" *ngIf="isSysAdmin" (click)="openCreateUserModal()">
|
||||||
<i class="bi bi-person-plus"></i> Criar novo usuário
|
<i class="bi bi-person-plus"></i> Criar novo usuário
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -209,8 +217,11 @@
|
||||||
|
|
||||||
<ng-template #publicHeader>
|
<ng-template #publicHeader>
|
||||||
<a routerLink="/" class="logo-area">
|
<a routerLink="/" class="logo-area">
|
||||||
<div class="logo-icon"><i class="bi bi-layers-fill"></i></div>
|
<img src="linegestao-logo.png" alt="Line Gestão" class="logo-symbol" />
|
||||||
<div class="logo-text">Line<span class="highlight">Gestão</span></div>
|
<div class="lg-wordmark" aria-label="Line Gestão">
|
||||||
|
<div class="lg-wordmark__line">Line</div>
|
||||||
|
<div class="lg-wordmark__movel">Gestão</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav-links">
|
<nav class="nav-links">
|
||||||
<a href="https://www.linemovel.com.br/empresas" target="_blank" class="nav-link">Para Empresas</a>
|
<a href="https://www.linemovel.com.br/empresas" target="_blank" class="nav-link">Para Empresas</a>
|
||||||
|
|
@ -507,8 +518,11 @@
|
||||||
<aside *ngIf="isLoggedHeader" class="side-menu" [class.open]="menuOpen" (click)="$event.stopPropagation()">
|
<aside *ngIf="isLoggedHeader" class="side-menu" [class.open]="menuOpen" (click)="$event.stopPropagation()">
|
||||||
<div class="side-menu-header">
|
<div class="side-menu-header">
|
||||||
<a class="side-logo" routerLink="/dashboard" (click)="closeMenu()">
|
<a class="side-logo" routerLink="/dashboard" (click)="closeMenu()">
|
||||||
<span class="side-logo-icon"><i class="bi bi-layers-fill"></i></span>
|
<img src="linegestao-logo.png" alt="Line Gestão" class="side-logo-symbol" />
|
||||||
<span class="side-logo-text">Line<span class="highlight">Gestão</span></span>
|
<div class="side-wordmark" aria-label="Line Gestão">
|
||||||
|
<div class="side-wordmark__line">Line</div>
|
||||||
|
<div class="side-wordmark__movel">Gestão</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="close-btn" (click)="closeMenu()"><i class="bi bi-x-lg"></i></button>
|
<button type="button" class="close-btn" (click)="closeMenu()"><i class="bi bi-x-lg"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -546,8 +560,5 @@
|
||||||
<a *ngIf="canViewAll" routerLink="/trocanumero" routerLinkActive="active" class="side-item" (click)="closeMenu()">
|
<a *ngIf="canViewAll" routerLink="/trocanumero" routerLinkActive="active" class="side-item" (click)="closeMenu()">
|
||||||
<i class="bi bi-arrow-left-right"></i> <span>Troca de número</span>
|
<i class="bi bi-arrow-left-right"></i> <span>Troca de número</span>
|
||||||
</a>
|
</a>
|
||||||
<a *ngIf="isSysAdmin" routerLink="/system/fornecer-usuario" routerLinkActive="active" class="side-item" (click)="closeMenu()">
|
|
||||||
<i class="bi bi-shield-lock-fill"></i> <span>Criar credenciais do cliente</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ $text-main: #111827;
|
||||||
$text-muted: #6b7280;
|
$text-muted: #6b7280;
|
||||||
$bg-light: #f9fafb;
|
$bg-light: #f9fafb;
|
||||||
$border-color: #e5e7eb;
|
$border-color: #e5e7eb;
|
||||||
|
$logo-primary-blue-light: #004dcc;
|
||||||
|
$logo-primary-purple-dark: #6a0dad;
|
||||||
|
$logo-secondary-grey: #757575;
|
||||||
|
|
||||||
/* Utils */
|
/* Utils */
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
@ -39,15 +42,68 @@ $border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-area {
|
.logo-area {
|
||||||
display: flex; align-items: center; gap: 10px; text-decoration: none; color: #111827;
|
display: flex; align-items: center; gap: 14px; text-decoration: none; color: #111827; min-width: 0;
|
||||||
.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-symbol {
|
||||||
}
|
width: 48px;
|
||||||
.logo-text {
|
height: 48px;
|
||||||
font-size: 19px; font-weight: 700; letter-spacing: -0.5px;
|
object-fit: contain;
|
||||||
.highlight { background: linear-gradient(90deg, #2f6bff 0%, #7c3aed 55%, #ec4899 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
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; }
|
.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; } }
|
@media (max-width: 900px) { .nav-links { display: none; } }
|
||||||
.logged-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; }
|
.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) {
|
@media (min-width: 1200px) {
|
||||||
.header-inner.container {
|
.header-inner.container {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
|
@ -637,7 +745,7 @@ $border-color: #e5e7eb;
|
||||||
/* SIDE MENU */
|
/* SIDE MENU */
|
||||||
.menu-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1050; }
|
.menu-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1050; }
|
||||||
.side-menu {
|
.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); }
|
&.open { transform: translateX(0); }
|
||||||
}
|
}
|
||||||
.side-menu-header { padding: 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid $border-color; }
|
.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;
|
transition: color 0.2s ease;
|
||||||
&:hover { color: $primary; }
|
&: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-menu-body { padding: 16px; display: flex; flex-direction: column; gap: 4px; }
|
||||||
.side-item {
|
.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;
|
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 {
|
.logo-area {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-text {
|
.lg-wordmark {
|
||||||
font-size: 16px;
|
--scale: 0.29;
|
||||||
white-space: nowrap;
|
}
|
||||||
}
|
|
||||||
|
.logo-symbol {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-actions {
|
.logged-actions {
|
||||||
gap: 8px;
|
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 {
|
.notifications-dropdown {
|
||||||
right: 0;
|
right: 0;
|
||||||
width: min(360px, calc(100vw - 24px));
|
width: min(360px, calc(100vw - 24px));
|
||||||
|
|
@ -769,17 +973,15 @@ $border-color: #e5e7eb;
|
||||||
.logo-area {
|
.logo-area {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-icon {
|
.logo-symbol {
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
font-size: 16px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.logo-text {
|
.lg-wordmark {
|
||||||
font-size: 14px;
|
--scale: 0.22;
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header público (Home/Login/Register): mantém logo visível e CTA fixo à direita */
|
/* 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;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-inner > .logo-area .logo-text {
|
.header-inner > .logo-area .lg-wordmark {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-inner > .logo-area .lg-wordmark {
|
||||||
|
--scale: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
.header-inner > .header-actions {
|
.header-inner > .header-actions {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
@ -808,8 +1012,12 @@ $border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header logado: mantém nome visível, porém menor para smartphone */
|
/* Header logado: mantém nome visível, porém menor para smartphone */
|
||||||
.left-logged .logo-area .logo-text {
|
.left-logged .logo-area .lg-wordmark {
|
||||||
font-size: 13px;
|
--scale: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-header-context {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-actions {
|
.logged-actions {
|
||||||
|
|
@ -1112,16 +1320,13 @@ $border-color: #e5e7eb;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-inner > .logo-area .logo-icon {
|
.header-inner > .logo-area .logo-symbol {
|
||||||
width: 30px;
|
width: 32px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
font-size: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-inner > .logo-area .logo-text {
|
.header-inner > .logo-area .lg-wordmark {
|
||||||
display: block;
|
--scale: 0.18;
|
||||||
font-size: 13px;
|
|
||||||
letter-spacing: -0.25px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-inner > .header-actions .btn-login-header {
|
.header-inner > .header-actions .btn-login-header {
|
||||||
|
|
@ -1134,10 +1339,8 @@ $border-color: #e5e7eb;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-logged .logo-area .logo-text {
|
.left-logged .logo-area .lg-wordmark {
|
||||||
display: block;
|
--scale: 0.175;
|
||||||
font-size: 12px;
|
|
||||||
letter-spacing: -0.2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-actions {
|
.logged-actions {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Component, HostListener, Inject, ElementRef, ViewChild, AfterViewInit,
|
||||||
import { RouterLink, Router, NavigationEnd } from '@angular/router';
|
import { RouterLink, Router, NavigationEnd } from '@angular/router';
|
||||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import { PLATFORM_ID } from '@angular/core';
|
import { PLATFORM_ID } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../services/auth.service';
|
import { AuthService } from '../../services/auth.service';
|
||||||
import { NotificationsService, NotificationDto } from '../../services/notifications.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 { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { CustomSelectComponent } from '../custom-select/custom-select';
|
import { CustomSelectComponent } from '../custom-select/custom-select';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import { confirmActionModal, confirmDeletionWithTyping, showDeletionWarning } from '../../utils/destructive-confirmation';
|
import { confirmActionModal, confirmDeletionWithTyping, showDeletionWarning } from '../../utils/destructive-confirmation';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -33,6 +35,9 @@ export class Header implements AfterViewInit, OnDestroy {
|
||||||
isHome = false;
|
isHome = false;
|
||||||
isSysAdmin = false;
|
isSysAdmin = false;
|
||||||
canViewAll = false;
|
canViewAll = false;
|
||||||
|
clientTenantDisplayName = '';
|
||||||
|
private clientTenantNameTenantId: string | null = null;
|
||||||
|
private readonly baseApi: string;
|
||||||
notifications: NotificationDto[] = [];
|
notifications: NotificationDto[] = [];
|
||||||
notificationsLoading = false;
|
notificationsLoading = false;
|
||||||
notificationsError = false;
|
notificationsError = false;
|
||||||
|
|
@ -97,10 +102,14 @@ export class Header implements AfterViewInit, OnDestroy {
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
|
private http: HttpClient,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private hostElement: ElementRef<HTMLElement>,
|
private hostElement: ElementRef<HTMLElement>,
|
||||||
@Inject(PLATFORM_ID) private platformId: object
|
@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(
|
this.createUserForm = this.fb.group(
|
||||||
{
|
{
|
||||||
nome: ['', [Validators.required, Validators.minLength(2)]],
|
nome: ['', [Validators.required, Validators.minLength(2)]],
|
||||||
|
|
@ -205,12 +214,22 @@ export class Header implements AfterViewInit, OnDestroy {
|
||||||
if (!isPlatformBrowser(this.platformId)) {
|
if (!isPlatformBrowser(this.platformId)) {
|
||||||
this.isSysAdmin = false;
|
this.isSysAdmin = false;
|
||||||
this.canViewAll = false;
|
this.canViewAll = false;
|
||||||
|
this.clientTenantDisplayName = '';
|
||||||
|
this.clientTenantNameTenantId = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isSysAdmin = this.authService.hasRole('sysadmin');
|
const isSysAdmin = this.authService.hasRole('sysadmin');
|
||||||
const isGestor = this.authService.hasRole('gestor');
|
const isGestor = this.authService.hasRole('gestor');
|
||||||
this.isSysAdmin = isSysAdmin;
|
this.isSysAdmin = isSysAdmin;
|
||||||
this.canViewAll = isSysAdmin || isGestor;
|
this.canViewAll = isSysAdmin || isGestor;
|
||||||
|
|
||||||
|
if (!this.isClientHeader) {
|
||||||
|
this.clientTenantDisplayName = '';
|
||||||
|
this.clientTenantNameTenantId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ensureClientTenantName();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
|
|
@ -435,6 +454,22 @@ export class Header implements AfterViewInit, OnDestroy {
|
||||||
return this.notifications.filter(n => !n.lida).length;
|
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() {
|
get notificationsVisible() {
|
||||||
return this.notificationsView === 'lidas'
|
return this.notificationsView === 'lidas'
|
||||||
? this.notifications.filter(n => n.lida)
|
? this.notifications.filter(n => n.lida)
|
||||||
|
|
@ -1130,4 +1165,66 @@ export class Header implements AfterViewInit, OnDestroy {
|
||||||
private getHeaderElement(): HTMLElement | null {
|
private getHeaderElement(): HTMLElement | null {
|
||||||
return this.hostElement.nativeElement.querySelector('.app-header');
|
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<string[]>(`${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)}…`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -354,15 +354,6 @@
|
||||||
<span class="lbl">Ativas</span>
|
<span class="lbl">Ativas</span>
|
||||||
<span class="val">{{ statusResumo.ativos | number:'1.0-0' }}</span>
|
<span class="val">{{ statusResumo.ativos | number:'1.0-0' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item">
|
|
||||||
<span class="dot d-blocked-soft"></span>
|
|
||||||
<span class="lbl">Demais Linhas</span>
|
|
||||||
<span class="val">{{ clientDemaisLinhas | number:'1.0-0' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-item total-row">
|
|
||||||
<span class="lbl">Total</span>
|
|
||||||
<span class="val">{{ statusResumo.total | number:'1.0-0' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -206,11 +206,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-label {
|
.hero-label {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-value {
|
.hero-value {
|
||||||
|
|
|
||||||
|
|
@ -1250,13 +1250,6 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
||||||
if (this.isCliente) {
|
if (this.isCliente) {
|
||||||
const overview = this.clientOverview;
|
const overview = this.clientOverview;
|
||||||
const cards: KpiCard[] = [
|
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',
|
key: 'linhas_ativas',
|
||||||
title: 'Linhas Ativas',
|
title: 'Linhas Ativas',
|
||||||
|
|
@ -1432,10 +1425,10 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
||||||
// 1. Status Pie
|
// 1. Status Pie
|
||||||
if (this.chartStatusPie?.nativeElement) {
|
if (this.chartStatusPie?.nativeElement) {
|
||||||
const chartLabels = this.isCliente
|
const chartLabels = this.isCliente
|
||||||
? ['Ativas', 'Demais linhas']
|
? ['Ativas']
|
||||||
: ['Ativos', 'Perda/Roubo', 'Bloq 120d', 'Reservas', 'Outros'];
|
: ['Ativos', 'Perda/Roubo', 'Bloq 120d', 'Reservas', 'Outros'];
|
||||||
const chartData = this.isCliente
|
const chartData = this.isCliente
|
||||||
? [this.statusResumo.ativos, this.clientDemaisLinhas]
|
? [this.statusResumo.ativos]
|
||||||
: [
|
: [
|
||||||
this.statusResumo.ativos,
|
this.statusResumo.ativos,
|
||||||
this.statusResumo.perdaRoubo,
|
this.statusResumo.perdaRoubo,
|
||||||
|
|
@ -1444,7 +1437,7 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.statusResumo.outras,
|
this.statusResumo.outras,
|
||||||
];
|
];
|
||||||
const chartColors = this.isCliente
|
const chartColors = this.isCliente
|
||||||
? [palette.status.ativos, '#cbd5e1']
|
? [palette.status.ativos]
|
||||||
: [
|
: [
|
||||||
palette.status.ativos,
|
palette.status.ativos,
|
||||||
palette.status.blocked,
|
palette.status.blocked,
|
||||||
|
|
@ -1870,10 +1863,6 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
||||||
return Number.isNaN(n) ? null : n;
|
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;
|
trackByKpiKey = (_: number, item: KpiCard) => item.key;
|
||||||
|
|
||||||
private getPalette() {
|
private getPalette() {
|
||||||
|
|
|
||||||
|
|
@ -1704,7 +1704,7 @@
|
||||||
<span class="lbl">Linha</span>
|
<span class="lbl">Linha</span>
|
||||||
<span class="val text-blue fs-4">{{ detailData.linha || '-' }}</span>
|
<span class="val text-blue fs-4">{{ detailData.linha || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item span-2">
|
<div class="info-item span-2" *ngIf="!isClientRestricted">
|
||||||
<span class="lbl">Cliente</span>
|
<span class="lbl">Cliente</span>
|
||||||
<span class="val text-dark" [title]="detailData.cliente">{{ detailData.cliente || '-' }}</span>
|
<span class="val text-dark" [title]="detailData.cliente">{{ detailData.cliente || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1712,6 +1712,10 @@
|
||||||
<span class="lbl">Usuário</span>
|
<span class="lbl">Usuário</span>
|
||||||
<span class="val">{{ detailData.usuario || '-' }}</span>
|
<span class="val">{{ detailData.usuario || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-item span-2" *ngIf="isClientRestricted">
|
||||||
|
<span class="lbl">Centro de Custos</span>
|
||||||
|
<span class="val text-dark"> </span>
|
||||||
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="lbl">Item</span>
|
<span class="lbl">Item</span>
|
||||||
<span class="val">{{ detailData.item }}</span>
|
<span class="val">{{ detailData.item }}</span>
|
||||||
|
|
@ -1724,6 +1728,10 @@
|
||||||
<span class="lbl">Tipo de Chip</span>
|
<span class="lbl">Tipo de Chip</span>
|
||||||
<span class="val">{{ detailData.tipoDeChip || '-' }}</span>
|
<span class="val">{{ detailData.tipoDeChip || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-item" *ngIf="isClientRestricted">
|
||||||
|
<span class="lbl">Franquia Line</span>
|
||||||
|
<span class="val">{{ formatFranquia(detailData.franquiaLine) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@
|
||||||
|
|
||||||
<div class="left-content fade-in-up">
|
<div class="left-content fade-in-up">
|
||||||
<div class="brand-header mb-4">
|
<div class="brand-header mb-4">
|
||||||
<div class="brand-logo">
|
<div class="brand-logo" aria-label="Line Gestão">
|
||||||
<i class="bi bi-layers-fill"></i>
|
<img src="linegestao-logo.png" alt="Line Gestão" class="login-logo-symbol" />
|
||||||
<span>LineGestão</span>
|
<div class="login-wordmark">
|
||||||
|
<div class="login-wordmark__line">Line</div>
|
||||||
|
<div class="login-wordmark__movel">Gestão</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,86 @@
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|
||||||
.brand-logo {
|
.brand-logo {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
font-size: 20px;
|
min-width: 0;
|
||||||
font-weight: 700;
|
}
|
||||||
color: var(--text-main);
|
|
||||||
|
|
||||||
i { color: var(--brand-blue); font-size: 24px; }
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Loading…
Reference in New Issue