line-gestao-frontend/src/app/pages/faturamento/faturamento.html

340 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- faturamento.html (COMPLETO - tabela enxuta + modal detalhes completo) -->
<!-- Toast (Sucesso) -->
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 10000;">
<div #successToast class="toast text-bg-success border-0 shadow" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header border-bottom-0">
<strong class="me-auto text-primary">LineGestão</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Fechar"></button>
</div>
<div class="toast-body bg-white rounded-bottom text-dark">
{{ toastMessage }}
</div>
</div>
</div>
<section class="fat-page" (click)="closeClientDropdown()">
<span class="page-blob blob-1" aria-hidden="true"></span>
<span class="page-blob blob-2" aria-hidden="true"></span>
<span class="page-blob blob-3" aria-hidden="true"></span>
<span class="page-blob blob-4" aria-hidden="true"></span>
<div class="container-fat">
<div class="fat-card" data-animate>
<div class="fat-header">
<div class="header-row-top">
<div class="title-badge" data-animate>
<i class="bi bi-cash-coin"></i> Faturamento
</div>
<div class="header-title" data-animate>
<h5 class="title mb-0">Faturamento</h5>
<small class="subtitle">Totais, lucro e comparativo Vivo x Line</small>
</div>
<div class="header-actions d-flex gap-2 justify-content-end" data-animate>
<button type="button" class="btn btn-glass btn-sm" (click)="onExport()" [disabled]="loading || exporting">
<span *ngIf="!exporting"><i class="bi bi-download me-1"></i> Exportar</span>
<span *ngIf="exporting"><span class="spinner-border spinner-border-sm me-2"></span> Exportando...</span>
</button>
<button type="button" class="btn btn-glass btn-sm" (click)="onExportTemplate()" [disabled]="loading || exportingTemplate">
<span *ngIf="!exportingTemplate"><i class="bi bi-file-earmark-arrow-down me-1"></i> Exportar Modelo</span>
<span *ngIf="exportingTemplate"><span class="spinner-border spinner-border-sm me-2"></span> Gerando Modelo...</span>
</button>
</div>
</div>
<!-- FILTROS -->
<div class="filters-row mt-4" data-animate>
<div class="filter-tabs">
<button type="button" class="filter-tab" [class.active]="filterTipo === 'ALL'" (click)="setFilter('ALL')" [disabled]="loading">
Todos
</button>
<button type="button" class="filter-tab" [class.active]="filterTipo === 'PF'" (click)="setFilter('PF')" [disabled]="loading">
<i class="bi bi-person me-1"></i> Pessoa Física
</button>
<button type="button" class="filter-tab" [class.active]="filterTipo === 'PJ'" (click)="setFilter('PJ')" [disabled]="loading">
<i class="bi bi-building me-1"></i> Pessoa Jurídica
</button>
</div>
<!-- CLIENTE MULTI-SELECT -->
<div class="client-filter-wrap" (click)="$event.stopPropagation()">
<button
type="button"
class="btn-client-filter"
[class.has-selection]="selectedClients.length > 0"
(click)="toggleClientMenu()"
[disabled]="loading">
<ng-container *ngIf="selectedClients.length === 0">
<i class="bi bi-people-fill me-2"></i>
<span>Clientes</span>
<i class="bi bi-chevron-down ms-2 small"></i>
</ng-container>
<ng-container *ngIf="selectedClients.length > 0">
<div class="chips-container">
<span *ngFor="let client of selectedClients" class="client-chip" (click)="$event.stopPropagation()">
{{ client }}
<i class="bi bi-x chip-close" (click)="removeClient(client, $event)"></i>
</span>
</div>
<i class="bi bi-chevron-down ms-1 small text-muted"></i>
</ng-container>
</button>
<div class="client-dropdown" *ngIf="showClientMenu">
<div class="dropdown-header-search">
<input
type="text"
class="form-control form-control-sm"
placeholder="Buscar na lista..."
[(ngModel)]="clientSearchTerm"
autofocus
(click)="$event.stopPropagation()">
</div>
<div class="dropdown-list">
<div class="dropdown-item-custom" [class.selected]="selectedClients.length === 0" (click)="selectClient(null)">
<i class="bi bi-grid me-2"></i> Todos os Clientes
</div>
<ng-container *ngFor="let client of filteredClientsList">
<div class="dropdown-item-custom" [class.selected]="isClientSelected(client)" (click)="selectClient(client)">
<div class="d-flex align-items-center justify-content-between w-100">
<span>{{ client }}</span>
<i class="bi bi-check-lg text-brand" *ngIf="isClientSelected(client)"></i>
</div>
</div>
</ng-container>
</div>
<div class="dropdown-footer" *ngIf="selectedClients.length > 0">
<button class="btn btn-outline-secondary btn-sm w-100" (click)="clearClientSelection($event)">
<i class="bi bi-x-circle me-1"></i> Limpar seleção
</button>
</div>
</div>
</div>
</div>
<!-- KPIs -->
<div class="fat-kpis mt-4 animate-fade-in">
<div class="kpi kpi-stack kpi-stack-tight">
<span class="lbl">Clientes Faturados</span>
<span class="val">
<span *ngIf="loadingKpis" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loadingKpis">{{ kpiTotalClientes || 0 }}</span>
</span>
</div>
<div class="kpi kpi-stack kpi-stack-tight">
<span class="lbl">Linhas Faturadas</span>
<span class="val">
<span *ngIf="loadingKpis" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loadingKpis">{{ kpiTotalLinhas || 0 }}</span>
</span>
</div>
<div class="kpi kpi-wide kpi-stack">
<span class="lbl text-vivo">Total Vivo</span>
<span class="val">
<span *ngIf="loadingKpis" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loadingKpis">{{ formatMoney(kpiTotalVivo) }}</span>
</span>
</div>
<div class="kpi kpi-wide kpi-stack">
<span class="lbl text-line">Total Line</span>
<span class="val">
<span *ngIf="loadingKpis" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loadingKpis">{{ formatMoney(kpiTotalLine) }}</span>
</span>
</div>
<div class="kpi kpi-stack kpi-stack-tight">
<span class="lbl text-brand">Lucro</span>
<span class="val">
<span *ngIf="loadingKpis" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loadingKpis">{{ formatMoney(kpiLucro) }}</span>
</span>
</div>
</div>
<!-- CONTROLS -->
<div class="controls mt-3 mb-2" data-animate>
<div class="input-group input-group-sm search-group">
<span class="input-group-text">
<i class="bi"
[class.bi-search]="!loading"
[class.bi-hourglass-split]="loading"
[class.text-brand]="loading"></i>
</span>
<input
class="form-control"
placeholder="Pesquisar..."
[(ngModel)]="searchTerm"
(ngModelChange)="onSearch()" />
<button class="btn btn-outline-secondary btn-clear" type="button" (click)="clearSearch()" *ngIf="searchTerm">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div class="page-size d-flex align-items-center gap-2">
<span class="text-muted small fw-bold text-uppercase" style="letter-spacing: 0.5px; font-size: 0.75rem;">
Clientes por pág:
</span>
<div class="select-wrapper">
<app-select class="select-glass" size="sm" [options]="pageSizeOptions" [(ngModel)]="pageSize" (ngModelChange)="onPageSizeChange()" [disabled]="loading"></app-select>
</div>
</div>
</div>
</div>
<!-- BODY: GRUPOS -->
<div class="fat-body">
<div class="groups-container">
<div class="text-center p-5" *ngIf="loading">
<span class="spinner-border text-brand"></span>
</div>
<div class="empty-group" *ngIf="!loading && pagedClientGroups.length === 0">
Nenhum dado encontrado.
</div>
<div class="group-list" *ngIf="!loading">
<div
*ngFor="let g of pagedClientGroups; trackBy: trackByCliente"
class="client-group-card"
[class.expanded]="expandedGroup === g.cliente">
<div class="group-header" (click)="toggleGroup(g.cliente)">
<div class="group-info">
<h6 class="mb-0 fw-bold text-dark td-clip" [title]="g.cliente">{{ g.cliente }}</h6>
<div class="group-badges">
<span class="badge-pill total">{{ g.total }} Registros</span>
<span class="badge-pill lines">{{ g.linhas }} Linhas</span>
<span class="badge-pill vivo">{{ formatMoney(g.totalVivo) }}</span>
<span class="badge-pill line">{{ formatMoney(g.totalLine) }}</span>
<span class="badge-pill lucro" *ngIf="g.lucro !== 0">{{ formatMoney(g.lucro) }}</span>
</div>
</div>
<div class="group-toggle-icon"><i class="bi bi-chevron-down"></i></div>
</div>
<div class="group-body" *ngIf="expandedGroup === g.cliente">
<div class="group-actions-row">
<small class="text-muted fw-bold">Registros do Cliente</small>
<span class="chip-muted"><i class="bi bi-info-circle me-1"></i> Clique no “olho” para ver todos os detalhes</span>
</div>
<div class="table-wrap inner-table-wrap">
<table class="table table-modern table-compact align-middle text-center mb-0">
<thead>
<tr class="thead-group">
<th rowspan="2" class="sortable th-item" (click)="setSort('item')">
<div class="th-content">ITEM</div>
</th>
<th rowspan="2" class="sortable" (click)="setSort('qtdlinhas')">
<div class="th-content">QTD LINHAS</div>
</th>
<th colspan="2" class="th-block th-vivo">VIVO</th>
<th colspan="2" class="th-block th-line">LINE MÓVEL</th>
<th rowspan="2" class="actions-col">AÇÕES</th>
</tr>
<tr class="thead-sub">
<th class="sortable" (click)="setSort('franquiavivo')">
<div class="th-content">FRANQUIA</div>
</th>
<th class="sortable" (click)="setSort('valorcontratovivo')">
<div class="th-content">VALOR (R$)</div>
</th>
<th class="sortable" (click)="setSort('franquialine')">
<div class="th-content">FRANQUIA</div>
</th>
<th class="sortable" (click)="setSort('valorcontratoline')">
<div class="th-content">VALOR (R$)</div>
</th>
</tr>
</thead>
<tbody>
<tr *ngIf="groupRows.length === 0">
<td colspan="7" class="text-center py-4 empty-state text-muted fw-bold">Nenhum registro.</td>
</tr>
<tr *ngFor="let r of groupRows; trackBy: trackById" class="table-row-item">
<td class="text-muted fw-bold">{{ r.item }}</td>
<td class="fw-black">{{ r.qtdLinhas ?? 0 }}</td>
<td class="fw-bold">{{ formatFranquia(r.franquiaVivo) }}</td>
<td class="fw-bold text-vivo">{{ formatMoney(r.valorContratoVivo) }}</td>
<td class="fw-bold">{{ formatFranquia(r.franquiaLine) }}</td>
<td class="fw-bold text-line">{{ formatMoney(r.valorContratoLine) }}</td>
<td>
<div class="action-group justify-content-center">
<button class="btn-icon" (click)="onDetalhes(r)" title="Detalhes"><i class="bi bi-eye"></i></button>
<button class="btn-icon success" (click)="onComparativo(r)" title="Comparativo Vivo x Line"><i class="bi bi-columns-gap"></i></button>
<button *ngIf="isSysAdmin" class="btn-icon primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="onDelete(r)" title="Excluir"><i class="bi bi-trash"></i></button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="fat-footer">
<div class="small text-muted fw-bold">Mostrando {{ pageStart }}{{ pageEnd }} de {{ total }}</div>
<nav>
<ul class="pagination pagination-sm mb-0 pagination-modern">
<li class="page-item" [class.disabled]="page === 1 || loading">
<button class="page-link" (click)="goToPage(page - 1)">Anterior</button>
</li>
<li class="page-item" *ngFor="let p of pageNumbers" [class.active]="p === page">
<button class="page-link" (click)="goToPage(p)">{{ p }}</button>
</li>
<li class="page-item" [class.disabled]="page === totalPages || loading">
<button class="page-link" (click)="goToPage(page + 1)">Próxima</button>
</li>
</ul>
</nav>
</div>
</div>
</div>
</section>
<!-- MODAIS -->
<app-faturamento-modals [vm]="$any(vm)"></app-faturamento-modals>