Compare commits

...

2 Commits

Author SHA1 Message Date
Eduardo Lopes 7962420565 feat: atualizar clientes em tempo real na criacao de credenciais 2026-03-03 14:46:27 -03:00
Eduardo f252d604d6 Feat: Deploy Alterações Logo 2026-03-03 13:12:47 -03:00
6 changed files with 222 additions and 57 deletions

View File

@ -42,7 +42,7 @@ $logo-secondary-grey: #757575;
} }
.logo-area { .logo-area {
display: flex; align-items: center; gap: 14px; text-decoration: none; color: #111827; min-width: 0; display: flex; align-items: center; gap: 10px; text-decoration: none; color: #111827; min-width: 0;
} }
.logo-symbol { .logo-symbol {
@ -61,7 +61,7 @@ $logo-secondary-grey: #757575;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: geometricPrecision; text-rendering: geometricPrecision;
min-width: 0; min-width: 0;
--scale: 0.34; --scale: 0.31;
} }
.lg-wordmark__line { .lg-wordmark__line {
@ -106,6 +106,91 @@ $logo-secondary-grey: #757575;
text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12); text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12);
} }
/* Header padrão (Home/Login/Interno): "LineGestão" em uma única linha */
.lg-wordmark:not([aria-label='Line Gestão Empresas']) {
.lg-wordmark__line {
display: inline-flex;
align-items: baseline;
&::after {
content: 'Gestão';
margin-left: 0.02em;
font: inherit;
letter-spacing: inherit;
background: linear-gradient(
180deg,
#c8c3ff 0%,
#7a6cff 26%,
#4b3fe6 52%,
#2b21c8 74%,
#120a78 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12);
}
}
.lg-wordmark__movel {
display: none;
}
}
/* Logo do cliente: "LineGestão" na primeira linha e "Empresas" abaixo */
.lg-wordmark[aria-label='Line Gestão Empresas'] {
.lg-wordmark__line {
display: inline-flex;
align-items: baseline;
font-size: calc(86px * var(--scale));
&::after {
content: 'Gestão';
margin-left: 0.02em;
font: inherit;
letter-spacing: inherit;
background: linear-gradient(
180deg,
#c8c3ff 0%,
#7a6cff 26%,
#4b3fe6 52%,
#2b21c8 74%,
#120a78 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12);
}
}
.lg-wordmark__movel {
font-size: 0;
margin-left: 0;
margin-top: calc(-6px * var(--scale));
line-height: 1;
align-self: flex-start;
&::before {
content: 'Empresas';
font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight: 700;
font-size: calc(38px * var(--scale));
letter-spacing: -0.01em;
background: linear-gradient(
180deg,
#6f7f96 0%,
#4b5b72 48%,
#2f3d52 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
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; }
.nav-links .nav-link { .nav-links .nav-link {
display: inline-flex; align-items: center; gap: 6px; color: $text-main; text-decoration: none; font-weight: 600; font-size: 14px; transition: color 0.2s; display: inline-flex; align-items: center; gap: 6px; color: $text-main; text-decoration: none; font-weight: 600; font-size: 14px; transition: color 0.2s;
@ -766,7 +851,7 @@ $logo-secondary-grey: #757575;
.side-logo { .side-logo {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 8px;
text-decoration: none; text-decoration: none;
color: $text-main; color: $text-main;
min-width: 0; min-width: 0;
@ -784,13 +869,15 @@ $logo-secondary-grey: #757575;
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
align-items: baseline; align-items: baseline;
gap: 6px; gap: 0;
line-height: 1; line-height: 1;
min-width: 0; min-width: 0;
--scale: 0.23; --scale: 0.23;
} }
.side-wordmark__line { .side-wordmark__line {
display: inline-flex;
align-items: baseline;
font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
font-weight: 800; font-weight: 800;
font-size: calc(92px * var(--scale)); font-size: calc(92px * var(--scale));
@ -808,28 +895,29 @@ $logo-secondary-grey: #757575;
background-clip: text; background-clip: text;
color: transparent; color: transparent;
line-height: 1; line-height: 1;
}
.side-wordmark__movel { &::after {
font-family: 'Poppins', 'Nunito', system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; content: 'Gestão';
font-weight: 800;
font-size: calc(92px * var(--scale));
letter-spacing: -0.012em;
white-space: nowrap;
margin-left: 0; margin-left: 0;
margin-top: 0; font: inherit;
letter-spacing: inherit;
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
#aeb8c7 0%, #c8c3ff 0%,
#6b778d 50%, #7a6cff 26%,
#3f4b60 100% #4b3fe6 52%,
#2b21c8 74%,
#120a78 100%
); );
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
color: transparent; color: transparent;
line-height: 1; text-shadow: 0 1px 1px rgba(15, 23, 42, 0.12);
position: relative; }
top: 0; }
.side-wordmark__movel {
display: none;
} }
.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 {
@ -864,7 +952,7 @@ $logo-secondary-grey: #757575;
} }
.lg-wordmark { .lg-wordmark {
--scale: 0.29; --scale: 0.27;
} }
.logo-symbol { .logo-symbol {
@ -971,7 +1059,7 @@ $logo-secondary-grey: #757575;
} }
.logo-area { .logo-area {
gap: 8px; gap: 6px;
min-width: 0; min-width: 0;
} }
@ -981,7 +1069,7 @@ $logo-secondary-grey: #757575;
} }
.lg-wordmark { .lg-wordmark {
--scale: 0.22; --scale: 0.21;
} }
/* 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 */
@ -996,7 +1084,7 @@ $logo-secondary-grey: #757575;
} }
.header-inner > .logo-area .lg-wordmark { .header-inner > .logo-area .lg-wordmark {
--scale: 0.2; --scale: 0.19;
} }
.header-inner > .header-actions { .header-inner > .header-actions {
@ -1013,7 +1101,7 @@ $logo-secondary-grey: #757575;
/* 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 .lg-wordmark { .left-logged .logo-area .lg-wordmark {
--scale: 0.2; --scale: 0.19;
} }
.client-header-context { .client-header-context {
@ -1317,7 +1405,7 @@ $logo-secondary-grey: #757575;
@media (max-width: 420px) { @media (max-width: 420px) {
.header-inner > .logo-area { .header-inner > .logo-area {
gap: 6px; gap: 5px;
} }
.header-inner > .logo-area .logo-symbol { .header-inner > .logo-area .logo-symbol {
@ -1336,7 +1424,7 @@ $logo-secondary-grey: #757575;
} }
.left-logged .logo-area { .left-logged .logo-area {
gap: 6px; gap: 5px;
} }
.left-logged .logo-area .lg-wordmark { .left-logged .logo-area .lg-wordmark {

View File

@ -1190,9 +1190,16 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
'BLOQUEAR', 'BLOQUEAR',
'BLOQUEAD', 'BLOQUEAD',
'BLOQUEADO', 'BLOQUEADO',
'BLOQUEIO',
'BLOQ120',
'RESERVA', 'RESERVA',
'NAOATRIBUIDO', 'NAOATRIBUIDO',
'PENDENTE', 'PENDENTE',
'COBRANCA',
'FATURAMENTO',
'FINANCEIRO',
'BACKOFFICE',
'ADMINISTRATIVO',
]; ];
if (invalidUserTokens.some((token) => usuarioKey.includes(token))) { if (invalidUserTokens.some((token) => usuarioKey.includes(token))) {
return false; return false;

View File

@ -21,6 +21,7 @@ import { NavigationEnd, Router } from '@angular/router';
import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { PlanAutoFillService } from '../../services/plan-autofill.service'; import { PlanAutoFillService } from '../../services/plan-autofill.service';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { TenantSyncService } from '../../services/tenant-sync.service';
import { firstValueFrom, Subscription, filter } from 'rxjs'; import { firstValueFrom, Subscription, filter } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation'; import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation';
@ -121,7 +122,9 @@ interface ApiLineDetail {
} }
type UpdateMobileLineRequest = Omit<ApiLineDetail, 'id'>; type UpdateMobileLineRequest = Omit<ApiLineDetail, 'id'>;
type CreateMobileLineRequest = Omit<ApiLineDetail, 'id'>; type CreateMobileLineRequest = Omit<ApiLineDetail, 'id'> & {
reservaLineId?: string | null;
};
interface ClientGroupDto { interface ClientGroupDto {
cliente: string; cliente: string;
@ -268,7 +271,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private planAutoFill: PlanAutoFillService, private planAutoFill: PlanAutoFillService,
private authService: AuthService, private authService: AuthService,
private router: Router private router: Router,
private tenantSyncService: TenantSyncService
) {} ) {}
private readonly apiBase = (() => { private readonly apiBase = (() => {
@ -2716,11 +2720,12 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
private buildCreatePayload(model: any): CreateMobileLineRequest { private buildCreatePayload(model: any): CreateMobileLineRequest {
this.calculateFinancials(model); this.calculateFinancials(model);
const { contaEmpresa: _contaEmpresa, uid: _uid, reservaLineId: _reservaLineId, ...createModelPayload } = model; const { contaEmpresa: _contaEmpresa, uid: _uid, ...createModelPayload } = model;
return { return {
...createModelPayload, ...createModelPayload,
item: Number(model.item), item: Number(model.item),
reservaLineId: (model.reservaLineId ?? '').toString().trim() || null,
dataBloqueio: this.dateInputToIso(model.dataBloqueio), dataBloqueio: this.dateInputToIso(model.dataBloqueio),
dataEntregaOpera: this.dateInputToIso(model.dataEntregaOpera), dataEntregaOpera: this.dateInputToIso(model.dataEntregaOpera),
dataEntregaCliente: this.dateInputToIso(model.dataEntregaCliente), dataEntregaCliente: this.dateInputToIso(model.dataEntregaCliente),
@ -2755,7 +2760,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
linha: (row.linha ?? '').toString(), linha: (row.linha ?? '').toString(),
chip: (row.chip ?? '').toString(), chip: (row.chip ?? '').toString(),
usuario: (row.usuario ?? '').toString(), usuario: (row.usuario ?? '').toString(),
tipoDeChip: (row.tipoDeChip ?? '').toString() tipoDeChip: (row.tipoDeChip ?? '').toString(),
reservaLineId: null
}; };
return this.buildCreatePayload(lineModel); return this.buildCreatePayload(lineModel);
@ -2772,9 +2778,13 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
private async finalizeCreateSuccess(createdCount: number) { private async finalizeCreateSuccess(createdCount: number) {
const targetClient = (this.createModel?.cliente ?? '').toString().trim(); const targetClient = (this.createModel?.cliente ?? '').toString().trim();
const isNewClientCreated = this.createMode === 'NEW_CLIENT' && !!targetClient;
this.createSaving = false; this.createSaving = false;
this.closeAllModals(); this.closeAllModals();
if (isNewClientCreated) {
this.tenantSyncService.notifyTenantsChanged();
}
await this.showToast(this.getCreateSuccessMessage(createdCount)); await this.showToast(this.getCreateSuccessMessage(createdCount));

View File

@ -64,7 +64,7 @@
.brand-logo { .brand-logo {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 12px; gap: 8px;
min-width: 0; min-width: 0;
} }
@ -81,7 +81,7 @@
flex-direction: column; flex-direction: column;
line-height: 0.92; line-height: 0.92;
min-width: 0; min-width: 0;
--scale: 0.31; --scale: 0.28;
} }
.login-wordmark__line { .login-wordmark__line {
@ -101,26 +101,32 @@
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
color: transparent; color: transparent;
}
.login-wordmark__movel { display: inline-flex;
font-family: "Poppins", "Nunito", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; align-items: baseline;
font-weight: 700;
font-size: calc(34px * var(--scale)); &::after {
letter-spacing: -0.01em; content: 'Gestão';
white-space: nowrap; margin-left: 0.02em;
margin-left: calc(0.33em * var(--scale)); font: inherit;
margin-top: calc(-6px * var(--scale)); letter-spacing: inherit;
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
#aeb8c7 0%, #c8c3ff 0%,
#6b778d 50%, #7a6cff 26%,
#3f4b60 100% #4b3fe6 52%,
#2b21c8 74%,
#120a78 100%
); );
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
color: transparent; color: transparent;
} }
}
.login-wordmark__movel {
display: none;
}
@media (max-width: 1366px) { @media (max-width: 1366px) {
.login-logo-symbol { .login-logo-symbol {
@ -129,7 +135,7 @@
} }
.login-wordmark { .login-wordmark {
--scale: 0.27; --scale: 0.25;
} }
} }
@ -140,7 +146,7 @@
} }
.login-wordmark { .login-wordmark {
--scale: 0.24; --scale: 0.22;
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { import {
@ -10,12 +10,14 @@ import {
Validators, Validators,
} from '@angular/forms'; } from '@angular/forms';
import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { Subscription } from 'rxjs';
import { import {
SysadminService, SysadminService,
SystemTenantDto, SystemTenantDto,
CreateSystemTenantUserResponse, CreateSystemTenantUserResponse,
} from '../../services/sysadmin.service'; } from '../../services/sysadmin.service';
import { TenantSyncService } from '../../services/tenant-sync.service';
@Component({ @Component({
selector: 'app-system-provision-user', selector: 'app-system-provision-user',
@ -24,7 +26,7 @@ import {
templateUrl: './system-provision-user.html', templateUrl: './system-provision-user.html',
styleUrls: ['./system-provision-user.scss'], styleUrls: ['./system-provision-user.scss'],
}) })
export class SystemProvisionUserPage implements OnInit { export class SystemProvisionUserPage implements OnInit, OnDestroy {
readonly sourceType = 'MobileLines.Cliente'; readonly sourceType = 'MobileLines.Cliente';
provisionForm: FormGroup; provisionForm: FormGroup;
@ -37,10 +39,12 @@ export class SystemProvisionUserPage implements OnInit {
submitErrors: string[] = []; submitErrors: string[] = [];
successMessage = ''; successMessage = '';
createdUser: CreateSystemTenantUserResponse | null = null; createdUser: CreateSystemTenantUserResponse | null = null;
private tenantChangesSub?: Subscription;
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private sysadminService: SysadminService private sysadminService: SysadminService,
private tenantSyncService: TenantSyncService
) { ) {
this.provisionForm = this.fb.group( this.provisionForm = this.fb.group(
{ {
@ -56,6 +60,18 @@ export class SystemProvisionUserPage implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.tenantChangesSub = this.tenantSyncService.changes$.subscribe(() => {
this.loadTenants();
});
this.loadTenants();
}
ngOnDestroy(): void {
this.tenantChangesSub?.unsubscribe();
}
@HostListener('window:focus')
onWindowFocus(): void {
this.loadTenants(); this.loadTenants();
} }

View File

@ -0,0 +1,38 @@
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { EMPTY, Observable, Subject, fromEvent, merge } from 'rxjs';
import { filter, map } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class TenantSyncService {
private readonly storageKey = 'linegestao.tenants.updatedAt';
private readonly localChangesSubject = new Subject<void>();
readonly changes$: Observable<void>;
private readonly isBrowser: boolean;
constructor(@Inject(PLATFORM_ID) platformId: object) {
this.isBrowser = isPlatformBrowser(platformId);
const storageChanges$ = this.isBrowser
? fromEvent<StorageEvent>(window, 'storage').pipe(
filter((event) => event.key === this.storageKey && !!event.newValue),
map(() => void 0)
)
: EMPTY;
this.changes$ = merge(this.localChangesSubject.asObservable(), storageChanges$);
}
notifyTenantsChanged(): void {
this.localChangesSubject.next();
if (!this.isBrowser) return;
try {
localStorage.setItem(this.storageKey, String(Date.now()));
} catch {
// ignore
}
}
}